Metro Goldwyn Mayer Wiki

The World Is Not Enough is a 1999 spy film, the nineteenth in the James Bond series produced by Eon Productions and the third to star Pierce Brosnan as the fictional MI6 agent James Bond. The film was directed by Michael Apted, with the original story and screenplay written by Neal Purvis, Robert Wade and Bruce Feirstein.[3] It was produced by Michael G. Wilson and Barbara Broccoli. The title is taken from the translation of the motto on the fictional Bond family coat of arms, seen first in On Her Majesty's Secret Service.

The film's plot revolves around the assassination of billionaire Sir Robert King by the terrorist Renard, and Bond's subsequent assignment to protect King's daughter Elektra, who had previously been held for ransom by Renard. During his assignment, Bond unravels a scheme to increase petroleum prices by triggering a nuclear meltdown in the waters of Istanbul.

Filming locations included Spain, France, Azerbaijan, Turkey, and the UK, with interiors shot at Pinewood Studios. The film received mixed reviews, with the plot and Denise Richards' casting being frequently targeted for criticism; in contrast, the film also received praise for the performances of Brosnan and Sophie Marceau, as well as its emotional weight and focus on characters in comparison to previous entries. The World Is Not Enough earned $361.8 million worldwide. It was also the first Eon-produced Bond film to be officially released under the Metro-Goldwyn-Mayer label instead of United Artists, the original owner and distributor of the franchise.

Plot[]

In Bilbao, MI6 agent James Bond meets a Swiss banker to retrieve money for Sir Robert King, a British oil tycoon and friend of M. Bond interrogates the banker to attempt to identify the assassin of an MI6 agent, but the banker is killed before revealing this information, and Bond is forced to escape with the money.

At MI6 headquarters in London, the money is revealed to be laced with explosives that kill King. Bond locates the assassin and chases her by boat on the Thames to the Millennium Dome, where she attempts to escape via hot air balloon. Bond offers her protection; she refuses and blows up the balloon at the cost of her life, injuring Bond's shoulder in the process.

Bond traces the recovered money to Renard, a KGB agent-turned-terrorist. Following an earlier attempt on his life by MI6, Renard was left with a bullet embedded in his brain, which makes him immune to pain but will eventually kill him. M assigns Bond to protect King's daughter Elektra, whom Renard had previously abducted and held for ransom. Bond flies to Azerbaijan, where Elektra is overseeing the construction of an oil pipeline. During a tour of the pipeline's proposed route in the mountains, Bond and Elektra are attacked by a hit squad in armed, paraglider-equipped snowmobiles.

Bond visits Valentin Zukovsky at a casino to acquire information about Elektra's attackers. There, Bond grows suspicious as Elektra immediately loses one million dollars, and he later seduces her while learning more about her kidnapping. Bond discovers that Elektra's head of security, Sasha Davidov, is secretly in league with Renard; he kills Davidov and boards a plane bound for a Russian ICBM base in Kazakhstan. Posing as a Russian nuclear scientist, Bond meets American nuclear physicist Dr. Christmas Jones. Renard removes the GPS locator card and weapons-grade plutonium from a nuclear bomb. Before Bond can kill him, Jones blows his cover. Renard steals the bomb and flees, leaving everyone to die. Bond and Jones escape the exploding silo with the locator card.

Back in Azerbaijan, Bond warns M that Elektra may not be as innocent as she appears, and hands her the locator card as proof of the theft. An alarm sounds, revealing that the stolen bomb from Kazakhstan is attached to a pipeline inspection pig heading towards the oil terminal. Bond and Jones enter the pipeline to deactivate the bomb, and Jones discovers that half of the plutonium is missing. They both jump clear of the rig and a large section of the pipe is destroyed. Bond and Jones are presumed killed. Back at the command centre, Elektra reveals that she killed her father as revenge for using her as bait for Renard. She abducts M, whom she resents for having advised her father not to pay the ransom money.

Bond accosts Zukovsky at his caviar factory in the Caspian Sea and they are attacked by Elektra's helicopters. Zukovsky reveals his arrangement with Elektra was to accept a payoff via bets in his casino in exchange for the use of a submarine captained by Zukovsky's nephew, Nikolai. The group goes to Istanbul, where Jones realizes that if Renard were to insert the stolen plutonium into the submarine's nuclear reactor, the resulting nuclear meltdown would destroy Istanbul, thus sabotaging the Russians' oil pipeline in the Bosphorus. Elektra's pipeline is planned to go around Istanbul, with the sabotage dramatically increasing the value of her own oil. Bond gets a signal from the locator card at the Maiden's Tower – just before Zukovsky's henchman Bull blows up the command centre. Zukovsky is knocked unconscious, and Bond and Jones are captured by Elektra's henchmen. Jones is taken aboard the submarine, which was seized by Renard's men. Bond is taken to the tower, where Elektra tortures him with a garrote and reveals that she cut off a portion of her ear to make her kidnapping look more believable. Zukovsky and his men seize the tower, but Zukovsky is shot by Elektra. Before dying, Zukovsky uses his cane gun to free Bond, who frees M and kills Elektra.

Bond dives after the submarine, boards it and frees Jones. The submarine's hull ruptures as it sinks into the Bosphorus. Bond fights Renard and kills him by firing the plutonium rod into his stomach and impaling him. Bond and Jones escape from the submarine, leaving the flooded reactor to detonate underwater. Later, Bond and Jones share a romantic evening in Istanbul and end up in bed together while being monitored by MI6 satellites.

Cast[]

  • Pierce Brosnan as James Bond, MI6 agent, codename 007.
  • Sophie Marceau as Elektra King, an oil heiress who is seemingly being targeted by Renard, the world's most wanted terrorist. M gives Bond the task of protecting her at all costs, although he suspects that there is more to her than meets the eye. Sharon Stone and Vera Farmiga were also considered for the role before Broccoli saw Marceau's performance in Firelight.[4]
  • Robert Carlyle as Victor "Renard" Zokas, a former KGB agent turned high-tech terrorist who previously kidnapped Elektra. After a failed assassination attempt he has a bullet lodged in his brain, rendering him impervious to pain as well as slowly killing off his other senses, and ultimately killing him as well. Before the casting of Carlyle the role was offered to Javier Bardem and Jean Reno.[4]
  • Denise Richards as Dr. Christmas Jones, an American nuclear physicist assisting Bond in his mission.[5] Richards stated that she liked the role because it was "brainy", "athletic", and had "depth of character, in contrast to Bond girls from previous decades".[6] Richards stated that a lot of viewers "made fun of" the character's attire but that "these Bond girls are so outrageous and if I did really look like a scientist, the Bond fans would have been disappointed."[7] Jones was originally written as a French-Polynesian insurance investigator before but the studio insisted on changing her nationality after the casting of Marceau.[4]
  • Robbie Coltrane as Valentin Zukovsky: A former Russian mafia boss and Baku casino owner. Bond initially seeks out Zukovsky for intel on Renard and is subsequently aided by him when Zukovsky's nephew falls into Renard's captivity. Coltrane reprises his role from GoldenEye.
  • Desmond Llewelyn as Q: MI6's "quartermaster" who supplies Bond with multi-purpose vehicles and gadgets useful for the latter's mission. The film would be Llewelyn's final performance as Q. Although the actor was not officially retiring from the role, the Q character was training his eventual replacement in this film. Llewelyn was killed in a car accident shortly after the film's premiere.
  • Maria Grazia Cucinotta as "Cigar Girl": An experienced assassin working for Renard, who appears as a lady who supplies Bond and the banker with cigars during their meeting in Bilbao and who tries to assassinate Bond in London. In the novelisation, the character is given the name Giulietta da Vinci.
  • Samantha Bond as Miss Moneypenny: M's secretary.
  • Michael Kitchen as Bill Tanner: M's Chief of Staff.
  • Colin Salmon as Charles Robinson: M's Deputy Chief of Staff.
  • Serena Scott Thomas as Dr. Molly Warmflash, an MI6 agent and doctor assigned to examine Bond, as well as describing Renard's seeming invincibility due to the terminal bullet in his brain that will kill him when it reaches the center of his brain.
  • Ulrich Thomsen as Sasha Davidov: Elektra King's head of security in Azerbaijan and Renard's secret liaison.
  • Goldie as Bull: Valentin Zukovsky's gold-toothed and gold-haired bodyguard. Although listed as 'Bull' in the credits, Zukovsky refers to him as 'Bullion' in the film.
  • John Seru as Gabor: Elektra King's bodyguard who is seen accompanying King wherever she travels.
  • Claude-Oliver Rudolph as Colonel Akakievich: The leader of the Russian ICBM base in Kazakhstan.
  • Judi Dench as M: The head of MI6.
  • Patrick Malahide portrays Lachaise, the Swiss banker killed early on in the film.

John Cleese portrays R: Q's assistant and appointed successor. Bond humorously refers to him as "R": "If you're Q, does that make him R?"

Production[]

Development[]

File:Yacht The World Is Not Enough, James Bond 007, 2000.jpg

Yacht used in the opening boat chase, on display at boot Düsseldorf in spring 2000

In November 1997, a month prior to the release of Tomorrow Never Dies, Barbara Broccoli watched a news report on Nightline detailing how the world's major oil companies were vying for control of the untapped oil reserves in the Caspian Sea in the wake of the Soviet Union's collapse, and suggested that controlling the only pipeline from the Caspian to the West would be an appropriate motivation for a potential Bond villain.[8] She and Michael G. Wilson hired screenwriters Neal Purvis and Robert Wade to work on the film following their work on Plunkett & Macleane; Purvis and Wade would eventually write or co-write all of the following Bond films up to No Time to Die.[9] Broccoli was especially impressed by the writers' suggestion of a female main villain, stating that "With Elektra, Bond thinks he has found Tracy, but he's really found Blofeld".[8]

Joe Dante, and later Peter Jackson, were initially offered the opportunity to direct the film. Barbara Broccoli enjoyed Jackson's Heavenly Creatures, and a screening of The Frighteners was arranged for her. She disliked the latter film, however, and showed no further interest in Jackson. Jackson, a lifelong Bond fan, remarked that as Eon tended to go for less famous directors, he would likely not get another chance to direct a Bond film after The Lord of the Rings.[10] Barbara Broccoli also was in talks with Alfonso Cuarón to direct, who nearly accepted.[4] Hoping to find a director capable of eliciting strong performances from women, the producers eventually hired Michael Apted, as his work with Sissy Spacek in Coal Miner's Daughter, Sigourney Weaver in Gorillas in the Mist and Jodie Foster in Nell has earned all three actresses Oscar nominations (with Spacek winning). Apted's then-wife Dana Stevens did an uncredited rewrite, primarily to strengthen the female characters' roles, before Bruce Feirstein, who had worked in the previous two films, was hired to work on Bond's role.[8][11]

Initially the film was to be released in 2000, rumoured to be titled Bond 2000. Other rumoured titles included Death Waits for No Man, Fire and Ice, Pressure Point and Dangerously Yours.[12] The title The World Is Not Enough is an English translation of the Latin phrase Non sufficit orbis, which was the motto of the Spanish king Philip II after ascending the Portuguese throne in 1580.[13] As Orbis non-sufficit, was the motto of Sir Thomas Bond. In the novel On Her Majesty's Secret Service and its film adaptation, this is revealed to be the Bond family motto.

The phrase orbis non-sufficit is thought to originate from the Pharsalia by Lucan. It appears twice, both with uncomplimentary associations: the first reference is to a group of villainous mutineers, and the second is to the ambitious Julius Caesar.[14] It was then applied to Alexander the Great by Juvenal in his collection of satirical poems, the Satires: "The world was not big enough for Alexander the Great, but a coffin was".

Filming[]

File:ChathamHDSubmarine0083.JPG

Russian Victor III-class submarine used in filming

The pre-title sequence begins in Bilbao, Spain, featuring the Guggenheim Museum. After the opening scene, the film moves to London, showcasing the SIS Building and the Millennium Dome on the Thames. The sequence lasts for about 14 minutes, making it the longest pre-title sequence in the series until No Time to Die in 2021. Originally, the pre-credits sequence was to have ended with Bond's leap from the window and descent to the ground, finishing as Bond rushes away from the area as police cars approach. Then, after the credits the sequence in MI6 headquarters would have been next, with the boat scenes the next major action sequence. However, the pre-credits scenes were viewed as lacklustre when compared to ones from previous 007 films, so the credits were pushed back to appear after the boat sequence. The Daily Telegraph claimed that the British Government prevented some filming in front of the actual MI6 Headquarters at Vauxhall Cross, citing a security risk. However, a Foreign Office spokesperson rejected the claims and expressed displeasure with the article.[15] Following the title sequence, Eilean Donan Castle in Scotland is used by MI6 as a location headquarters. Other locations include Baku, Azerbaijan, the Azerbaijan Oil Rocks and Istanbul, Turkey, where Maiden's Tower and Küçüksu Palace are shown.[16]

Principal photography began on 17 January 1999, and lasted until June of that year. The studio work for the film was shot as usual in Pinewood Studios, including Albert R. Broccoli's 007 Stage. Bilbao, Spain was used briefly for the exterior of the Swiss bank and flyover-bridge adjacent to the Guggenheim Museum. In London outdoor footage was shot of the SIS Building and Vauxhall Cross with several weeks filming the boat chase on the River Thames eastwards towards the Millennium Dome, Greenwich.[17] The canal footage of the chase where Bond soaks the parking wardens was filmed at Wapping and the boat stunts in Millwall Dock and under Glengall Bridge were filmed at the Isle of Dogs. Chatham Dockyard was also used for part of the boat chase.[18] Stowe School, Buckinghamshire, was used as the site of the King family estate on the banks of Loch Lomond. Filming continued in Scotland at Eilean Donan Castle which was used to depict the exterior of MI6 temporary operations centre "Castle Thane". The skiing chase sequence in the Caucasus was shot on the slopes of Chamonix, France.[16] Filming of the scene was delayed by an avalanche; the crew helped in the rescue operation.[19]

File:BMW Z8 - Flickr - Alexandre Prévot (2) (cropped).jpg

The filming occurred a few months before the BMW Z8 (unit pictured) was released

The interior (and single exterior shot) of L'Or Noir casino in Baku, Azerbaijan, was shot at Halton House, the officers' mess of RAF Halton. RAF Northolt was used to depict the airfield runway in Azerbaijan.[16] Zukovsky's quayside caviar factory was shot entirely at the outdoor water tank at Pinewood. The exterior of Kazakhstan nuclear facility was shot at the Bardenas Reales, in Navarre, Spain, and the exterior of the oil refinery control centre at the Motorola building in Groundwell, Swindon.[20] The exterior of the oil pipeline was filmed in Cwm Dyli, Snowdonia, Wales,[21] while the production teams shot the oil pipeline explosion on Hankley Common, Elstead, Surrey. Istanbul, Turkey, was used in the film, and Elektra King's Baku villa was actually in the city, also using the famous Maiden's Tower which was used as Renard's hideout in Turkey. The underwater submarine scenes were filmed in the Bahamas.[22]

The BMW Z8 driven by Bond in the film was the final part of a three-film product placement deal with BMW (which began with the Z3 in GoldenEye and continued with the 750iL in Tomorrow Never Dies) but, due to filming preceding release of the Z8 by a few months, several working mock-ups and models were manufactured for filming purposes.

Music[]

Main article: The World Is Not Enough (soundtrack)
File:GarbageLiveMay2012.jpg

Rock band Garbage (pictured in 2012) sang the film's title song.

The soundtrack to The World Is Not Enough is the second Bond soundtrack to be composed by David Arnold.[23] Arnold broke tradition by not ending the film with a reprise of the opening theme or, as with the previous three films, a new song. Originally, Arnold intended to use the song "Only Myself to Blame" at the end of the film; however, Apted discarded this and the song was replaced by a remix of the "James Bond Theme".[24] "Only Myself to Blame", written by Arnold and Don Black and sung by Scott Walker, is the 19th and final track on the album and its melody is Elektra King's theme. The theme is heard in "Casino", "Elektra's Theme" and "I Never Miss".[24] Arnold added two new themes to the final score, both of which are reused in the following film, Die Another Day.

The title song, "The World Is Not Enough", was written by David Arnold with Don Black and performed by Garbage. It is the fifth Bond theme co-written by Black, preceded by "Thunderball",[25] "Diamonds Are Forever",[26] "The Man with the Golden Gun",[27] and "Tomorrow Never Dies".[28] IGN chose "The World Is Not Enough" as the ninth-best James Bond theme of all time.[29] In 2012 Grantland ranked the song as the second-best Bond song of all-time, behind only "Goldfinger."[30] The song also appeared in two "best of 1999" polls: #87 in 89X's "Top 89 Songs of 1999"[31] and No. 100 in Q101's "Top 101 of 1999".[32]

Another theme song was made by English post-Britpop band Straw (band), which was also titled The World Is Not Enough but was rejected in favor of Garbage's song.[33] The song is unique as the lyrics contains references to several of the Ian Fleming novels, including references to Goldfinger, You Only Live Twice, On Her Majesty's Secret Service, and Live and Let Die.[34]

Release and reception[]

The World Is Not Enough premiered on 19 November 1999 in the United States and on 26 November 1999 in the United Kingdom. Its world premiere was 8 November 1999 at the Fox Bruin Theater, Los Angeles, USA.[35] At that time MGM signed a marketing partnership with MTV, primarily for American youths, who were assumed to have considered Bond as "an old-fashioned secret service agent". As a result, MTV broadcast more than 100 hours of Bond-related programmes immediately after the film was released, most being presented by Denise Richards.[36]

Box office[]

The film opened at the top of the North American box office with $35.5 million earned during its opening weekend. For five days, it remained in that spot until it was handed to Toy Story 2.[37] Its final worldwide gross was $361.8 million worldwide,[2] with $126 million in the United States alone.[2] It became the highest grossing James Bond film of all time until the release of Die Another Day.[38] The film was also selected for the first round of nominations for the Academy Award for Best Visual Effects and it was failing to get nominated.[39] The film was nominated for a Best Action/Adventure/Thriller Film Saturn Award, Pierce Brosnan won both the Empire Award and the Blockbuster Entertainment Award as Best Actor, and David Arnold won a BMI Film Music Award for his score. The film became the first in the Bond series to win a Golden Raspberry when Denise Richards was chosen as "Worst Supporting Actress" at the 1999 Razzie Awards. Richards and Brosnan were also nominated for "Worst Screen Couple" (lost to Will Smith and Kevin Kline for Wild Wild West).[40] The initial release of the DVD includes the featurette "Secrets of 007", which cuts into "making of" material during the film; the documentary "The Making of The World Is Not Enough"; two commentary tracks—one by director Michael Apted, and the other by production designer Peter Lamont, second unit director Vic Armstrong, and composer David Arnold; a trailer for the PlayStation video game, and the Garbage music video.[41] The Ultimate Edition released in 2006 had as additional extras a 2000 documentary named "Bond Cocktail", a featurette on shooting the Q Boat scenes, Pierce Brosnan in a press conference in Hong Kong, deleted scenes, and a tribute to Desmond Llewelyn.[42]

Critical reception[]

File:Denise richards.jpg

Denise Richards (pictured on the film's premiere) was widely criticized for her performance.

Reception was mixed. Chicago Sun-Times critic Roger Ebert said the film was a "splendid comic thriller, exciting and graceful, endlessly inventive", and gave it three-and-a-half stars out of four.[43] On the other hand, Eleanor Ringel Gillespie of The Atlanta Journal-Constitution disliked the film, calling it "dated and confused".[44] Rotten Tomatoes gave The World Is Not Enough Template:A or an rating, the lowest of the Brosnan Bond films. The site's consensus states: "Plagued by mediocre writing, uneven acting, and a fairly by-the-numbers plot, The World Is Not Enough is partially saved by some entertaining and truly Bond-worthy action sequences."[45] Metacritic gives the film a score of 59 out of 100.[46] Nathan Rabin from The A.V. Club opined that "enough fun moments are scattered throughout to make it a decent Bond entry. But the series still needs a massive shot of fresh ideas if it wishes to become anything more than a nostalgia-fueled commercial sure thing".[47] Antonia Quirke from The Independent said that the film "is certainly less definitively feeble than other recent Bond offerings, with an at least two-dimensional female character in the bold and oval Marceau. But my reaction is much the same as to a new Rolling Stones album: I'm just grateful that it's not embarrassing".[48] Negative criticism was focused on the execution of the plot, and the action scenes were considered excessive.[49]

Richards was widely criticized for not being credible in the role of a nuclear scientist, with Variety calling her "the least plausible nuclear physicist in the history of movies, who makes even the electrochemist Elisabeth Shue played in 1997's The Saint sound like a Nobel laureate"; Nathan Rabin panned her performance and called it "so laughably awful that the film comes to a dead stop whenever she's on screen".[50][51][52][47] She was ranked as one of the worst Bond girls of all time by Entertainment Weekly in 2008.[53]

In contrast, Sophie Marceau was praised for her role as Elektra, with most critics agreeing that she was a better Bond Girl than Denise Richards' Christmas Jones and a better Bond villain than Robert Carlyle's Renard.[54] Peter Bradshaw from The Guardian called her "terrific: sexy, stylish, with a really beautiful face entirely innocent of the cosmetic surgeon's art".[55] The film's plot twist received praise as well, making Elektra the first main female Bond Villain of the series.

Retrospective[]

Pete Debruge of Variety wrote in 2012 that "[The World Is Not Enough] presents a conflicted persona torn between the corny antics of the Roger Moore era and the grim seriousness of where things would eventually go under Daniel Craig’s tenure. It also contains a dose of Timothy Dalton-esque toughness [...] Much of what made Brosnan such a great Bond is thrust into the backseat by lame jokes and a premature attempt to mix up the formula", concluding that it was "nothing but a reversion to the franchise's most adolescent tendencies".[52] Entertainment Weekly picked it as the worst Bond film of all time in 2006, saying it had a plot "so convoluted even Pierce Brosnan has admitted to being mystified".[56] Norman Wilner of MSN chose it as the third worst film, above A View to a Kill and Licence to Kill,[57] while IGN chose it as the fifth worst, both in 2007.[58]

Adaptations[]

Main article: The World Is Not Enough (novel)

Bond novelist Raymond Benson wrote his adaptation of The World Is Not Enough from the film's screenplay. It was Benson's fourth Bond novel and followed the story closely, but with some details changed. For instance, Elektra sings quietly before her death and Bond still carries his Walther PPK instead of the newer P99. The novel also gave the cigar girl/assassin the name Giulietta da Vinci and retained a scene between her and Renard that was cut from the film.

In 2000, the film was adapted by Electronic Arts to create two first-person shooters of the same name for the Nintendo 64 and PlayStation. The Nintendo 64 version was developed by Eurocom and the PlayStation version was developed by Black Ops.[59] Versions of The World Is Not Enough for the PC and the PlayStation 2 were planned for release in 2000, but both were cancelled.[60] These versions would have used the id Tech 3 game engine from Quake III Arena. Although this game marks Pierce Brosnan's fifth appearance in a Bond video game, the game includes only his likeness; the character is voiced by Adam Blackwood.[61]

See also[]

Lua error: bad argument #2 to 'title.new' (unrecognized namespace name 'Portal').

  • Outline of James Bond

References[]

  1. The World is Not Enough. European Audiovisual Observatory.
  2. 2.0 2.1 2.2 2.3 The World Is Not Enough. Box Office Mojo.
  3. Simpson, p 26
  4. 4.0 4.1 4.2 4.3 Field, Matthew (2015). Some kind of hero : 007 : the remarkable story of the James Bond films. ISBN 978-0-7509-6421-0. 
  5. Parker, Barry R. (2005). Death Rays, Jet Packs, Stunts & Supercars: The Fantastic Physics of Film's Most Celebrated Secret Agent. Baltimore: Johns Hopkins University Press. ISBN 978-0-8018-8248-7. 
  6. Thomas, Rebecca. "One girl is not enough", BBC News, 19 November 1999. 
  7. Freydkin, Donna (6 November 2012). Denise Richards: Bond showed me the 'World'. USA Today.
  8. 8.0 8.1 8.2 (2007) James Bond Encyclopedia. London: Dorling Kindersley, page 306–7. ISBN 978-1-4053-3427-3. 
  9. Priggé, Steven. Movie moguls speak: interviews with top film producers --[[ ---------------------------------- 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 "*" returns two values: first value is one of these: 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 returns two values: first value is one of these: 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}; (p.27)
  10. Brian Sibley (2006). Peter Jackson: A Film-maker's Journey. London: Harpercollins, page 397–8. ISBN 0-00-717558-2. 
  11. Dye, Kerry Douglas. "His Word is Bond: An Interview With 007 Screenwriter Bruce Feirstein", 15 November 1999. 
  12. TWINE & The Rumoured Titles (26 June 2001).
  13. . Felipe II. La biografía definitiva (2010) (lám. 48)
  14. (2018-04-01) Juvenal's Tenth Satire (in en). Oxford University Press, page 101. ISBN 978-1-78694-836-6. 
  15. "Bond is backed... by the government", 27 April 1999. 
  16. 16.0 16.1 16.2 Filming locations for The World Is Not Enough (1999).
  17. British Waterways' Film Map: Canals and rivers on screen.
  18. Kent Film Office. Kent Film Office The World Is Not Enough Film Focus.
  19. Ian Nathan. "Unseen Bond", Empire, October 2008, p. 105. 
  20. Motorola building.
  21. Hollywood Films & TV Locations - Visit Wales.
  22. Template:Cite video
  23. [[[:Template:AllMusic]] "The World Is Not Enough" OST review].
  24. 24.0 24.1 David Arnold official website.
  25. Template:Cite video
  26. Template:Cite video
  27. Template:Cite video
  28. Template:Cite video
  29. Spence D. (17 November 2006). Top 10 James Bond Theme Songs.
  30. What’s the Greatest Bond Song of All Time? « --[[ ---------------------------------- 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 "*" returns two values: first value is one of these: 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 returns two values: first value is one of these: 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};
  31. 89X's "Top 89 Songs of 1999. Rocklists.com.
  32. Q101's "Top 101 of 1999. Rocklists.com.
  33. Rogers, Jude. "For your ears only", The Guardian, 31 October 2008. 
  34. Straw - the world is not enough 1999 JAMES BOND Rejected theme.
  35. "Bond 19: More than enough", BBC News, 19 November 1999. 
  36. "Selling a super spy", BBC News, 19 November 1999. 
  37. Lyman, Rick. "Those Toys Are Leaders In Box-Office Stampede", The New York Times, November 29, 1999. 
  38. James Bond movies. Box Office Mojo.
  39. TWINE Could Be Up for an Oscar. Commanderbond.net (4 January 2000).
  40. Awards for The World Is Not Enough. Internet Movie Database.
  41. The World Is Not Enough DVD review. TimeForDVD.com.
  42. The World Is Not Enough DVD & Soundtrack. UGO.
  43. Ebert, Roger. The World is Not Enough. Chicago Sun-Times.
  44. Gillespie, Eleanor Ringel. "The World Is Not Enough". 
  45. The World Is Not Enough. Fandango.Template:RT data
  46. The World Is Not Enough. Metacritic.
  47. 47.0 47.1 The World is Not Enough DVD Review (19 January 2000).
  48. Film: Review - Why Pierce is not enough. The Independent (28 November 1999).
  49. Matt Venendaal (16 May 2006). The World Is Not Enough (DVD) review.
  50. (2002) Film Fatales: Women in Espionage Films and Television, 1962–1973. Jefferson, NC: McFarland & Co. ISBN 978-0-7864-1194-8. 
  51. Howe, Desson, 19 November 1999, 'World': Bond without end --[[ ---------------------------------- 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 "*" returns two values: first value is one of these: 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 returns two values: first value is one of these: 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};, Washington Post.
  52. 52.0 52.1 Revisiting 1999's 'The World Is Not Enough'. Penske Media Corporation (19 October 2012).
  53. Rich, Joshua (8 January 2008). "James Bond Babes: Best and Worst". Entertainment Weekly. https://ew.com/gallery/bond-girls-best-and-worst/. Retrieved 5 February 2008. 
  54. Bond movies revisited: The World Is Not Enough (1999).
  55. You're stuck in the 50s, Mr Bond (en-US) (26 November 1999).
  56. Benjamin Svetkey, Joshua Rich (15 November 2006). "Countdown: Ranking the Bond Films". Entertainment Weekly. http://www.ew.com/ew/article/0,,1560072_2,00.html. Retrieved 5 February 2008. 
  57. Norman Wilner. Rating the Spy Game. MSN.
  58. James Bond's Top 20 (17 November 2006).
  59. Black Ops had previously adapted Tomorrow Never Dies for the PlayStation and would go on to develop Nightfire in 2002.
  60. The World Is Not Enough preview (PS2).
  61. Adam Blackwood.

External links[]