Pierce Brosnan

**Pierce Brendan Brosnan** Template:Post-nominals (Template:IPAc-en; born 16 May 1953) is an Irish actor and film producer. He is best known as the fifth actor to play secret agent James Bond in the Bond film series, starring in four films from 1995 to 2002 (*GoldenEye*, *Tomorrow Never Dies*, *The World Is Not Enough*, and *Die Another Day*) and in multiple video games.

After leaving school at age 16, Brosnan began training in commercial illustration and went on to attend the Drama Centre in London for three years. Following a stage acting career, he rose to popularity in the television series *Remington Steele* (1982–1987). After the conclusion of the series, Brosnan appeared in films such as the Cold War spy film *The Fourth Protocol* (1987) and the comedy *Mrs. Doubtfire* (1993). After achieving worldwide fame for his role as James Bond, Brosnan took the lead in other major films including the epic disaster adventure film *Dante's Peak* (1997) and the remake of the heist film *The Thomas Crown Affair* (1999). Since leaving the role of Bond, he has starred in films such as the musical comedy *Mamma Mia!* (2008), the political thriller *The Ghost Writer* (2010), the action fantasy *Percy Jackson & the Olympians: The Lightning Thief* (2010), the action spy thriller *The November Man* (2014) and the *Mamma Mia!* sequel *Mamma Mia! Here We Go Again* (2018).

Brosnan has received two Golden Globe Award nominations, first for the miniseries *Nancy Astor* (1982) and next for the dark comedy film *The Matador* (2005). In 1996, along with American film producer Beau St. Clair, he formed a Los Angeles-based production company called Irish DreamTime. He is also known for his charitable work and environmental activism. In 1997, Brosnan received a star on the Hollywood Walk of Fame for his contributions to the film industry. In 2020, he was listed at No. 15 on *The Irish Times*' list of greatest Irish film actors.

## Early life

Brosnan was born on 16 May 1953 at St. Mary's Hospital in Drogheda, County Louth, the only child of May (*Template:Née* Smith) and carpenter Thomas Brosnan. He has the same name as his grandfather, Pierce Brosnan, who in turn was named after his parents' surnames: John Brosnan and Margaret Pierce.Template:Bcn For 12 years, he lived in Navan, County Meath, and said in 1999 that he considers it to be his hometown. His father abandoned the family when Brosnan was an infant. When he was four years old, his mother moved to London to work as a nurse. From that point on, he was largely brought up by his maternal grandparents, Philip and Kathleen Smith. After their deaths, he lived with an aunt and then an uncle, but was subsequently sent to live in a boarding house run by a woman named Eileen. He later said, "Childhood was fairly solitary. I never knew my father. He left when I was an infant. [...] To be Irish Catholic in the 1950s, and have a marriage which was not there, a father who was not there [...] the mother, the wife suffered greatly. My mother was very courageous. She took the bold steps to go away and be a nurse in England. Basically wanting a better life for her and myself. My mother came home once a year, twice a year."

Brosnan was brought up in a Catholic family, and educated in a local school run by the De La Salle Brothers while serving as an altar boy. He left Ireland on 12 August 1964 and went to Scotland to be reunited with his mother and her new husband, William Carmichael, at their home in Longniddry. Carmichael took Brosnan to see a James Bond film for the first time (*Goldfinger*) at the age of 11. They later moved back to London, where Brosnan was educated at Elliott School in Putney, now known as Ark Putney Academy. When discussing his transition from Ireland to England, he said, "When you go to a very large city, a metropolis like London, as an Irish boy of 10, life suddenly moves pretty fast. [...] And you're Irish. And they make you feel it; the British have a wonderful way of doing that, and I had a certain deep sense of being an outsider." His nickname at school was simply "Irish".

After leaving school at 16, Brosnan decided to be a painter and began training in commercial illustration at Saint Martin's School of Art in London. While attending a rehearsal for a workshop at the Oval House, he saw a fire eater teaching people how to eat fire and decided to join in. He later trained for three years as an actor at the Drama Centre London. Describing the feeling of becoming an actor and the influence it had on his life, he said, "When I found acting, or when acting found me, it was a liberation. It was a stepping stone into another life, away from a life that I had, and acting was something I was good at, something which was appreciated. That was a great satisfaction in my life."

## Career

### Early career

Graduating from the Drama Centre in 1975, Brosnan began working as an acting assistant stage manager at the York Theatre Royal, making his acting debut in *Wait Until Dark*. Within six months, he was selected by playwright Tennessee Williams to play the role of McCabe in the British première of *The Red Devil Battery Sign* (billed as "Pierce Brosman"). His performance caused a stir in London and Brosnan still has the telegram sent by Williams, stating only "Thank God for you, my dear boy". In 1977, he was picked by Franco Zeffirelli to appear in the play *Filumena* by Eduardo De Filippo opposite Joan Plowright and Frank Finlay.

Brosnan continued his career making brief appearances in films such as *The Long Good Friday* (1980) and *The Mirror Crack'd* (1980), as well as early television performances in *The Professionals*, *Murphy's Stroke*, and *Play for Today*. He became a television star in the United States with his leading role in the popular miniseries *Manions of America*. He followed this in 1982 with the BBC's nine-part miniseries *Nancy Astor* (which aired in America on *Masterpiece Theatre*) that dramatised the life of Lady Astor, the first woman to sit in the British Parliament. His portrayal of Robert Gould Shaw II garnered him a 1985 Golden Globe Award nomination for Best Supporting Actor.

In 1982, Brosnan moved to Southern California and rose in popularity in the United States playing the title role in the NBC romantic, often-comedic detective series *Remington Steele*. The *Washington Post* noted that same year that Brosnan "could make it as a young James Bond." After *Remington Steele* ended in 1987, Brosnan went on to appear in several films, including *The Fourth Protocol* (1987), a Cold War thriller in which he starred alongside Michael Caine, *The Deceivers* and James Clavell's *Noble House* both in 1988, and *The Lawnmower Man* (1992). In 1992, he shot a pilot for NBC called *Running Wilde,* playing a reporter for *Auto World* magazine with Jennifer Love Hewitt playing his daughter, but the pilot never aired. In 1993 he played a supporting role in the comedy film *Mrs. Doubtfire*. He also appeared in several television films, including *Victim of Love* (1991), *Death Train* (1993) and *Night Watch* (1995), a spy thriller set in Hong Kong. In 2003, Brosnan was rewarded the Irish Film and Television Academy Lifetime Achievement Award for his contribution to Irish Film.

### James Bond

 File:PierceBrosnanCannesPhoto2.jpg
Brosnan first met James Bond film producer Albert R. Broccoli on the sets of *For Your Eyes Only*, as his first wife, Cassandra Harris, had been cast as Countess Lisl von Schlaf, mistress to Milos Columbo. Broccoli said, "if he can act ... he's my guy" to inherit the role of Bond from Roger Moore. It was reported by both *Entertainment Tonight* and the *National Enquirer,* that Brosnan was going to inherit another role of Moore's, that of Simon Templar in *The Saint*. Brosnan denied the rumours in July 1993 but added, "it's still languishing there on someone's desk in Hollywood."

In 1986, NBC cancelled *Remington Steele*. As Brosnan was offered the role of James Bond, the publicity improved *Remington Steele*'s ratings and it was renewed, contractually requiring Brosnan to return to the show. This caused Eon Productions to have to look elsewhere for the new 007.Template:R The producers instead hired Timothy Dalton for *The Living Daylights* (1987), and *Licence to Kill* (1989). While Brosnan was reluctant to discuss losing the Bond role, in part because Dalton was a friend, he appeared in Diet Coke commercials portraying what the *Los Angeles Times* described as "a dashing Bond-like character", and NBC advertised *Noble House* with Brosnan dressed in a Bond-like tuxedo. Legal disputes between the Bond producers and the studio over distribution rights resulted in the cancellation of a proposed third Dalton film in 1991 and put the Bond series on a hiatus for several years. After the legal issues had been resolved, Dalton decided not to return for a third film. On 7 June 1994, Brosnan was announced as the fifth actor to play Bond.

Brosnan was signed for a three-film Bond deal with the option of a fourth. The first, 1995's *GoldenEye*, grossed US$350 million worldwide, the fourth highest worldwide gross of any film in 1995, making it the most successful Bond film since *Moonraker*, adjusted for inflation. It holds a 78% Rotten tomato rating, while *Metacritic* holds it at 65%. In the *Chicago Sun-Times*, Roger Ebert gave the film 3 stars out of 4, saying that Brosnan's Bond was "somehow more sensitive, more vulnerable, more psychologically complete" than the previous ones, also commenting on Bond's "loss of innocence" since previous films. James Berardinelli described Brosnan as "a decided improvement over his immediate predecessor" with a "flair for wit to go along with his natural charm", but added that "fully one-quarter of *Goldeneye* is momentum-killing padding."

 File:Pierce Brosnan (Madame Tussauds).jpg
In 1996, Brosnan formed a film production company called Irish DreamTime along with producing partner and longtime friend Beau St. Clair. Brosnan and St. Clair released Irish DreamTime's first production, *The Nephew*, in 1998. One year later, the company's second studio project, *The Thomas Crown Affair*, was released and met both critical and box office success.

Brosnan returned in 1997's *Tomorrow Never Dies* and 1999's *The World Is Not Enough*, which were also successful. In 2002, Brosnan appeared for the fourth time as Bond in *Die Another Day*, receiving mixed reviews similarly to the former two, but was a success at the box office. Brosnan himself subsequently criticised many aspects of his fourth Bond movie. During the promotion, he mentioned that he would like to continue his role as James Bond: "I'd like to do another, sure. Connery did six. Six would be a number, then never come back." Brosnan asked Eon Productions, when accepting the role, to be allowed to work on other projects between Bond films. The request was granted, and for every Bond film, Brosnan appeared in at least two other mainstream films, including several he produced, playing a wide range of roles, ranging from a scientist in Tim Burton's *Mars Attacks!*, to the title role in *Grey Owl* which documents the life of Englishman Archibald Stansfeld Belaney, one of Canada's first conservationists.

Shortly after the release of *Die Another Day*, the media began questioning whether or not Brosnan would reprise the role for a fifth time. At that time, Brosnan was approaching his 50th birthday. Brosnan kept in mind that both fans and critics were very unhappy with Roger Moore playing the role until the age of 57, but he was receiving popular support from both critics and the franchise fanbase for a fifth instalment. For this reason, he remained enthusiastic about reprising his role. In October 2004, Brosnan said he considered himself dismissed from the role. Although Brosnan had frequently been rumoured as still in the running to play 007, he had denied it several times, and in February 2005 he posted on his website that he was finished with the role. Daniel Craig took over the role on 14 October 2005. In an interview with *The Globe and Mail*, Brosnan was asked what he thought of Craig as the new James Bond. He replied, "I'm looking forward to it like we're all looking forward to it. Daniel Craig is a great actor and he's going to do a fantastic job". He re-affirmed this support in an interview to the *International Herald Tribune*, stating that "[Craig's] on his way to becoming a memorable Bond." Brosnan later admitted that he was hurt by the end of his tenure; "Barbara [Broccoli] and Michael [Wilson] were on the line – 'We're so sorry'. She was crying, Michael was stoic and he said, 'You were a great James Bond. Thank you very much' and I said, 'Thank you very much. Goodbye.'
"That was it. I was utterly shocked and just kicked to the kerb with the way it went down."

During his tenure on the James Bond films, Brosnan also took part in James Bond video games. In 2002, Brosnan's likeness was used as the face of Bond in the James Bond video game *Nightfire* (voiced by Maxwell Caulfield). In 2004, Brosnan starred in the Bond game *Everything or Nothing,* contracting for his likeness to be used as well as doing the voice-work for the character. He also starred along with Jamie Lee Curtis and Geoffrey Rush in *The Tailor of Panama* in 2001, and lent his voice to *The Simpsons* episode "Treehouse of Horror XII" as a machine.

### Post-James Bond

 File:Pierce Brosnan at the 2005 Toronto Film Festival.jpg
Since 2004, Brosnan has talked of backing a film about Caitlin Macnamara, wife of poet Dylan Thomas, the title role to be played by Miranda Richardson. Brosnan's first post-Bond role was that of Daniel Rafferty in 2004's *Laws of Attraction*. Garreth Murphy, of entertainment.ie, described Brosnan's performance as "surprisingly effective, gently riffing off his James Bond persona and supplementing it with a raffish energy". In the same year, Brosnan starred in *After the Sunset* alongside Salma Hayek and Woody Harrelson. The film received generally negative reviews and was a box office flop. Brosnan's next film was 2005's *The Matador*. He starred as Julian Noble, a jaded, neurotic assassin who meets a travelling salesman (Greg Kinnear) in a Mexican bar. The film garnered generally positive reviews. Roger Ebert for the *Chicago Sun-Times* called Brosnan's performance the best of his career. Brosnan was nominated for a Golden Globe award for Best Actor in a Musical or Comedy, but lost to Joaquin Phoenix for *Walk the Line.*. In 2006, Brosnan narrated *The Official Film of the 2006 FIFA World Cup*, directed by Michael Apted.

In 2007, Brosnan appeared in the film *Seraphim Falls* alongside fellow Irishman Liam Neeson. The film was released for limited screenings on 26 January 2007 to average reviews. Kevin Crust of the *Los Angeles Times* noted that Brosnan and Neeson made "fine adversaries;" Michael Rechtshaffen of *The Hollywood Reporter* thought that they were "hard-pressed to inject some much-needed vitality into their sparse lines." During the same year, Brosnan spoke of making a western with fellow Irish actors Gabriel Byrne and Colm Meaney. In that same year Brosnan starred as Tom Ryan in *Butterfly on a Wheel*. The film was released in the United States under the name of *Shattered* and in Europe as *Desperate Hours*.

In 2008, Brosnan joined Meryl Streep in the film adaptation of the ABBA musical *Mamma Mia!*. He played Sam Carmichael, one of three men believed to be the father of Sophie (Amanda Seyfried), while Streep played Sophie's mother. Judy Craymer, producer of the film, said "Pierce brings a certain smooch factor, and we think he'll have great chemistry with Meryl in a romantic comedy." Brosnan's preparation in singing for the role included walking up and down the coast and singing karaoke to his own voice for about six weeks, followed by rehearsals in New York, which he noted "sounded dreadful." Brosnan's singing in the film was generally disparaged by critics, with his singing compared in separate reviews to the sound of a water buffalo, a donkey, and a wounded raccoon. In September 2008, Brosnan provided the narration for the *Thomas & Friends* movie *The Great Discovery*. He was originally set to narrate for both US and UK from Series 12 to Series 14, but withdrew from it for unknown reasons.

 File:Pierce Brosnan (Berlin Film Festival 2010).jpg
In 2009, Brosnan finished the well-received *The Ghost Writer*, playing a disgraced British Prime Minister, directed and produced by Roman Polanski. The film won a Silver Bear at the Berlin International Film Festival. He starred as Charles Hawkins in the film *Remember Me* and as Chiron in *Percy Jackson & the Olympians: The Lightning Thief*, both released in 2010. In 2012, Brosnan played the role of Philip in the Danish romantic comedy *Love Is All You Need*.

In February 2013, Brosnan was awarded honorary patronage of the Dublin University Players society at Trinity College, Dublin. He also starred opposite Owen Wilson in *No Escape*, playing a "heroic government agent". Brosnan was slated to star in *Last Man Out*, a crime film adapted from Stuart Neville's novel titled *The Twelve* (released as *Ghosts of Belfast* in the US) by Craig Ferguson and Ted Mulkerin, with director Terry Loan at the helm. However, it never came to fruition after years in development, and Brosnan is no longer attached to the production.

In 2013, Brosnan appeared in television commercials as a tongue in cheek version of himself to promote the launch of Sky Broadband in Ireland. In 2005, Brosnan was reported to be starring in *The November Man*, an adaptation of Bill Granger's novel, *There Are No Spies*. but the project was cancelled in 2007. After its cancellation, Brosnan's production company, "Irish DreamTime" resurrected *The November Man* in 2012 with an announcement made on his part that he was jumping back to the spy arena. Filming took place in Serbia a year later, with Brosnan in action as a retired CIA operative called Devereaux, alongside co-star Olga Kurylenko in a supporting role. The film was received negatively, with a 34% on Rotten Tomatoes and a 38/100 on Metacritic. In 2015, he appeared alongside Milla Jovovich in a suspense thriller movie written by Phil Shelby, called *Survivor*, which began filming in January 2014, with Charles and Irwin Winkler producing, and James McTeigue directing. Brosnan later starred in a revenge thriller called *I.T.*, which was released in September 2016, in a limited release and through video on demand.

 File:Pierce Brosnan Deauville 2019 (fixed spots).jpg
Brosnan replaced actor Sam Neill in the role of Eli McCullough in a television adaptation of Philipp Meyer's novel *The Son*, with Kevin Murphy serving as both executive producer and showrunner of the series, which began production in June 2016, and aired for two seasons from 2017 to 2019. In 2017, Brosnan starred in *The Foreigner*, opposite Jackie Chan, as a former IRA man turned government official, Liam Hennessy. *The Foreigner* was filmed in London, and was directed by Martin Campbell, who previously worked with Brosnan on his debut James Bond film, *GoldenEye*. It was noted that Brosnan's character bore a strong resemblance to Sinn Féin leader Gerry Adams.

A third-time collaboration with director Martin Campbell would have seen Brosnan starring in a film adaptation of Ernest Hemingway's 1950 novel *Across the River and into the Trees*. However, the reluctance of investors to finance the production along with several delays led both Brosnan and Campbell to drop out of the project. Paula Ortiz was later brought on to direct the film and Liev Schreiber replaced Brosnan in the lead role.

In 2018, Brosnan co-starred with Guy Pearce and Minnie Driver in a mystery thriller film called *Spinning Man*, based on George Harrar's novel of the same name. He later reprised his role as Sam Carmichael in the sequel to Catherine Johnson's *Mamma Mia!*, entitled *Mamma Mia! Here We Go Again*, along with the rest of the cast members from the first film. He also had a supporting role in the action thriller headlined by Dave Bautista in *Final Score*.

In 2021, Brosnan executive produced and starred in the heist caper entitled *The Misfits* directed by Renny Harlin from a screenplay by Robert Henry and Kurt Wimmer. The film was released to poor reception from critics. He also appeared in A24's horror film *False Positive* opposite Ilana Glazer, who co-wrote and co-produced it, as well. The film was a moderate success.

### Upcoming roles

Brosnan will soon be seen as King Louis XIV in the family adventure film *The King’s Daughter* based on the 1997 novel *The Moon and the Sun* by Vonda N. McIntyre. It is set to have a limited theatrical release in the U.S. and Canada on 21 January 2022.

Brosnan was touted to star opposite Jamie Dornan and Cillian Murphy in a film based on the events of The Maze Prison Escape, titled *H-Block* with Jim Sheridan at the helm. Initially, filming was to begin sometime in August 2017 before getting postponed to March 2018 due to scheduling conflicts with cast members. As of today, preliminary production on the film is yet to commence.

Alongside Jesse Eisenberg and Vanessa Redgrave, Brosnan is set to appear in a historical period piece based on a novel by Jonathan Miles, entitled *The Wreck of The Medusa*, set against the backdrop of the early 19th century during King Louis XVIII's reign in France, with the film to be directed by Peter Webber. During an interview in October 2020, Webber claimed that the lack of interest in "films about artists" in the market earned the project several setbacks, and as such still remains in development.

In July 2017, a rumour surfaced that Brosnan was linked to an upcoming video game entitled *Shadow Stalkers*, a project led by ZOOM Platform. However, his role remains undisclosed as yet.

At *Star Wars* Celebration 2019 in Chicago, a mural was revealed showing an artist's rendering of Grand Admiral Thrawn with a strong resemblance to Brosnan, fuelling speculation about an upcoming role in the *Star Wars* franchise.

In March 2021, Brosnan was cast as Doctor Fate/Kent Nelson in the DC Extended Universe film, *Black Adam*. In July 2021, he joined Adam Devine in the Netflix action-comedy film *The Out-Law* directed by Tyler Spindel. In October 2021, Brosnan was set to lead the hitman thriller film *Fast Charlie* directed by Phillip Noyce.

## Charity and activism

 File:Pierce Brosnan sitting in Pacific Green sofa.jpg
Brosnan has been an Ambassador for UNICEF Ireland since 2001 and recorded a special announcement to mark the launch of UNICEF's "Unite for Children, Unite against AIDS" Campaign with Liam Neeson. Brosnan supported John Kerry in the 2004 presidential election and is a vocal supporter of same-sex marriage.

Brosnan first became aware of the nuclear arms race in 1962, at the age of nine, when worldwide condemnation of U.S. nuclear testing in Nevada made international headlines. During the 1990s, he participated in news conferences in Washington, D.C. to help Greenpeace draw attention to the campaign to bring an end to nuclear testing with a Comprehensive Nuclear-Test-Ban Treaty. Brosnan boycotted the French premiere of *GoldenEye* in support of Greenpeace's protest against the French nuclear testing program.

From 1997 to 2000, Brosnan and wife Smith worked with the Natural Resources Defense Council (NRDC) and International Fund for Animal Welfare (IFAW) to stop a proposed salt factory from being built at Laguna San Ignacio. The couple with Halle Berry, Cindy Crawford and Daryl Hannah successfully fought the Cabrillo Port Liquefied Natural Gas facility that was proposed off the coast of Malibu; the State Lands Commission eventually denied the lease to build the terminal. In May 2007, Governor Arnold Schwarzenegger vetoed the facility.

In May 2007, Brosnan and Smith donated $100,000 to help replace a playground on the Hawaiian island of Kauai, where they own a house. On 7 July 2007, Brosnan presented a film at Live Earth in London; he also recorded a television advertisement for the cause.

Brosnan is also listed as a member of the Sea Shepherd Conservation Society's Board of Advisors. In 2004, he was named 'Best-dressed Environmentalist' by the Sustainable Style Foundation.

Brosnan also raises money for charitable causes through sales of his paintings. After Brosnan left school, he pursued a career in art and began working as an illustrator. "I always wanted to be an artist, a painter. I started as a Trainee Artist in a small studio in South London." A colleague suggested that Pierce attend a theatre workshop, and eventually he abandoned his artwork to pursue a career in acting. Brosnan took up painting again in the late 1980s during his first wife's illness as he found it therapeutic. "Sometimes dramatic moments affect the way you see yourself in the world…from a very hard time in my life, I started painting again and out came every colour I could imagine." Citing his influences as Picasso, Matisse, Bonnard and Kandinsky, Brosnan spends much of his free time between film shoots in front of his easel. "I am self taught, an enthusiastic painter as a friend of mine likes to say." He has continued painting since then, using spare time on set and at home. Profits from sales of giclée prints of his works are given to a trust to benefit "environmental, children's and women's health charities." Since Harris' death, Brosnan has been an advocate for cancer awareness and, in 2006, he served as spokesperson for Lee National Denim Day, a breast cancer fundraiser which raises millions of dollars and raises more money in a single day than any other breast cancer fundraiser.

In 2021, Brosnan launched his first-ever NFT collection of digital artworks "Big Noise" - on a digital platform focusing on artist empowerment, sustainability and technical innovation - inspired by his painting "Earplugs" which he painted while filming the James Bond film *GoldenEye*, incorporating abstract movement, self-recorded sound elements, including his voice, and bespoke visuals.

## Personal life

 File:Pierce Brosnan at the LBJ Presidential Library 2017.jpg
Brosnan has married twice, having been widowed once, and has five children and three grandchildren. He met Australian actress Cassandra Harris through her stepson David Harris, the nephew of Richard Harris, shortly after leaving drama school. On meeting her, he said, "What a beautiful looking woman. I never for an instant thought she was someone I'd spend 17 years of my life with. I didn't think of wooing her, or attempting to woo her; I just wanted to enjoy her beauty and who she was." They began dating and bought a house in Wimbledon. They married in December 1980 and had one son together, Sean, who was born on 13 September 1983 and later became an actor. They also lived with Harris' children, Charlotte (1971–2013) and Chris, and Brosnan adopted them after their father Dermot Harris died in 1986; they subsequently took on his surname. Early in their relationship, Brosnan worked in West End plays and television films. After Harris appeared in the James Bond film *For Your Eyes Only* in 1981, they secured a bank loan and moved to Southern California, where Brosnan was cast in the title role of the TV series *Remington Steele*, easing their financial worries.

An episode of *Remington Steele* that was filmed in Ireland generated significant publicity. This led to Brosnan meeting his father, who had left when Brosnan was an infant, at his hotel. He expected to see a "very tall man" but instead described his father as "a man of medium stature, pushed-back silver hair, flinty eyes, and a twizzled jaw" who "had a very strong Kerry accent". He was regretful that they met under such public circumstances and wished for a more private arrangement.

While filming *The Deceivers* in Rajasthan in 1987, Harris became seriously ill. She was later diagnosed with ovarian cancer and died on 28 December 1991 at age 43. Brosnan struggled to cope with her death:

> "When your partner gets cancer, then life changes. Your timetable and reference for your normal routines and the way you view life, all this changes. Because you're dealing with death. You're dealing with the possibility of death and dying. And it was that way through the chemotherapy, through the first-look operation, the second look, the third look, the fourth look, the fifth look. Cassie was very positive about life. I mean, she had the most amazing energy and outlook on life. It was and is a terrible loss, and I see it reflected, from time to time, in my children."
>
>

 Harris had always wanted Brosnan to play James Bond; in 1995, four years after her death, Brosnan secured the role. His adopted daughter Charlotte also died of ovarian cancer, on 28 June 2013.

In 1994, Brosnan met American journalist Keely Shaye Smith in Mexico. They were married in 2001 at Ballintubber Abbey in Ireland. They have two sons together named Dylan and Paris. They reside primarily in Malibu, California, with a second American home in Hawaii and Irish residences in Dublin and County Meath. On 11 February 2015, the $18 million Malibu home caught fire and sustained $1 million in damages. During the 30 minutes it took firefighters to extinguish the fire, flames destroyed the contents of the garage, including Brosnan's 2002 Aston Martin V12 Vanquish, and spread to a bedroom above it; no injuries were sustained.

In July 2003, Queen Elizabeth II made Brosnan an honorary Officer of the Order of the British Empire for his "outstanding contribution to the British film industry". As an Irish citizen, he is ineligible to receive the full OBE honour, which is awarded only to a citizen of the Commonwealth realms, but he is still allowed the letters "OBE" after his name. In 2002, he was also awarded an honorary degree from the Dublin Institute of Technology and, a year later, the University College Cork. On 23 September 2004, he became an American citizen while retaining his Irish citizenship. He said, "My Irishness is in everything I do. It's the spirit of who I am, as a man, an actor, a father. It's where I come from." When asked by a fan if it annoyed him when people confused his nationality because of his fairly neutral accent, he said, "It amuses me in some respects that they should confuse me with an Englishman when I'm a dyed-in-the-wool, born and bred Irishman... I don't necessarily fly under any flag, but no, it doesn't bother me."

Brosnan has expressed contempt for his Christian Brothers education, but commented in 2013,

> "It always helps to have a bit of prayer in your back pocket. At the end of the day, you have to have something and for me that is God, Jesus, my Catholic upbringing, my faith... God has been good to me. My faith has been good to me in the moments of deepest suffering, doubt and fear. It is a constant, the language of prayer. I might not have got my sums right from the Christian Brothers or might not have got the greatest learning of literature from them, but I certainly got a strapping amount of faith."
>
>

 He attends Mass but also adheres to other spiritual beliefs, stating in 2008 that he loves "the teachings of Buddhist philosophy", which he referred to as his "own private faith". He elaborated, "I don't preach it, but it's a faith that is a comfort to me when the night is long."

## Filmography

### Video games

 2004 | *James Bond 007: Everything or Nothing* | Physical likeness and voice

## References

1. ↑ Barnes, Mike. "Beau St. Clair, Pierce Brosnan's Producing Partner, Dies at 63", *The Hollywood Reporter*, 11 January 2016.
2. ↑ Pierce Brosnan (en).
3. ↑ The 50 greatest Irish film actors of all time – in order.
4. ↑ 4.00 4.01 4.02 4.03 4.04 4.05 4.06 4.07 4.08 4.09 4.10 Brosnan's personal site.
5. ↑ Brosnan Family Tree.
6. ↑ "Pierce Brosnan honoured by Navan Town", RTÉ News, 11 November 1999.
7. ↑ 7.0 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 Chutkow, Paul (December 1997). Brosnan. Pierce Brosnan.. Cigaraficionado.com.
8. ↑ Farndale, Nigel. "Pierce Brosnan: James who?", *The Daily Telegraph*, 29 January 2008.
9. ↑ Louie, Rebbeca. "Eyes that Pierce. After Bond, Brosnan looks to future with a killer role", *Daily News*, 25 December 2005.  [dead link]
10. ↑ 10.0 10.1 10.2 Template:Cite magazine
11. ↑ 11.0 11.1 Template:Cite episode
12. ↑ Template:Cite episode
13. ↑ "Brosnan is a true Father Figure", ShowBiz Ireland, 1 November 2002.
14. ↑ Nathan, Ian (October 1997). "The Empire 100 Interview". *Empire* (100): 116.
15. ↑ 15.0 15.1 Butler, Karen. "Fierce Brosnan", Irish Echo Online, February 2007.
16. ↑ Jonathan Jones (30 September 2011). Saint Martins emerges blinking in bright new home. But is it art?: King's Cross premises a far cry from Soho 'hell', but some students fear college will have lost its charm --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};. *The Guardian*. Accessed August 2013.
17. ↑ Alumni. St Martins College.
18. ↑ Pierce Brosnan: questions from the floor --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};: guardian.co.uk Guardian News and Media – 18 March 2003
19. ↑ Drama Centre London: Former. Central Saint Martins College of Art and Design.
20. ↑ Carrick, Peter (2002). *Pierce Brosnan.* Citadel Press, p18–36. Template:ISBN.
21. ↑ Membery, York (2002). *Pierce Brosnan: The Biography.* Virgin Books. Template:ISBN.
22. ↑ Obit of Helen Montagu. 8 January 2004. *The Independent*[dead link]. Retrieved 27 August 2010
23. ↑ Manions of America. *MTV Movies*.
24. ↑ Awards for Pierce Brosnan.
25. ↑ 25.0 25.1 25.2 25.3 25.4 Last, Kimberly (1996). "Pierce Brosnan's Long and Winding Road To Bond". *007 Magazine*. http://www.klast.net/bond/pb\_road.html. Retrieved 22 February 2007.
26. ↑ Template:Cite episode
27. ↑ Belson, Eve (July 1993). "Pierce Brosnan: Urbane Leading Man". *Orange Coast*.
28. ↑  (1998) *When the Snow Melts: The Autobiography of Cubby Broccoli*. London, England: Boxtree, page 280–281. ISBN 978-0-7522-1162-6.
29. ↑ Broeske, Pat H.. "'Noble House' Hype: NBC's Heavy Artillery", *Los Angeles Times*, 21 February 1988.
30. ↑ Chapman, James (2000). *Licence to Thrill: A Cultural History of the James Bond Films*. New York City: Columbia University Press, page 247. ISBN 978-0-231-12048-7.
31. ↑ GoldenEye.
32. ↑ 1995 Worldwide Grosses. *Box Office Mojo*.
33. ↑ Box Office History for James Bond Movies. *The Numbers*. Nash Information Service.
34. ↑ GoldenEye (1995). *rottentomatoes.com*.
35. ↑ GoldenEye. *metacritic.com*.
36. ↑ Ebert, Roger. "GoldenEye", Sun-Times Media Group, 17 November 1995.
37. ↑ Berardinelli, James (1995). GoldenEye. *reelviews.net*.
38. ↑ McNary, Dave. "Beau St. Clair, Pierce Brosnan's Producing Partner, Dies", *Variety*, 11 January 2016.
39. ↑ Bio Basics. PBFC.
40. ↑ 40.0 40.1 Nathon, Ian (December 2002). "Numero Uno (Die Another Day cover story)". *Empire* (162): 103.
41. ↑ "Brosnan uncertain over more Bond", 2 April 2004.
42. ↑ "Brosnan: No More 007", *scifi.com*, 14 October 2004.
43. ↑ Brosnan, Pierce (February 2005). The Official Pierce Brosnan site.
44. ↑ "Daniel Craig takes on 007 mantle", BBC, 14 October 2005.
45. ↑ "Pierce Brosnan answers", *Globe and Mail*, 14 September 2006.
46. ↑ Anderson, John. "A grittier Brosnan takes on riskier roles", *International Herald Tribune*, 22 January 2007.
47. ↑  (2015) *Some Kind of Hero: The Remarkable Story of the James Bond Films*. Cheltenham, Gloustershire, England: The History Press. ISBN 978-0750966504.
48. ↑ Everything or Nothing. EA Games.
49. ↑ Thorpe, Vanessa. "Race to put the passion of Dylan's Caitlin on big screen", *The Observer*, 26 November 2006.
50. ↑ Murphy, Garreth (10 May 2004). Laws of Attraction. *entertainment.ie*.
51. ↑ After the Sunset.
52. ↑ The Matador.
53. ↑ Ebert, Roger. "The Matador (2005)", *Chicago Sun-Times*, 6 January 2006.
54. ↑ "'Brokeback Mountain' leads Golden Globe nominations", CNN, 15 December 2005.
55. ↑ "The 2006 FIFA World Cup Film: The Grand Finale (2006)" --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};. The New York Times. Retrieved 28 January 2013
56. ↑ Crust, Kevin (26 January 2007). Seraphim Falls. *Los Angeles Times*.
57. ↑ Rechtshaffen, Michael. "Seraphim Falls", *The Hollywood Reporter*, 18 September 2006.
58. ↑ "Pierce Brosnan Plans All-Irish Western", *StarPulse News*, 17 March 2007.
59. ↑ "Pierce Brosnan to Romance Meryl Streep in Mamma Mia! Movie", Theatre.com, 7 March 2007.
60. ↑ Kit, Borys. "Brosnan joining Streep in "Mamma Mia!"", *Reuters*, 7 March 2007.
61. ↑ "Brosnan set for Abba show movie", *BBC News*, 7 March 2007.
62. ↑ Welcome to Paradise! | PARADE Magazine. Parade.com (15 June 2008).
63. ↑ Bat Out of Hell ShareThis --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};, New York Magazine, March 2009
64. ↑ Bat Out of Hell --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};, Philadelphia Inquirer Movie Review, 18 July 2008
65. ↑ Mamma Mia! (PG-13) \*\*½ --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};, Miami Herald Movies, 18 July 2008
66. ↑ A true romantic. Dfi.dk (10 May 2012).
67. ↑ "Dublin University honours actor Pierce Brosnan".
68. ↑ Cannes 2012: Pierce Brosnan Joins Crime Scene's Thriller 'The Coup'. *The Hollywood Reporter* (1 May 2012).
69. ↑ Pierce Brosnan to lead "Last Man Out". *ComingSoon.net* (7 February 2013).
70. ↑ Pierce Brosnan helps launch €10m Sky campaign. *Irish Independent* (9 April 2013).
71. ↑ Guider, Elizabeth. "Duo plant a Wildflower", *Variety*, 13 December 2005.
72. ↑ Pierce Brosnan and Dominic Cooper to Star in Spy Thriller NOVEMBER MAN (15 May 2012).
73. ↑ Shooting begins on thriller November Man (19 June 2013).
74. ↑ The November Man Reviews.
75. ↑ The November Man (2014).
76. ↑ Millennium Sets Milla Jovovich, Emma Thompson, Pierce Brosnan, Angela Bassett For 'Survivor' Thriller Pic. *Deadline* (7 October 2013).
77. ↑ Pierce Brosnan Stars In Voltage Thriller "I.T.". *Deadline* (3 October 2013).
78. ↑ Pierce Brosnan's 'I.T.' Acquired By RLJ Entertainment For September Bow (27 July 2016).
79. ↑ Pierce Brosnan To Star In AMC Series 'The Son' In TV Return, Replaces Sam Neill (6 June 2016).
80. ↑ Pierce Brosnan looks exactly like Gerry Adams for new movie (20 January 2016).
81. ↑ 'Goldeneye' Star and Director to Reteam For Ernest Hemingway War Movie. *Movies.com* (8 February 2016).
82. ↑ Exclusive interview: Martin Campbell makes Jackie Chan serious in The Foreigner. *Monsters & Critics* (9 October 2017).
83. ↑ "Liev Schreiber To Lead Hemingway Adaptation 'Across The River And Into The Trees', Film To Shoot In Venice Next Month)", *Deadline.com*, 7 September 2020.
84. ↑ Simon Kaijser's Thriller 'Spinning Man' (25 April 2017).
85. ↑ Behind the Scenes of Mamma Mia! Here We Go Again. *E!* (21 October 2017).
86. ↑ Pierce Brosnan joins Dave Bautista in 'Final Score' (26 July 2016).
87. ↑ Berlin: Pierce Brosnan to Star in Heist Film 'The Misfits' (Exclusive) (12 February 2019).
88. ↑ Pierce Brosnan shares a glimpse of 'The Misfits' set in Abu Dhabi (24 February 2019).
89. ↑ Renny Harlin's Pierce Brosnan Starrer 'The Misfits' Gets U.S. & Int'l Deals, First Look Image (9 March 2021).
90. ↑ Pierce Brosnan Joins Elaborate Heist in New 'The Misfits' Trailer (28 April 2021).
91. ↑ The Misfits (2021) Reviews. CBS Interactive.
92. ↑ 'The King's Daughter': Gravitas Ventures Nabs Rights To Fantasy Film Starring Pierce Brosnan & More, With Narration By Julie Andrews (20 October 2021).
93. ↑ Director Jim Sheridan's Next: 'H-Block' Coming Together With Cillian Murphy, Jamie Dornan, Pierce Brosnan (1 May 2017).
94. ↑ Jim Sheridan's Prison Break Movie 'H Block' Being Postponed (7 August 2017).
95. ↑ Pierce Brosnan, Jesse Eisenberg And Vanessa Redgrave On For The Medusa (11 May 2017).
96. ↑ Peter Webber to Direct Series Sequel to Netflix's Colombian Teen Drama 'Pickpockets' (Exclusive) (26 October 2020).
97. ↑ Former SEGA CEO Bernie Stolar teams up with 3D Realms on new project (7 July 2017).
98. ↑ Star Wars: Live-Action Thrawn Art Looks EXACTLY Like Pierce Brosnan (en-US) (12 April 2019).
99. ↑ 'Black Adam': Pierce Brosnan to Play DC Hero Dr. Fate Opposite Dwayne Johnson (Exclusive) (24 March 2021).
100. ↑ Pierce Brosnan Joins Adam Devine in Netflix's Action-Comedy 'The Out-Laws'; Tyler Spindel To Direct (12 July 2021).
101. ↑ Pierce Brosnan To Star In Phillip Noyce's Hitman Thriller 'Fast Charlie' — AFM (28 October 2021).
102. ↑ "Children and AIDS". UNICEF --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};. Retrieved 25 October 2008
103. ↑ Metro.co.uk.
104. ↑ Lang, Kirsty. "Bond drops a bomb", *The Sunday Times*, 3 December 1995.
105. ↑ Silverman, Stephen M. (11 April 2007). Halle Berry, Others Protest Natural Gas Facility. *People*. Time Inc..
106. ↑ Cabrillo Port Dies a Santa Barbara Flavored Death End-Zone Dance --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive};, *Independent*, 24 May 2007
107. ↑ *Washington Post* article "Brosnan, Wife Help School Kids in Hawaii" 31 May 2007. Retrieved 27 August 2010
108. ↑ "London Live Earth line-up revealed", *NME News*, 5 July 2007.
109. ↑ Sea Shepherd Advisors – Pierce Brosnan.
110. ↑ Sustainable Style Foundation.
111. ↑ The Official Website of Pierce Brosnan.
112. ↑ "Pierce Brosnan to promote Lee breast cancer fund raiser". *The Business Journal* (American City Business Journals). 10 July 2006. http://triad.bizjournals.com/triad/stories/2006/07/10/daily18.html. Retrieved 22 February 2007.
113. ↑ From Bond Legend To Art 'LGND': Interview With Artistically Gifted James Bond Star PIERCE BROSNAN On Art, Acting And His Amazing Debut NFT Collection On LGND.Art. Interview by Georgina Lara Booth. Mashable (19 June 2021).
114. ↑ Crosby, Christine (26 September 2011). Pierce Brosnan - Why Is This Man Average?.
115. ↑ Lipworth, Elaine. "Pierce Brosnan: A new licence to thrill", *The Independent*, 17 February 2006.
116. ↑ "Cassandra Harris, Actress, 39", *The New York Times*, 31 December 1991.
117. ↑ Template:Cite magazine
118. ↑ "Pierce Brosnan's Son, Dylan, Is A Model" --[[ ----------------------------------

Lua module implementing the Template loop detected: Template:Webarchive template.

A merger of the functionality of three templates: Template:Wayback, Template:Webcite and Template:Cite archives

]]

--[[--------------------------< D E P E N D E N C I E S >------------------------------------------------------
]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;

--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------
]]

local categories = {}; -- category names
local config = {}; -- global configuration settings
local digits = {}; -- for i18n; table that translates local-wiki digits to western digits
local err\_warn\_msgs = {}; -- error and warning messages
local excepted\_pages = {};
local month\_num = {}; -- for i18n; table that translates local-wiki month names to western digits
local prefixes = {}; -- service provider tail string prefixes
local services = {}; -- archive service provider data from
local s\_text = {}; -- table of static text strings used to build final rendering
local uncategorized\_namespaces = {}; -- list of namespaces that we should not categorize
local uncategorized\_subpages = {}; -- list of subpages that should not be categorized

--[[--------------------------< P A G E S C O P E I D E N T I F I E R S >----------------------------------
]]

local non\_western\_digits; -- boolean flag set true when data.digits.enable is true
local this\_page = mw.title.getCurrentTitle();

local track = {}; -- Associative array to hold tracking categories
local ulx = {}; -- Associative array to hold template data

--[[--------------------------< S U B S T I T U T E >----------------------------------------------------------

Populates numbered arguments in a message string using an argument table.

]]

local function substitute (msg, args)
 return args and mw.message.newRawMessage (msg, args):plain() or msg;
end

--[[--------------------------< tableLength >-----------------------

Given a 1-D table, return number of elements

]]

local function tableLength(T)
 local count = 0
 for \_ in pairs(T) do count = count + 1 end
 return count
end

--[=[-------------------------< M A K E \_ W I K I L I N K >----------------------------------------------------

Makes a wikilink; when both link and display text is provided, returns a wikilink in the form D; if only
link is provided, returns a wikilink in the form L; if neither are provided or link is omitted, returns an
empty string.

]=]

local function make\_wikilink (link, display, no\_link)
 if nil == no\_link then
 if link and ( *~= link) then*
 if display and ( *~= display) then*
 return table.concat ({'', display, ''});
 else
 return table.concat ({'', link, ''});
 end
 end
 return display or *; -- link not set so return the display text*

 else -- no\_link
 if display and ( *~= display) then -- if there is display text*
 return display; -- return that
 else
 return link or *; -- return the target article name or empty string*
 end
 end
end

--[[--------------------------< createTracking >-----------------------

Return data in track[] ie. tracking categories

]]

local function createTracking()
 if not excepted\_pages[this\_page.fullText] then -- namespace:title/fragment is allowed to be categorized (typically this module's / template's testcases page(s))
 if uncategorized\_namespaces[this\_page.nsText] then
 return *; -- this page not to be categorized so return empty string*
 end
 for \_,v in ipairs (uncategorized\_subpages) do -- cycle through page name patterns
 if this\_page.text:match (v) then -- test page name against each pattern
 return *; -- this subpage type not to be categorized so return empty string*
 end
 end
 end

 local out = {};
 if tableLength(track) > 0 then
 for key, \_ in pairs(track) do -- loop through table
 table.insert (out, make\_wikilink (key)); -- and convert category names to links
 end
 end
 return table.concat (out); -- concat into one big string; empty string if table is empty

end

--[[--------------------------< inlineError >-----------------------

Critical error. Render output completely in red. Add to tracking category.

This function called as the last thing before abandoning this module

]]

local function inlineError (msg, args)
 track[categories.error] = 1
 return table.concat ({
 'Error in ', -- open the error message span
 config.tname, -- insert the local language template name
 ' template: ',
 substitute (msg, args), -- insert the formatted error message
 '.', -- close the span
 createTracking() -- add the category
 })
end

--[[--------------------------< inlineRed >-----------------------

Render a text fragment in red, such as a warning as part of the final output.
Add tracking category.

 ]]

local function inlineRed(msg, trackmsg)
 if trackmsg == "warning" then
 track[categories.warning] = 1;
 elseif trackmsg == "error" then
 track[categories.error] = 1;
 end

 return '' .. msg .. ''
end

--[[--------------------------< base62 >-----------------------

Convert base-62 to base-10
Credit: https://de.wikipedia.org/wiki/Modul:Expr

]]

local function base62( value )
 local r = 1 -- default return value is input value is malformed

 if value:match ('%W') then -- value must only be in the set [0-9a-zA-Z]
 return; -- nil return when value contains extraneous characters
 end

 local n = #value -- number of characters in value
 local k = 1
 local c
 r = 0
 for i = n, 1, -1 do -- loop through all characters in value from ls digit to ms digit
 c = value:byte( i, i )
 if c >= 48 and c <= 57 then -- character is digit 0-9
 c = c - 48
 elseif c >= 65 and c <= 90 then -- character is ascii a-z
 c = c - 55
 else -- must be ascii A-Z
 c = c - 61
 end
 r = r + c \* k -- accumulate this base62 character's value
 k = k \* 62 -- bump for next
 end -- for i

 return r
end

--[[--------------------------< D E C O D E \_ D A T E >--------------------------------------------------------

Given a date string, return it in iso format along with an indicator of the date's format. Except that month names
must be recognizable as legitimate month names with proper capitalization, and that the date string must match one
of the recognized date formats, no error checking is done here; return nil else

]]

local function decode\_date (date\_str)
 local patterns = {
 ['dmy'] = {'^(%d%d?) +([^%s%d]+) +(%d%d%d%d)$', 'd', 'm', 'y'}, -- %a does not recognize unicode combining characters used by some languages
 ['mdy'] = {'^([^%s%d]+) (%d%d?), +(%d%d%d%d)$', 'm', 'd', 'y'},
 ['ymd'] = {'^(%d%d%d%d) +([^%s%d]+) (%d%d?)$', 'y', 'm', 'd'}, -- not mos compliant at en.wiki but may be acceptible at other wikis
 };

 local t = {};

 if non\_western\_digits then -- this wiki uses non-western digits?
 date\_str = mw.ustring.gsub (date\_str, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 if date\_str:match ('^%d%d%d%d%-%d%d%-%d%d$') then -- already an iso format date, return western digits form
 return date\_str, 'iso';
 end

 for k, v in pairs (patterns) do
 local c1, c2, c3 = mw.ustring.match (date\_str, patterns[k][1]); -- c1 .. c3 are captured but we don't know what they hold

 if c1 then -- set on match
 t = { -- translate unspecified captures to y, m, and d
 [patterns[k][2]] = c1, -- fill the table of captures with the captures
 [patterns[k][3]] = c2, -- take index names from src\_pattern table and assign sequential captures
 [patterns[k][4]] = c3,
 };
 if month\_num[t.m] then -- when month not already a number
 t.m = month\_num[t.m]; -- replace valid month name with a number
 else
 return nil, 'iso'; -- not a valid date form because month not valid
 end

 return mw.ustring.format ('%.4d-%.2d-%.2d', t.y, t.m, t.d), k; -- return date in iso format
 end
 end
 return nil, 'iso'; -- date could not be decoded; return nil and default iso date
end

--[[--------------------------< makeDate >-----------------------

Given year, month, day numbers, (zero-padded or not) return a full date in df format
where df may be one of:
 mdy, dmy, iso, ymd

on entry, year, month, day are presumed to be correct for the date that they represent; all are required

in this module, makeDate() is sometimes given an iso-format date in year:
 makeDate (2018-09-20, nil, nil, df)
this works because table.concat() sees only one table member

]]

local function makeDate (year, month, day, df)
 local format = {
 ['dmy'] = 'j F Y',
 ['mdy'] = 'F j, Y',
 ['ymd'] = 'Y F j',
 ['iso'] = 'Y-m-d',
 };

 local date = table.concat ({year, month, day}, '-'); -- assemble year-initial numeric-format date (zero padding not required here)

 if non\_western\_digits then -- this wiki uses non-western digits?
 date = mw.ustring.gsub (date, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return mw.getContentLanguage():formatDate (format[df], date);
end

--[[--------------------------< I S \_ V A L I D \_ D A T E >----------------------------------------------------

Returns true if date is after 31 December 1899 (why is 1900 the min year? shouldn't the internet's date-of-birth
be min year?), not after today's date, and represents a valid date (29 February 2017 is not a valid date). Applies
Gregorian leapyear rules.

all arguments are required

]]

local function is\_valid\_date (year, month, day)
 local days\_in\_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 local month\_length;
 local y, m, d;
 local today = os.date ('\*t'); -- fetch a table of current date parts

 if not year or  *== year or not month or*  == month or not day or  *== day then*
 return false; -- something missing
 end

 y = tonumber (year);
 m = tonumber (month);
 d = tonumber (day);

 if 1900 > y or today.year < y or 1 > m or 12 < m then -- year and month are within bounds TODO: 1900?
 return false;
 end

 if (2==m) then -- if February
 month\_length = 28; -- then 28 days unless
 if (0==(y%4) and (0~=(y%100) or 0==(y%400))) then -- is a leap year?
 month\_length = 29; -- if leap year then 29 days in February
 end
 else
 month\_length=days\_in\_month[m];
 end

 if 1 > d or month\_length < d then -- day is within bounds
 return false;
 end
 -- here when date parts represent a valid date
 return os.time({['year']=y, ['month']=m, ['day']=d, ['hour']=0}) <= os.time(); -- date at midnight must be less than or equal to current date/time
end

--[[--------------------------< decodeWebciteDate >-----------------------

Given a URI-path to Webcite (eg. /67xHmVFWP) return the encoded date in df format

returns date string in df format - webcite date is a unix timestamp encoded as bae62
or the string 'query'

]]

local function decodeWebciteDate(path, df)

 local dt = {};
 local decode;

 dt = mw.text.split(path, "/")

 -- valid URL formats that are not base62

 -- http://www.webcitation.org/query?id=1138911916587475
 -- http://www.webcitation.org/query?url=http..&date=2012-06-01+21:40:03
 -- http://www.webcitation.org/1138911916587475
 -- http://www.webcitation.org/cache/73e53dd1f16cf8c5da298418d2a6e452870cf50e
 -- http://www.webcitation.org/getfile.php?fileid=1c46e791d68e89e12d0c2532cc3cf629b8bc8c8e

 if dt[2]:find ('query', 1, true) or
 dt[2]:find ('cache', 1, true) or
 dt[2]:find ('getfile', 1, true) or
 tonumber(dt[2]) then
 return 'query';
 end

 decode = base62(dt[2]); -- base62 string -> exponential number
 if not decode then
 return nil; -- nil return when dt[2] contains characters not in %w
 end
 dt = os.date('\*t', string.format("%d", decode):sub(1,10)) -- exponential number -> text -> first 10 characters (a unix timestamp) -> a table of date parts

 decode = makeDate (dt.year, dt.month, dt.day, 'iso'); -- date comparisons are all done in iso format with western digits
 if non\_western\_digits then -- this wiki uses non-western digits?
 decode = mw.ustring.gsub (decode, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 return decode;
end

--[[--------------------------< decodeWaybackDate >-----------------------

Given a URI-path to Wayback (eg. /web/20160901010101/http://example.com )
or Library of Congress Web Archives (eg. /all/20160901010101/http://example.com)
or UK Government Web Archive (eg. /ukgwa/20160901010101/http://example.com or /tna/20160901010101/http://example.com)

return the formatted date eg. "September 1, 2016" in df format
Handle non-digits in snapshot ID such as "re\_" and "-" and "\*"

 valid date string in df format - wayback date is valid (including the text string 'index' when date is '/\*/')
 empty string - wayback date is malformed (less than 8 digits, not a valid date)
 nil - wayback date is '/save/' or otherwise not a number

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeWaybackDate(path, df)

 local msg, snapdate;

 snapdate = path:gsub ('^/web/', *):gsub ('^/all/',* ):gsub ('^/ukgwa/', *):gsub ('^/tna/',* ):gsub ('^/', *); -- remove leading /web/, /all/, /ukgwa/, /tna/, or /*
 snapdate = snapdate:match ('^[^/]+'); -- get timestamp
 if snapdate == "\*" then -- eg. /web/\*/http.., etc.
 return 'index'; -- return indicator that this url has an index date
 end

 snapdate = snapdate:gsub ('%a%a\_%d?$', *):gsub ('%-',* ); -- from date, remove any trailing "re\_", dashes

 msg = *;*
 if snapdate:match ('%\*$') then -- a trailing '\*' causes calendar display at archive .org
 snapdate = snapdate:gsub ('%\*$', *); -- remove so not part of length calc later*
 msg = inlineRed (err\_warn\_msgs.ts\_cal, 'warning'); -- make a message
 end

 if not tonumber(snapdate) then
 return nil, 'ts\_nan'; -- return nil (fatal error flag) and message selector
 end

 local dlen = snapdate:len();
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate, msg; -- return date with message if any
 else
 return snapdate, msg .. inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message(s)
 end
end

--[[--------------------------< decodeArchiveisDate >-----------------------

Given an Archive.is "long link" URI-path (e.g. /2016.08.28-144552/http://example.com)
return the date in df format (e.g. if df = dmy, return 28 August 2016)
Handles "." and "-" in snapshot date, so 2016.08.28-144552 is same as 20160828144552

 valid date string in df format - archive.is date is valid (including the text string 'short link' when url is the short form)
 empty string - wayback date is malformed (not a number, less than 8 digits, not a valid date)
 nil - wayback date is '/save/'

 second return value is an appropriate 'message' may or may not be formatted

]]

local function decodeArchiveisDate(path, df)
 local snapdate

 if path:match ('^/%w+$') then -- short form url path is '/' followed by some number of base 62 digits and nothing else
 return "short link" -- e.g. http://archive.is/hD1qz
 end

 snapdate = mw.text.split (path, '/')[2]:gsub('[%.%-]', *); -- get snapshot date, e.g. 2016.08.28-144552; remove periods and hyphens*

 local dlen = string.len(snapdate)
 if dlen < 8 then -- we need 8 digits TODO: but shouldn't this be testing for 14 digits?
 return *, inlineRed (err\_warn\_msgs.ts\_short, 'error'); -- return empty string and error message*
 end

 local year, month, day = snapdate:match ('(%d%d%d%d)(%d%d)(%d%d)'); -- no need for snapdatelong here

 if not is\_valid\_date (year, month, day) then
 return *, inlineRed (err\_warn\_msgs.ts\_date, 'error'); -- return empty string and error message*
 end

 snapdate = table.concat ({year, month, day}, '-'); -- date comparisons are all done in iso format
 if 14 == dlen then
 return snapdate; -- return date
 else
 return snapdate, inlineRed (err\_warn\_msgs.ts\_len, 'warning'); -- return date with warning message
 end
 end

--[[--------------------------< serviceName >-----------------------

Given a domain extracted by mw.uri.new() (eg. web.archive.org) set tail string and service ID

]]

local function serviceName(host, no\_link)
 local tracking;
 local index;

 host = host:lower():gsub ('^web%.(.+)', '%1'):gsub ('^www%.(.+)', '%1'); -- lowercase, remove web. and www. subdomains

 if services[host] then
 index = host;
 else
 for k, \_ in pairs (services) do
 if host:find ('%f[%a]'..k:gsub ('([%.%-])', '%%%1')) then
 index = k;
 break;
 end
 end
 end

 if index then
 local out = {*}; -- empty string in [1] so that concatenated result has leading single space*
 ulx.url1.service = services[index][4] or 'other';
 tracking = services[index][5] or categories.other;
 -- build tail string
 if false == services[index][1] then -- select prefix
 table.insert (out, prefixes.at);
 elseif true == services[index][1] then
 table.insert (out, prefixes.atthe);
 else
 table.insert (out, services[index][1]);
 end

 table.insert (out, make\_wikilink (services[index][2], services[index][3], no\_link)); -- add article wikilink
 if services[index][6] then -- add tail postfix if it exists
 table.insert (out, services[index][6]);
 end

 ulx.url1.tail = table.concat (out, ' '); -- put it all together; result has leading space character

 else -- here when unknown archive
 ulx.url1.service = 'other';
 tracking = categories.unknown;
 ulx.url1.tail = table.concat ({*, prefixes.at, host, inlineRed (err\_warn\_msgs.unknown\_url, error)}, ' ');*
 end

 track[tracking] = 1
end

--[[--------------------------< parseExtraArgs >-----------------------

Parse numbered arguments starting at 2, such as url2..url10, date2..date10, title2..title10
 For example: Template loop detected: Template:Webarchive
 Three url arguments not in numeric sequence (1..4..7).
 Function only processes arguments numbered 2 or greater (in this case 4 and 7)
 It creates numeric sequenced table entries like:
 urlx.url2.url = <argument value for url4>
 urlx.url3.url = <argument value for url7>
 Returns the number of URL arguments found numbered 2 or greater (in this case returns "2")

 ]]

local function parseExtraArgs(args)

 local i, j, argurl, argurl2, argdate, argtitle

 j = 2
 for i = 2, config.maxurls do
 argurl = "url" .. i
 if args[argurl] then
 argurl2 = "url" .. j
 ulx[argurl2] = {}
 ulx[argurl2]["url"] = args[argurl]
 argdate = "date" .. i
 if args[argdate] then
 ulx[argurl2]["date"] = args[argdate]
 else
 ulx[argurl2]["date"] = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end

 argtitle = "title" .. i
 if args[argtitle] then
 ulx[argurl2]["title"] = args[argtitle]
 else
 ulx[argurl2]["title"] = nil
 end
 j = j + 1
 end
 end

 if j == 2 then
 return 0
 else
 return j - 2
 end
end

--[[--------------------------< comma >-----------------------

Given a date string, return "," if it's MDY

]]

local function comma(date)
 return (date and date:match ('%a+ +%d%d?(,) +%d%d%d%d')) or *;*
end

--[[--------------------------< createRendering >-----------------------

Return a rendering of the data in ulx[][]

]]

local function createRendering()

 local displayfield
 local out = {};

 local index\_date, msg = ulx.url1.date:match ('(index)(.\*)'); -- when ulx.url1.date extract 'index' text and message text (if there is a message)
 ulx.url1.date = ulx.url1.date:gsub ('index.\*', 'index'); -- remove message

 if 'none' == ulx.url1.format then -- For Template:Wayback, Template:Webcite
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx.url1.url); -- add url

 if ulx.url1.title then
 table.insert (out, ' ') -- the required space
 table.insert (out, ulx.url1.title) -- the title
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, ulx.url1.tail); -- tail text
 if ulx.url1.date then
 table.insert (out, ' ('); -- open date text; TODO: why the html entity? replace with regular space?
 table.insert (out, 'index' == ulx.url1.date and s\_text.archive or s\_text.archived); -- add text
 table.insert (out, ' '); -- insert a space
 table.insert (out, ulx.url1.date); -- add date
 table.insert (out, ')'); -- close date text
 end
 else -- no title
 if index\_date then -- when url date is 'index'
 table.insert (out, table.concat ({' ', s\_text.Archive\_index, ']'})); -- add the index link label
 table.insert (out, msg or *); -- add date mismatch message when url date is /\*/ and |date= has valid date*
 else
 table.insert (out, table.concat ({' ', s\_text.Archived, '] '})); -- add link label for url has timestamp date (will include mismatch message if there is one)
 end
 if ulx.url1.date then
 if 'index' ~= ulx.url1.date then
 table.insert (out, ulx.url1.date); -- add date when data is not 'index'
 end
 table.insert (out, comma(ulx.url1.date)); -- add ',' if date format is mdy
 table.insert (out, ulx.url1.tail); -- add tail text
 else -- no date
 table.insert (out, ulx.url1.tail); -- add tail text
 end
 end

 if 0 < ulx.url1.extraurls then -- For multiple archive URLs
 local tot = ulx.url1.extraurls + 1
 table.insert (out, '.') -- terminate first url
 table.insert (out, table.concat ({' ', s\_text.addlarchives, ': '})); -- add header text

 for i=2, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 displayfield = ulx[index]['title'] and 'title' or 'date'; -- choose display text
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add the url
 table.insert (out, ' '); -- the required space
 table.insert (out, ulx[index][displayfield]); -- add the label
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, i==tot and '.' or ', '); -- add terminator
 end
 end
 return table.concat (out); -- make a big string and done

 else -- For Template:Cite archives
 if 'addlarchives' == ulx.url1.format then -- Multiple archive services
 table.insert (out, table.concat ({s\_text.addlarchives, ': '})); -- add header text
 else -- Multiple pages from the same archive
 table.insert (out, table.concat ({s\_text.addlpages, ' '})); -- add header text
 table.insert (out, ulx.url1.date); -- add date to header text
 table.insert (out, ': '); -- close header text
 end

 local tot = ulx.url1.extraurls + 1;
 for i=1, tot do -- loop through the additionals
 local index = table.concat ({'url', i}); -- make an index
 table.insert (out, '['); -- open extlink markup
 table.insert (out, ulx[index]['url']); -- add url
 table.insert (out, ' '); -- add required space

 displayfield = ulx[index]['title'];
 if 'addlarchives' == ulx.url1.format then
 if not displayfield then
 displayfield = ulx[index]['date']
 end
 else -- must be addlpages
 if not displayfield then
 displayfield = table.concat ({s\_text.Page, ' ', i});
 end
 end
 table.insert (out, displayfield); -- add title, date, page label text
 table.insert (out, ']'); -- close extlink markup
 table.insert (out, (i==tot and '.' or ', ')); -- add terminator
 end
 return table.concat (out); -- make a big string and done
 end
end

--[[--------------------------< P A R A M E T E R \_ N A M E \_ X L A T E >--------------------------------------

for internaltionalization, translate local-language parameter names to their English equivalents

TODO: return error message if multiple aliases of the same canonical parameter name are found?

returns two tables:
 new\_args - holds canonical form parameters and their values either from translation or because the parameter was already in canonical form
 origin - maps canonical-form parameter names to their untranslated (local language) form for error messaging in the local language

unrecognized parameters are ignored

]]

local function parameter\_name\_xlate (args, params, enum\_params)
 local name; -- holds modifiable name of the parameter name during evaluation
 local enum; -- for enumerated parameters, holds the enumerator during evaluation
 local found = false; -- flag used to break out of nested for loops
 local new\_args = {}; -- a table that holds canonical and translated parameter k/v pairs
 local origin = {}; -- a table that maps original (local language) parameter names to their canonical name for local language error messaging
 local unnamed\_params; -- set true when unsupported positional parameters are detected

 for k, v in pairs (args) do -- loop through all of the arguments in the args table
 name = k; -- copy of original parameter name

 if 'string' == type (k) then
 if non\_western\_digits then -- true when non-western digits supported at this wiki
 name = mw.ustring.gsub (name, '%d', digits); -- convert this wiki's non-western digits to western digits
 end

 enum = name:match ('%d+$'); -- get parameter enumerator if it exists; nil else

 if not enum then -- no enumerator so looking for non-enumnerated parameters
 -- TODO: insert shortcut here? if params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (params) do -- loop through each parameter the params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 else -- enumerated parameters
 name = name:gsub ('%d$', '#'); -- replace enumeration digits with place holder for table search
 -- TODO: insert shortcut here? if num\_params[name] then name holds the canonical parameter name; no need to search further
 for pname, aliases in pairs (enum\_params) do -- loop through each parameter the num\_params table
 for \_, alias in ipairs (aliases) do -- loop through each alias in the parameter's aliases table
 if name == alias then
 pname = pname:gsub ('#$', enum); -- replace the '#' place holder with the actual enumerator
 new\_args[pname] = v; -- create a new entry in the new\_args table
 origin [pname] = k; -- create an entry to make canonical parameter name to original local language parameter name
 found = true; -- flag so that we can break out of these nested for loops
 break; -- no need to search the rest of the aliases table for name so go on to the next k, v pair
 end
 end

 if found then -- true when we found an alias that matched name
 found = false; -- reset the flag
 break; -- go do next args k/v pair
 end
 end
 end
 else
 unnamed\_params = true; -- flag for unsupported positional parameters
 end
 end -- for k, v
 return new\_args, origin, unnamed\_params;
end

--[[--------------------------< W E B A R C H I V E >----------------------------------------------------------

template entry point

]]

local function webarchive(frame)
 local args = getArgs (frame);

 local data = mw.loadData (table.concat ({ -- make a data module name; sandbox or live
 'Module:Webarchive/data',
 frame:getTitle():find('sandbox', 1, true) and '/sandbox' or  *-- this instance is ./sandbox then append /sandbox*
 }));
 categories = data.categories; -- fill in the forward declarations
 config = data.config;
 if data.digits.enable then
 digits = data.digits; -- for i18n; table of digits in the local wiki's language
 non\_western\_digits = true; -- use\_non\_western\_digits
 end
 err\_warn\_msgs = data.err\_warn\_msgs;
 excepted\_pages = data.excepted\_pages;
 month\_num = data.month\_num; -- for i18n; table of month names in the local wiki's language
 prefixes = data.prefixes;
 services = data.services;
 s\_text = data.s\_text;
 uncategorized\_namespaces = data.uncategorized\_namespaces;
 uncategorized\_subpages = data.uncategorized\_subpages;

 local origin = {}; -- holds a map of English to local language parameter names used in the current template; not currently used
 local unnamed\_params; -- boolean set to true when template call has unnamed parameters
 args, origin, unnamed\_params = parameter\_name\_xlate (args, data.params, data.enum\_params); -- translate parameter names in args to English

 local date, format, msg, udate, uri, url;
 local ldf = 'iso'; -- when there is no |date= parameter, render url dates in iso format

 if args.url and args.url1 then -- URL argument (first)
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.url, origin.url1});
 end

 url = args.url or args.url1;

 if not url then
 return inlineError (data.crit\_err\_msgs.empty);
 end
 -- these iabot bugs perportedly fixed; removing these causes lua script error
--[[ -- at Template:Webarchive/testcases/Production; resolve that before deleting these tests
 if mw.ustring.find( url, "https://web.http", 1, true ) then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot1);
 end
 if url == "https://web.archive.org/http:/" then -- track bug - TODO: IAbot bug; not known if the bug has been fixed; deferred
 track[categories.error] = 1;
 return inlineError (data.crit\_err\_msgs.iabot2);
 end
]]

 if not (url:lower():find ('^http') or url:find ('^//')) then
 return inlineError (data.crit\_err\_msgs.invalid\_url );
 end

 ulx.url1 = {}
 ulx.url1.url = url

 ulx.url1.extraurls = parseExtraArgs(args)

 local good = false;
 good, uri = pcall (mw.uri.new, ulx.url1.url); -- get a table of uri parts from this url; protected mode to prevent lua error when ulx.url1.url is malformed

 if not good or nil == uri.host then -- abandon when ulx.url1.url is malformed
 return inlineError (data.crit\_err\_msgs.invalid\_url);
 end

 serviceName(uri.host, args.nolink)

 if args.date and args.date1 then -- Date argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.date, origin.date1});
 end

 date = args.date or args.date1;
 date = date and date:gsub (' +', ' '); -- replace multiple spaces with a single space

 if date and config.verifydates then
 if '\*' == date then
 date = 'index';
 ldf = 'iso'; -- set to default format
 elseif 'mdy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'mdy'; -- then |date=mdy overrides iso
 elseif 'dmy' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'dmy'; -- then |date=dmy overrides iso
 elseif 'ymd' == date then
 date = nil; -- if date extracted from URL,
 ldf = 'ymd'; -- then |date=ymd overrides iso
 else
 date, ldf = decode\_date (date); -- get an iso format date from date and get date's original format
 end
 end

 if 'wayback' == ulx.url1.service or 'locwebarchives' == ulx.url1.service or 'ukgwa' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeWaybackDate (uri.path); -- get the url date in iso format and format of date in |date=; 'index' when wayback url date is \*
 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 else -- no |date=
 udate, msg = decodeWaybackDate (uri.path);

 if not udate then -- this is the only 'fatal' error return
 return inlineError (data.crit\_err\_msgs[msg]);
 end

 if  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 elseif 'webcite' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate = decodeWebciteDate (uri.path); -- get the url date in iso format
 if 'query' ~= udate then -- skip if query
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'),
 });
 end
 end
 end
 end
 else
 date = decodeWebciteDate( uri.path, "iso" )
 if date == "query" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif not date then -- invalid base62 string
 date = inlineRed (err\_warn\_msgs.date1, 'error');
 end
 end

 elseif 'archiveis' == ulx.url1.service then
 if date then
 if config.verifydates then
 if ldf then
 udate, msg = decodeArchiveisDate (uri.path) -- get the url date in iso format
 if 'short link' ~= udate then -- skip if short link
 if udate ~= date then -- date comparison using iso format dates
 date = udate;
 msg = table.concat ({
 inlineRed (err\_warn\_msgs.mismatch, 'warning'), -- add warning message
 msg, -- add message if there is one
 });
 end
 end
 end
 end
 else -- no |date=
 udate, msg = decodeArchiveisDate( uri.path, "iso" )
 if udate == "short link" then
 date = nil; -- unset
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 elseif  *== udate then*
 date = nil; -- unset
 else
 date = udate;
 end
 end

 else -- some other service
 if not date then
 msg = inlineRed (err\_warn\_msgs.date\_miss, 'warning');
 end
 end

 if 'index' == date then
 ulx.url1.date = date .. (msg or *); -- create index + message (if there is one)*
 elseif date then
 ulx.url1.date = makeDate (date, nil, nil, ldf) .. (msg or *); -- create a date in the wiki's local language + message (if there is one)*
 else
 ulx.url1.date = msg;
 end

 format = args.format; -- Format argument

 if not format then
 format = "none"
 else
 for k, v in pairs (data.format\_vals) do -- |format= accepts two specific values loop through a table of those values
 local found; -- declare a nil flag
 for \_, p in ipairs (v) do -- loop through local language variants
 if format == p then -- when |format= value matches
 format = k; -- use name from table key
 found = true; -- declare found so that we can break out of outer for loop
 break; -- break out of inner for loop
 end
 end

 if found then
 break;
 end
 end

 if format == "addlpages" then
 if not ulx.url1.date then
 format = "none"
 end
 elseif format == "addlarchives" then
 format = "addlarchives"
 else
 format = "none"
 end
 end
 ulx.url1.format = format

 if args.title and args.title1 then -- Title argument
 return inlineError (data.crit\_err\_msgs.conflicting, {origin.title, origin.title1});
 end

 ulx.url1.title = args.title or args.title1;

 local rend = createRendering()
 if not rend then
 return inlineError (data.crit\_err\_msgs.unknown);
 end

 return rend .. ((unnamed\_params and inlineRed (err\_warn\_msgs.unnamed\_params, 'warning')) or *) .. createTracking();*

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
]]

return {webarchive = webarchive}; foodworldnews.com
119. ↑ Rocha, Veronica; Parker, Ryan (12 February 2015). "Fire at Pierce Brosnan's Malibu home causes $1 million in damage". *Los Angeles Times*.
120. ↑ Doell, Zach (28 August 2015). "Pierce Brosnan Reveals He Lost his Aston Martin in a House Fire". Yahoo! News.
121. ↑ "Bond star Brosnan made honorary OBE", *BBC NEWS*, 14 July 2003.
122. ↑ "Pierce Brosnan and Eddie Jordan awarded Honorary Doctorates from Dublin Institute of Technology", Dublin Institute of Technology, 23 June 2003.
123. ↑ Template:Cite press release
124. ↑ Nathan, Ian. "Public Access: Pierce Brosnan". *Empire* (135): 10.
125. ↑ McGoldrick, Debbie (31 March 2011). Pierce Brosnan talks about his deep Catholic faith.
126. ↑ Trailer and Poster for Survivor, Starring Pierce Brosnan and Milla Jovovich. ComingSoon (2 April 2015).
127. ↑ Vlessing, Etan (11 September 2020). Pierce Brosnan, Lilly Singh, Brendan Gleeson to Voice 'Riverdance: The Animated Adventure' Feature (Exclusive). *The Hollywood Reporter*.
128. ↑ The "title character" of *Remington Steele* actually did not exist, and the man who assumed the identity never learned his own real name.

## External links

* Official website
* Pierce Brosnan at Wikipedia

* Pierce Brosnan at James Bond Wiki
* Pierce Brosnan at the Internet Movie Database

**Soundtracks:** *Moonraker*