The Silence of the Lambs

***The Silence of the Lambs*** is a 1991 American psychological horror film directed by Jonathan Demme and written by Ted Tally, adapted from Thomas Harris' 1988 novel. It stars Jodie Foster as Clarice Starling, a young FBI trainee who is hunting a serial killer, "Buffalo Bill" (Ted Levine), who skins his female victims. To catch him, she seeks the advice of the imprisoned Dr. Hannibal Lecter (Anthony Hopkins), a brilliant psychiatrist and cannibalistic serial killer. The film also features performances from Scott Glenn, Anthony Heald and Kasi Lemmons.

*The Silence of the Lambs* was released on February 14, 1991 and grossed $272.7 million worldwide on a $19 million budget, becoming the fifth-highest-grossing film of 1991 worldwide. It premiered at the 41st Berlin International Film Festival, where it competed for the Golden Bear, while Demme received the Silver Bear for Best Director. It became the third film (the other two being 1934's *It Happened One Night* and 1975's *One Flew Over the Cuckoo's Nest*) to win Academy Awards in all the top five categories: Best Picture, Best Director, Best Actor, Best Actress, and Best Adapted Screenplay. It is also the only Best Picture winner widely considered a horror film, and one of only six horror films to have been nominated in the category with *The Exorcist* (1973), *Jaws* (1975), *The Sixth Sense* (1999), *Black Swan* (2010), and *Get Out* (2017).

*The Silence of the Lambs* is regularly cited by critics, film directors and audiences as one of the greatest and most influential films. In 2018, *Empire* ranked it 48th on their list of the 500 greatest movies of all time. The American Film Institute ranked it the fifth-greatest and most influential thriller film while Starling and Lecter were ranked among the greatest film heroines and villains. The film is considered "culturally, historically or aesthetically" significant by the U.S. Library of Congress and was selected for preservation in the National Film Registry in 2011. A sequel, *Hannibal,* was released in 2001, followed by the prequels *Red Dragon* (2002) and *Hannibal Rising* (2007).

## Plot

In 1990, Clarice Starling is pulled from her FBI training at the Quantico, Virginia FBI Academy by Jack Crawford of the Bureau's Behavioral Science Unit. He assigns her to interview Hannibal Lecter, a former psychiatrist and incarcerated cannibalistic serial killer. Crawford believes Lecter's insight could prove useful in the pursuit of a psychopath serial killer nicknamed "Buffalo Bill", who kills young women and removes their skin from their bodies.

At the Baltimore State Hospital for the Criminally Insane, Dr. Frederick Chilton makes a crude pass at Starling before he escorts her to Lecter's cell. Although initially pleasant and courteous, Lecter grows impatient with Starling's interviewing and rebuffs her. As she is leaving, a prisoner named Miggs flicks s---- at her. Lecter, who considers this an "unspeakably ugly" act, calls Starling back and tells her to seek out his old patient. This leads her to a storage facility, where she discovers a jar containing a man's severed head. She returns to Lecter, who says the man is linked to Buffalo Bill. He offers to profile Buffalo Bill on condition he be transferred away from Chilton, whom he detests. Another Buffalo Bill victim is found with a death's head moth lodged in her throat.

Buffalo Bill abducts Catherine Martin, a senator's daughter. Crawford authorizes Starling to offer Lecter a fake deal, promising a prison transfer if he provides information that helps them capture Buffalo Bill and rescue Catherine. Instead, Lecter demands a *quid pro quo* from Starling, offering clues about Buffalo Bill in exchange for personal information. Starling tells Lecter about her father's murder when she was ten years old. Chilton secretly records the conversation and reveals Starling's deceit before offering Lecter a different deal. Lecter agrees and is flown to Memphis, where he meets and torments Senator Martin, then gives her misleading information on Buffalo Bill, including that his name is "Louis Friend".

Starling figures out that "Louis Friend" is an anagram of "iron sulfide"—fool's gold. She visits Lecter, who is now imprisoned in a cell in a Tennessee courthouse, and requests the truth. Lecter says all the information she needs is contained in the Buffalo Bill case file, then insists on continuing their *quid pro quo*. She recounts a traumatic childhood incident of hearing spring lambs being slaughtered on a relative's Montana farm. Lecter speculates that Starling hopes that saving Catherine will end the recurring nightmares she has of lambs screaming. Lecter returns the Buffalo Bill case files to Starling as Chilton arrives and has the police escort her from the building. Later that evening, Lecter kills his guards, escapes from his cell, and disappears.

Starling analyzes Lecter's file annotations and figures out that Buffalo Bill knew his first victim, Frederika Bimmel. Starling travels to her Ohio hometown and discovers both she and Buffalo Bill were tailors. At Frederika's home, she notices unfinished dresses and dress patterns identical to the patches of skin removed from the victims. She phones Crawford and says Buffalo Bill is making a "suit" with human skin. Crawford is already en route to make an arrest, having cross-referenced Lecter's notes with hospital archives and finding a man named Jame Gumb. Gumb smuggled death's head moths into the U.S. and was refused a sex-change operation, mistakenly believing he was transsexual. Starling continues interviewing Frederika's friends while Crawford and an FBI HRT storm Gumb's address in Illinois, finding the house empty. Meanwhile, Starling goes to interview another person who knew Frederika. At the house, she meets "Jack Gordon", but realizes he is Gumb after spotting a death's head moth flying loose. She pursues him into a cavernous basement and finds Catherine trapped in a dry well. In a dark room, Gumb stalks Starling with night-vision goggles, but reveals himself by cocking his revolver. Starling reacts quickly and shoots Gumb dead.

At the FBI Academy graduation party, Starling receives a phone call from Lecter, who is at a Bimini airport. He assures her that he has no intention of pursuing her and requests that she return the favor, which she says she cannot. Lecter subsequently hangs up the phone because he is "having an old friend for dinner." He trails a newly arrived Chilton into the crowd.

## Cast

Template:Div col

* Jodie Foster as Clarice Starling
	+ Masha Skorobogatov as young Clarice
* Anthony Hopkins as Dr. Hannibal Lecter
* Scott Glenn as Jack Crawford
* Ted Levine as Jame "Buffalo Bill" Gumb
* Anthony Heald as Dr. Frederick Chilton
* Brooke Smith as Catherine Martin
* Diane Baker as U.S. Senator Ruth Martin
* Kasi Lemmons as Ardelia Mapp
* Frankie Faison as Barney Matthews
* Tracey Walter as Lamar
* Charles Napier as Lt. Boyle
* Danny Darst as Sgt. Tate
* Alex Coleman as Sgt. Jim Pembry
* Dan Butler as Roden
* Paul Lazar as Pilcher
* Ron Vawter as Paul Krendler
* Roger Corman as FBI Director Hayden Burke
* Chris Isaak as SWAT Commander
* Harry Northup as Mr. Bimmel
* Brent Hinkley as Officer Murray
* Cynthia Ettinger as Officer Jacobs

Template:Div col end

## Production

### Development

*The Silence of the Lambs* is based on the 1988 novel by Thomas Harris. It was the second film to feature the character Hannibal Lecter; the first, *Manhunter* (1986), was also adapted from a Harris novel. Prior to the release of the *Silence of the Lambs* novel, Orion Pictures partnered with Gene Hackman to adapt it for film. With Hackman set to direct and possibly star in as FBI agent Jack Crawford, negotiations were made to split the $500,000 cost of rights between Hackman and the studio. The producers also had to acquire the rights to the Lecter character, which were owned by *Manhunter* producer Dino De Laurentiis. Owing to the financial failure of *Manhunter*, De Laurentiis lent the rights to Orion for free.

In November 1987, Ted Tally was brought on to write the adaptation; Tally had crossed paths with Harris many times, with his interest in adapting *The Silence of the Lambs* originating from receiving an advance copy of the book from Harris. When Tally was about halfway through with the first draft, Hackman withdrew from the project and financing fell through. However, Orion co-founder Mike Medavoy encouraged Tally to keep writing as the studio took care of financing and searched for a replacement director. Orion sought director Jonathan Demme to helm the project. With the screenplay not yet completed, Demme signed on after reading the novel. From there, the project developed quickly; Tally said: "[Demme] read my first draft not long after it was finished, and we met, then I was just startled by the speed of things. We met in May 1989 and were shooting in November. I don't remember any big revisions."

### Casting

Jodie Foster was interested in playing FBI agent Clarice Starling immediately after reading the novel. However, in spite of the fact that Foster had just won an Academy Award for her performance in *The Accused* (1988), Demme was not convinced that she was right for the role. Having just collaborated on *Married to the Mob* (1988), Demme's first choice for the role of Starling was Michelle Pfeiffer, who turned it down, later saying, "It was a difficult decision, but I got nervous about the subject matter." He then approached Meg Ryan, who turned it down as well for its gruesome themes, and then Laura Dern, of whom the studio was skeptical as not being a bankable choice. As a result, Foster was awarded the role due to her passion towards the character.

For the role of Lecter, Demme originally approached Sean Connery. After Connery turned it down, Anthony Hopkins was offered the role based on his performance in *The Elephant Man* (1980). When Hopkins's agent told him a script was en route titled *The Silence of the Lambs*, Hopkins responded, "Is it a children's story?" Hopkins called his agent back after reading the first 10 pages, saying, "This is the best part I've ever read," then had dinner with Demme and accepted the role.

Other actors considered for the role included Al Pacino, Robert De Niro, Dustin Hoffman, Derek Jacobi and Daniel Day-Lewis. The mask Hopkins wore became an iconic symbol of the film. It was created by Ed Cubberly, of Frenchtown, New Jersey, who had made masks for NHL goalkeepers.

Hopkins created his interpretation of Lecter based upon the voice of the HAL 9000 as voiced by Douglas Rain in *2001: A Space Odyssey* as well as the vocal cadences of both actor Katharine Hepburn and writer Truman Capote. He was initially scared to talk to Foster, knowing that she had just won an Oscar.

Gene Hackman was cast to play Jack Crawford, the Agent-in-Charge of the Behavioral Science Unit of the FBI in Quantico, Virginia, but he found the script too violent. Scott Glenn was then cast in the role. In preparation for the role, Glenn met with John E. Douglas. Douglas gave Glenn a tour of the Quantico facility and also played for him an audio tape containing various recordings that serial killers Lawrence Bittaker and Roy Norris had made of themselves raping and torturing a 16-year-old girl. According to Douglas, Glenn wept as he listened to the recordings, and even changed his liberal stance on the death penalty.

### Filming

Principal photography on *The Silence of the Lambs* began on November 15, 1989, and wrapped on March 1, 1990. Filming primarily took place in and around Pittsburgh, Pennsylvania, with some scenes shot in nearby northern West Virginia. The Victorian home in Perryopolis, Pennsylvania used as Buffalo Bill's home in the film went up for sale in August 2015 for $300,000. The home sat on the market for nearly a year, before finally selling for $195,000. The exterior of the Western Center near Canonsburg, Pennsylvania served as the setting for Baltimore State Hospital for the Criminally Insane. In what was a rare act of cooperation at the time, the FBI allowed scenes to be filmed at the FBI Academy in Quantico; some FBI staff members even acted in bit parts.

The design for the basement and pit used by Buffalo Bill was inspired by the real-life kidnappings and murders of Gary M. Heidnik.

### Music

Template:Album ratings

The musical score for *The Silence of the Lambs* was composed by Howard Shore, who would also go on to collaborate with Demme on *Philadelphia*. Recorded in Munich during the latter half of the summer of 1990, the score was performed by the Munich Symphony Orchestra. "I tried to write in a way that goes right into the fabric of the movie," explained Shore on his approach. "I tried to make the music just fit in. When you watch the movie you are not aware of the music. You get your feelings from all elements simultaneously, lighting, cinematography, costumes, acting, music. Jonathan Demme was very specific about the music." The music editor was Suzana Peric. A soundtrack album was released by MCA Records on February 5, 1991. Music from the film was later used in the trailers for its 2001 sequel, *Hannibal*.

In addition to Shore's score, recordings of popular music are used prominently in the film. This includes British post-punk music, such as the song "Hip Priest" by The Fall which can be heard playing during the climactic scene in which Starling enters Buffalo Bill's house.

## Release

### Box office

*The Silence of the Lambs* was released on February 14, 1991, grossing almost $14 million from 1,497 theaters over the 4-day Presidents' Day weekend, placing at number one at the US box office. It remained at number one for five weeks.

The film opened at the Odeon Leicester Square in London in June 1991 and grossed £290,936 in its opening week, which distributor Rank claimed was a world record opening week from one theatre. The following week it expanded to 281 screens and grossed £4,260,472 for the week, a UK record.

The film grossed $131 million in the United States and Canada with a total worldwide gross of $273 million. It was the fifth-highest-grossing film of 1991 worldwide.

### Home media

The film was released on VHS in October 1991 by Orion Home Video. It was the most rented video in the United States upon release. It was released on DVD on March 6, 2001 by MGM Home Entertainment. The Criterion Collection, which had released the film on LaserDisc in 1994, released a DVD special edition in 1998, and later a Blu-Ray edition in 2018.

## Reception

### Critical response

Template:Multiple image

*The Silence of the Lambs* was a sleeper hit that gradually gained widespread success and critical acclaim. Foster, Hopkins, and Levine garnered much acclaim for their performances. Review aggregator Rotten Tomatoes reports that 96% of 104 film critics have given the film a positive review, with an average rating of 8.90/10. The website's critical consensus reads: "Director Jonathan Demme's smart, taut thriller teeters on the edge between psychological study and all-out horror, and benefits greatly from stellar performances by Anthony Hopkins and Jodie Foster." Metacritic, another review aggregator, assigned the film a weighted average score of 85 out of 100, based on 19 reviews from mainstream critics, indicating "universal acclaim". Audiences polled by CinemaScore gave the film an average grade of "A–" on an A+ to F scale.

Roger Ebert of the *Chicago Sun-Times*, specifically mentioned the "terrifying qualities" of Hannibal Lecter. Ebert later added the film to his list of *The Great Movies*, recognizing the film as a "horror masterpiece" alongside such classics as *Nosferatu*, *Psycho*, and *Halloween*. However, the film is also notable for being one of two multi-Academy Award winners (the other being *Unforgiven*) to get a bad review from Ebert's colleague, Gene Siskel. Writing for *Chicago Tribune*, Siskel said, "Foster's character, who is appealing, is dwarfed by the monsters she is after. I'd rather see her work on another case."

### Criticism from the LGBT community

*The Silence of the Lambs* was criticized by members of the LGBT community for its portrayal of Buffalo Bill as bisexual and transgender, although Bill's sexual orientation is not stated and Lecter expressly states Bill is "not really transsexual".
Demme responded that Buffalo Bill "wasn't a gay character. He was a tormented man who hated himself and wished he was a woman because that would have made him as far away from himself as he possibly could be." Demme added that he "came to realize that there is a tremendous absence of positive gay characters in movies". Much of the criticism was made towards Foster, who critics alleged was a lesbian.Template:Full citation needed

In a 1992 interview with *Playboy* magazine, the feminist and women's rights advocate Betty Friedan stated: "I thought it was absolutely outrageous that *The Silence of the Lambs* won fourTemplate:Sic Oscars. […] I'm not saying that the movie shouldn't have been shown. I'm not denying the movie was an artistic triumph, but it was about the evisceration, the skinning alive of women. That is what I find offensive. Not the *Playboy* centerfold."

### Accolades

 **Best Actress**, Jodie Foster

The film won the Big Five Academy Awards: Best Picture, Best Director (Demme), Best Actor (Hopkins), Best Actress (Foster), and Best Adapted Screenplay (Ted Tally), making it only the third film in history to accomplish that feat. It was also nominated for Best Sound (Tom Fleischman and Christopher Newman) and Best Film Editing, but lost to *Terminator 2: Judgment Day* and *JFK*, respectively.

Other awards include Best Film by the National Board of Review of Motion Pictures, CHI Awards and PEO Awards. Demme won the Silver Bear for Best Director at the 41st Berlin International Film Festival and was nominated for the Golden Globe Award for Best Director. The film was nominated for the Grand Prix of the Belgian Film Critics Association. It was also nominated for the British Academy Film Award for Best Film. Screenwriter Ted Tally received an Edgar Award for Best Motion Picture Screenplay. The film was awarded Best Horror Film of the Year during the 2nd Horror Hall of Fame telecast, with Vincent Price presenting the award to the film's executive producer Gary Goetzman.

In 1998, the film was listed as one of the 100 greatest films in the past 100 years by the American Film Institute. In 2006, at the Key Art Awards, the original poster for *The Silence of the Lambs* was named best film poster "of the past 35 years". *The Silence of the Lambs* placed seventh on Bravo's *The 100 Scariest Movie Moments* for Lecter's escape scene. The American Film Institute named Hannibal Lecter (as portrayed by Hopkins) the number one film villain of all time and Clarice Starling (as portrayed by Foster) the sixth-greatest film hero of all time. In 2011, ABC aired a prime-time special, *Best in Film: The Greatest Movies of Our Time*, that counted down the best films chosen by fans based on results of a poll conducted by ABC and *People* magazine. *The Silence of the Lambs* was selected as the best suspense/thriller and Dr. Hannibal Lecter was selected as the fourth-greatest film character.

The film and its characters have appeared in the following AFI "100 Years" lists:

* AFI's 100 Years...100 Movies – #65
* AFI's 100 Years...100 Thrills – #5
* AFI's 100 Years...100 Heroes & Villains:
	+ Clarice Starling – #6 Hero
	+ Hannibal Lecter – #1 Villain
	+ Buffalo Bill - Nominated Villain
* AFI's 100 Years...100 Movie Quotes:
	+ "A census taker once tried to test me. I ate his liver with some fava beans and a nice Chianti." – #21
* AFI's 100 Years...100 Movies (10th Anniversary Edition) – #74

In 2015, *Entertainment Weekly*'s 25th anniversary year, it included *The Silence of the Lambs* in its list of the 25 best movies made since the magazine's beginning.

## Legacy

According to *The Guardian*, before *The Silence of the Lambs*, serial killers in films had been "claw-handed bogeymen with melty faces and rubber masks. By contrast, Lecter was highly intelligent with impeccable manners," and played by an actor with "impeccable credentials".

When *The Silence of the Lambs* was re-released in the UK in 2017, the British Board of Film Classification reclassified it from an 18 to a 15 certificate. *Silence of the Lambs* producer Ed Saxon said audiences had become desensitized and that the film had become less shocking. However, the BBFC's Craig Lapper felt that audiences had instead become used to procedural crime dramas with serial killers as dramatic tropes, and suggested that *The Silence of the Lambs* had created interest in these themes.

## See also

* *Clarice*, sequel TV series
* List of films based on crime books
* *Silence! The Musical*, an unauthorized parody musical adaptation of the film
* List of Academy Award records

## References

1. ↑ The Silence of the Lambs. British Board of Film Classification.
2. ↑ 2.0 2.1 The Silence of the Lambs (1991).
3. ↑ The 500 Greatest Movies of All Time – #400–301.
4. ↑ 4.0 4.1 Bernstein, Jill (February 8, 2001). How Ridley Scott's Hannibal came to be made. *The Guardian*. Guardian Media Group.
5. ↑ Tiech, John (June 20, 2012). *Pittsburgh Film History: On Set in the Steel City*. Stroud, Gloucestershire: The History Press, page 63. ISBN 978-1-60949-709-5.
6. ↑ Medavoy, Mike (June 25, 2013). *You're Only as Good as Your Next One: 100 Great Films, 100 Good Films, and 100 for Which I Should Be Shot*, Reprint, New York City: Atria Books, page 183. ISBN 9781439118139.
7. ↑ Konow, David (October 2, 2012). *Reel Terror: The Scary, Bloody, Gory, Hundred-Year History of Classic Horror Films*. London, England: St. Martin's Press, page 459. ISBN 978-0-312-66883-9.
8. ↑ Engel, Joel (February 12, 2013). *Screenwriters on Screen-Writing: The Best in the Business Discuss Their Craft*, Kindle, New York City: Hyperion Books. ISBN 9781401305574.
9. ↑ Kapsis, Robert E. (December 19, 2008). *Jonathan Demme: Interviews (Conversations With Filmmakers Series)*. Jackson, Mississippi: University Press of Mississippi, page 71–75. ISBN 978-1-60473-118-7.
10. ↑ Scott, Kevin Conroy (April 28, 2006). *Screenwriters' Masterclass: Screenwriters Discuss their Greatest Films*. New York City: HarperCollins. ISBN 9780571261581.
11. ↑ The Total Film Interview – Jodie Foster. *Total Film*. Future Publishing (December 1, 2005).
12. ↑ Davis, Cindy (February 27, 2012). Mindhole Blowers: 20 Facts About The Silence of the Lambs That Might Make You Crave a Nice Chianti. *Pajiba*.
13. ↑ The Barbara Walters Special, American Broadcast Company, 1992
14. ↑ Davis, Cindy (April 2, 2015). 'Silence of the Lambs' director admits he didn't want to cast Jodie Foster. *NME*. TI Media.
15. ↑ Maslin, Janet (February 19, 1991). How to Film a Gory Story With Restraint. *The New York Times*.
16. ↑ Odam, Matthew (October 26, 2013). AFF panel wrap: Jonathan Demme in conversation with Paul Thomas Anderson. *Austin American-Statesman*. Cox Media Group.
17. ↑ 17.0 17.1 "Jodie Foster and Anthony Hopkins Reunite for 'Silence of the Lambs' 30th Anniversary", *Variety*, Variety Media, January 20, 2021.
18. ↑ 18.0 18.1 18.2 18.3 White, Peter (November 6, 2017). Jodie Foster Lifts The Lid On 'The Silence of the Lambs' At BFI – Q&A. Penske Media Corporation.
19. ↑ 19.0 19.1 Lang, Brent (September 11, 2013). Derek Jacobi, Daniel Day-Lewis Almost Played Hannibal Lecter in 'Silence of the Lambs'. TheWrap.
20. ↑ Filmtracks: The Silence of the Lambs (Howard Shore). *Filmtracks.com* (November 24, 2009).
21. ↑ 21.0 21.1 The Silence of the Lambs.
22. ↑ Template:Cite magazine
23. ↑ Template:Cite magazine
24. ↑ 1991 Yearly Box Office Results.
25. ↑ The Silence of the Lambs.
26. ↑ The Silence of the Lambs :: Criterion Forum.
27. ↑ Collins, Jim (1992). *Film Theory Goes to the Movies*. London, England: Routledge, page 35. ISBN 978-0-415-90576-3.
28. ↑ *The Silence of the Lambs* (1991). *Rotten Tomatoes*. Fandango Media.
29. ↑ *The Silence of the Lambs* Reviews. *Metacritic*. CBS Interactive.
30. ↑ CinemaScore. *cinemascore.com*.
31. ↑ Ebert, Roger (February 14, 1991). The Silence of the Lambs Movie Review (1991). *Chicago Sun-Times*. Sun-Times Media Group.
32. ↑ Ebert, Roger (February 18, 2001). The Great Movies: The Silence of the Lambs (1991). *Chicago Sun-Times*. Sun-Times Media Group.
33. ↑ Siskel, Gene (February 15, 1991). Jodie Foster Appealing, But Not 'Silence of the Lambs'. *Chicago Tribune*. Tribune Company.
34. ↑ "Dr. Lecter, My Name Is Clarice Starling". *Vanity Fair* (23 February 2021).
35. ↑ Schmalz, Jeffrey. "From Visions of Paradise to H--- on Earth", *The New York Times*, February 28, 1993.
36. ↑ Hollinger 2012, pp. 46–47
37. ↑ "Interview of Friedan" by David Sheff, *Playboy*, September 1992, pp. 51–54, 56, 58, 60, 62, 149; reprinted in full in *Interviews with Betty Friedan*, Janann Sherman, ed. Univ. Press of Mississippi, 2002, Template:ISBN.
38. ↑ Pristin, Terry (March 31, 1992). 'Silence of the Lambs' Sweeps 5 Major Oscars. *Los Angeles Times*.
39. ↑ The 64th Academy Awards (1992) Nominees and Winners. *oscars.org*.
40. ↑ Berlinale: 1991 Prize Winners. *berlinale.de*.
41. ↑ 2nd Annual Horror Hall of Fame Telecast, 1991
42. ↑ AFI's 100 Years... 100 Movies Accessed March 14, 2007. --[[ ----------------------------------

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};
43. ↑ "'Sin City' place to be at Key Art Awards" --[[ ----------------------------------

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 Hollywood Reporter*. October 9, 2006. Retrieved October 7, 2007
44. ↑ 44.0 44.1 AFI 100 Years...100 Heroes & Villains Accessed March 14, 2007. --[[ ----------------------------------

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};
45. ↑ EW's 25 Best Movies in 25 Years. Entertainment Weekly.
46. ↑ 1992 Academy Awards.
47. ↑ 47.0 47.1 47.2 Clarke, Cath. "An old friend for dinner ... why we're not scared of Hannibal Lecter any more", *The Guardian*, 2017-10-13. (in en-GB)

## External links

Template:Wikiquote

* *The Silence of the Lambs* at the Internet Movie Database
* Template:Tcmdb title
* Template:Mojo title
* *The Silence of the Lambs* at the American Film Institute Catalog**Script error: No such module "EditAtWikidata".**
* Template:Rotten-tomatoes
* Template:Metacritic film
* *The Silence of the Lambs* an essay by Amy Taubin at the Criterion Collection

Template:Hannibal
Template:Jonathan Demme
Template:Navboxes

Template:Portal bar

Template:Authority control