وحدة:Citation/CS1/Identifiers

localidentifiers={};--[[--------------------------< F O R W A R D D E C L A R A T I O N S >--------------------------------------]]localis_set,in_array,set_error,select_one,add_maint_cat,substitute;-- functions in Module:Citation/CS1/Utilitieslocalz;-- table of tables defined in Module:Citation/CS1/Utilitieslocalcfg;-- table of configuration tables that are defined in Module:Citation/CS1/Configuration--[[--------------------------< E X T E R N A L _ L I N K _ I D >----------------------------------------------Formats a wiki style external link]]localfunctionexternal_link_id(options)localurl_string=options.id;localext_link;ifoptions.encode==trueoroptions.encode==nilthenurl_string=mw.uri.encode(url_string);endext_link=mw.ustring.format('[%s%s%s %s]',options.prefix,url_string,options.suffixor"",mw.text.nowiki(options.id));ifis_set(options.access)thenext_link=substitute(cfg.presentation[options.access],ext_link);-- add the free-to-read / paywall lockendreturnmw.ustring.format('[[%s|%s]]%s%s',options.link,options.label,options.separatoror"&nbsp;",ext_link);-- return mw.ustring.format( '[[%s|%s]]%s[%s%s%s %s]',-- options.link, options.label, options.separator or "&nbsp;",-- options.prefix, url_string, options.suffix or "",-- mw.text.nowiki(options.id)-- );end--[[--------------------------< I N T E R N A L _ L I N K _ I D >----------------------------------------------Formats a wiki style internal link]]localfunctioninternal_link_id(options)returnmw.ustring.format('[[%s|%s]]%s[[%s%s%s|%s]]',options.link,options.label,options.separatoror"&nbsp;",options.prefix,options.id,options.suffixor"",mw.text.nowiki(options.id));end--[[--------------------------< IS _ V A L I D _ I S X N >-----------------------------------------------------ISBN-10 and ISSN validator code calculates checksum across all isbn/issn digits including the check digit.ISBN-13 is checked in check_isbn().If the number is valid the result will be 0. Before calling this function, issbn/issn must be checked for lengthand stripped of dashes, spaces and other non-isxn characters.]]localfunctionis_valid_isxn(isxn_str,len)localtemp=0;isxn_str={isxn_str:byte(1,len)};-- make a table of byte values '0' → 0x30 .. '9' → 0x39, 'X' → 0x58len=len+1;-- adjust to be a loop counterfori,vinipairs(isxn_str)do-- loop through all of the bytes and calculate the checksumifv==string.byte("X")then-- if checkdigit is X (compares the byte value of 'X' which is 0x58)temp=temp+10*(len-i);-- it represents 10 decimalelsetemp=temp+tonumber(string.char(v))*(len-i);endendreturntemp%11==0;-- returns true if calculation result is zeroend--[[--------------------------< IS _ V A L I D _ I S X N _ 1 3 >----------------------------------------------ISBN-13 and ISMN validator code calculates checksum across all 13 isbn/ismn digits including the check digit.If the number is valid, the result will be 0. Before calling this function, isbn-13/ismn must be checked for lengthand stripped of dashes, spaces and other non-isxn-13 characters.]]localfunctionis_valid_isxn_13(isxn_str)localtemp=0;isxn_str={isxn_str:byte(1,13)};-- make a table of byte values '0' → 0x30 .. '9' → 0x39fori,vinipairs(isxn_str)dotemp=temp+(3-2*(i%2))*tonumber(string.char(v));-- multiply odd index digits by 1, even index digits by 3 and sum; includes check digitendreturntemp%10==0;-- sum modulo 10 is zero when isbn-13/ismn is correctend--[[--------------------------< C H E C K _ I S B N >------------------------------------------------------------Determines whether an ISBN string is valid]]localfunctioncheck_isbn(isbn_str)ifnil~=isbn_str:match("[^%s-0-9X]")thenreturnfalse,'invalid character';-- fail if isbn_str contains anything but digits, hyphens, or the uppercase Xendisbn_str=isbn_str:gsub("-",""):gsub(" ","");-- remove hyphens and spaceslocallen=isbn_str:len();iflen~=10andlen~=13thenreturnfalse,'length';-- fail if incorrect lengthendiflen==10thenifisbn_str:match("^%d*X?$")==nilthen-- fail if isbn_str has 'X' anywhere but last positionreturnfalse,'invalid form';endreturnis_valid_isxn(isbn_str,10),'checksum';elseifisbn_str:match("^%d+$")==nilthenreturnfalse,'invalid character';-- fail if isbn13 is not all digitsendifisbn_str:match("^97[89]%d*$")==nilthenreturnfalse,'invalid prefix';-- fail when isbn13 does not begin with 978 or 979endreturnis_valid_isxn_13(isbn_str),'checksum';endend--[[--------------------------< I S M N >----------------------------------------------------------------------Determines whether an ISMN string is valid. Similar to isbn-13, ismn is 13 digits begining 979-0-... and uses thesame check digit calculations. See http://www.ismn-international.org/download/Web_ISMN_Users_Manual_2008-6.pdfsection 2, pages 9–12.]]localfunctionismn(id)localhandler=cfg.id_handlers['ISMN'];localtext;localvalid_ismn=true;localid_copy;id_copy=id;-- save a copy because this testing is destructiveid=id:gsub("[%s-–]","");-- strip spaces, hyphens, and endashes from the ismnif13~=id:len()orid:match("^9790%d*$")==nilthen-- ismn must be 13 digits and begin 9790valid_ismn=false;elsevalid_ismn=is_valid_isxn_13(id);-- validate ismnend-- text = internal_link_id({link = handler.link, label = handler.label, -- use this (or external version) when there is some place to link to-- prefix=handler.prefix,id=id,separator=handler.separator, encode=handler.encode})text="[["..handler.link.."|"..handler.label.."]]"..handler.separator..id_copy;-- because no place to link to yetiffalse==valid_ismnthentext=text..' '..set_error('bad_ismn')-- add an error message if the ismn is invalidendreturntext;end--[[--------------------------< I S S N >----------------------------------------------------------------------Validate and format an issn. This code fixes the case where an editor has included an ISSN in the citation buthas separated the two groups of four digits with a space. When that condition occurred, the resulting link lookedlike this: |issn=0819 4327 gives: [http://www.worldcat.org/issn/0819 4327 0819 4327] -- can't have spaces in an external linkThis code now prevents that by inserting a hyphen at the issn midpoint. It also validates the issn for lengthand makes sure that the checkdigit agrees with the calculated value. Incorrect length (8 digits), charactersother than 0-9 and X, or checkdigit / calculated value mismatch will all cause a check issn error message. Theissn is always displayed with a hyphen, even if the issn was given as a single group of 8 digits.]]localfunctionissn(id,e)localissn_copy=id;-- save a copy of unadulterated issn; use this version for display if issn does not validatelocalhandler;localtext;localvalid_issn=true;ifethenhandler=cfg.id_handlers['EISSN'];elsehandler=cfg.id_handlers['ISSN'];endid=id:gsub("[%s-–]","");-- strip spaces, hyphens, and endashes from the issnif8~=id:len()ornil==id:match("^%d*X?$")then-- validate the issn: 8 digits long, containing only 0-9 or X in the last positionvalid_issn=false;-- wrong length or improper characterelsevalid_issn=is_valid_isxn(id,8);-- validate issnendiftrue==valid_issnthenid=string.sub(id,1,4).."-"..string.sub(id,5);-- if valid, display correctly formatted versionelseid=issn_copy;-- if not valid, use the show the invalid issn with error messageendtext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})iffalse==valid_issnthentext=text..' '..set_error('bad_issn',eand'e'or'')-- add an error message if the issn is invalidendreturntextend--[[--------------------------< A M A Z O N >------------------------------------------------------------------Formats a link to Amazon. Do simple error checking: asin must be mix of 10 numeric or uppercase alphacharacters. If a mix, first character must be uppercase alpha; if all numeric, asins must be 10-digitisbn. If 10-digit isbn, add a maintenance category so a bot or awb script can replace |asin= with |isbn=.Error message if not 10 characters, if not isbn10, if mixed and first character is a digit.]]localfunctionamazon(id,domain)localerr_cat=""ifnotid:match("^[%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u][%d%u]$")thenerr_cat=' '..set_error('bad_asin');-- asin is not a mix of 10 uppercase alpha and numeric characterselseifid:match("^%d%d%d%d%d%d%d%d%d[%dX]$")then-- if 10-digit numeric (or 9 digits with terminal X)ifcheck_isbn(id)then-- see if asin value is isbn10add_maint_cat('ASIN');elseifnotis_set(err_cat)thenerr_cat=' '..set_error('bad_asin');-- asin is not isbn10endelseifnotid:match("^%u[%d%u]+$")thenerr_cat=' '..set_error('bad_asin');-- asin doesn't begin with uppercase alphaendendifnotis_set(domain)thendomain="com";elseifin_array(domain,{'jp','uk'})then-- Japan, United Kingdomdomain="co."..domain;elseifin_array(domain,{'au','br','mx'})then-- Australia, Brazil, Mexicodomain="com."..domain;endlocalhandler=cfg.id_handlers['ASIN'];returnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix..domain.."/dp/",id=id,encode=handler.encode,separator=handler.separator})..err_cat;end--[[--------------------------< A R X I V >--------------------------------------------------------------------See: http://arxiv.org/help/arxiv_identifierformat and error check arXiv identifier. There are three valid forms of the identifier:the first form, valid only between date codes 9108 and 0703 is: arXiv:<archive>.<class>/<date code><number><version>where: <archive> is a string of alpha characters - may be hyphenated; no other punctuation <class> is a string of alpha characters - may be hyphenated; no other punctuation <date code> is four digits in the form YYMM where YY is the last two digits of the four-digit year and MM is the month number January = 01 first digit of YY for this form can only 9 and 0 <number> is a three-digit number <version> is a 1 or more digit number preceded with a lowercase v; no spaces (undocumented)the second form, valid from April 2007 through December 2014 is: arXiv:<date code>.<number><version>where: <date code> is four digits in the form YYMM where YY is the last two digits of the four-digit year and MM is the month number January = 01 <number> is a four-digit number <version> is a 1 or more digit number preceded with a lowercase v; no spacesthe third form, valid from January 2015 is: arXiv:<date code>.<number><version>where: <date code> and <version> are as defined for 0704-1412 <number> is a five-digit number]]localfunctionarxiv(id,class)localhandler=cfg.id_handlers['ARXIV'];localyear,month,version;localerr_cat='';localtext;ifid:match("^%a[%a%.%-]+/[90]%d[01]%d%d%d%d$")orid:match("^%a[%a%.%-]+/[90]%d[01]%d%d%d%dv%d+$")then-- test for the 9108-0703 format w/ & w/o versionyear,month=id:match("^%a[%a%.%-]+/([90]%d)([01]%d)%d%d%d[v%d]*$");year=tonumber(year);month=tonumber(month);if((not(90<yearor8>year))or(1>monthor12<month))or-- if invalid year or invalid month((91==yearand7>month)or(7==yearand3<month))then-- if years ok, are starting and ending months ok?err_cat=' '..set_error('bad_arxiv');-- set error messageendelseifid:match("^%d%d[01]%d%.%d%d%d%d$")orid:match("^%d%d[01]%d%.%d%d%d%dv%d+$")then-- test for the 0704-1412 w/ & w/o versionyear,month=id:match("^(%d%d)([01]%d)%.%d%d%d%d[v%d]*$");year=tonumber(year);month=tonumber(month);if((7>year)or(14<year)or(1>monthor12<month))or-- is year invalid or is month invalid? (doesn't test for future years)((7==year)and(4>month))then--or -- when year is 07, is month invalid (before April)?err_cat=' '..set_error('bad_arxiv');-- set error messageendelseifid:match("^%d%d[01]%d%.%d%d%d%d%d$")orid:match("^%d%d[01]%d%.%d%d%d%d%dv%d+$")then-- test for the 1501- format w/ & w/o versionyear,month=id:match("^(%d%d)([01]%d)%.%d%d%d%d%d[v%d]*$");year=tonumber(year);month=tonumber(month);if((15>year)or(1>monthor12<month))then-- is year invalid or is month invalid? (doesn't test for future years)err_cat=' '..set_error('bad_arxiv');-- set error messageendelseerr_cat=' '..set_error('bad_arxiv');-- arXiv id doesn't match any formatendtext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=handler.access})..err_cat;ifis_set(class)thenclass=' [['..'//arxiv.org/archive/'..class..' '..class..']]';-- external link within square brackets, not wikilinkelseclass='';-- empty string for concatenationendreturntext..class;end--[[--------------------------< B I O R X I V >-----------------------------------------------------------------Format bioRxiv id and do simple error checking. BiorXiv ids are exactly 6 digits.The bioRxiv id is the number following the last slash in the bioRxiv-issued DOI:http://dx.doi.org/10.1101/078733 -> 078733]]localfunctionbiorxiv(id)localhandler=cfg.id_handlers['BIORXIV'];localerr_cat='';-- presume that bioRxiv id is validifnil==id:match("^%d%d%d%d%d%d$")then-- if bioRxiv id has anything but six digitserr_cat=' '..set_error('bad_biorxiv');-- set an error messageendreturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=handler.access})..err_cat;end--[[--------------------------< N O R M A L I Z E _ L C C N >--------------------------------------------------lccn normalization (http://www.loc.gov/marc/lccn-namespace.html#normalization)1. Remove all blanks.2. If there is a forward slash (/) in the string, remove it, and remove all characters to the right of the forward slash.3. If there is a hyphen in the string: a. Remove it. b. Inspect the substring following (to the right of) the (removed) hyphen. Then (and assuming that steps 1 and 2 have been carried out): 1. All these characters should be digits, and there should be six or less. (not done in this function) 2. If the length of the substring is less than 6, left-fill the substring with zeroes until the length is six.Returns a normalized lccn for lccn() to validate. There is no error checking (step 3.b.1) performed in this function.]]localfunctionnormalize_lccn(lccn)lccn=lccn:gsub("%s","");-- 1. strip whitespaceifnil~=string.find(lccn,'/')thenlccn=lccn:match("(.-)/");-- 2. remove forward slash and all character to the right of itendlocalprefixlocalsuffixprefix,suffix=lccn:match("(.+)%-(.+)");-- 3.a remove hyphen by splitting the string into prefix and suffixifnil~=suffixthen-- if there was a hyphensuffix=string.rep("0",6-string.len(suffix))..suffix;-- 3.b.2 left fill the suffix with 0s if suffix length less than 6lccn=prefix..suffix;-- reassemble the lccnendreturnlccn;end--[[--------------------------< L C C N >----------------------------------------------------------------------Format LCCN link and do simple error checking. LCCN is a character string 8-12 characters long. The length ofthe LCCN dictates the character type of the first 1-3 characters; the rightmost eight are always digits.http://info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/length = 8 then all digitslength = 9 then lccn[1] is lower case alphalength = 10 then lccn[1] and lccn[2] are both lower case alpha or both digitslength = 11 then lccn[1] is lower case alpha, lccn[2] and lccn[3] are both lower case alpha or both digitslength = 12 then lccn[1] and lccn[2] are both lower case alpha]]localfunctionlccn(lccn)localhandler=cfg.id_handlers['LCCN'];localerr_cat='';-- presume that LCCN is validlocalid=lccn;-- local copy of the lccnid=normalize_lccn(id);-- get canonical form (no whitespace, hyphens, forward slashes)locallen=id:len();-- get the length of the lccnif8==lenthenifid:match("[^%d]")then-- if LCCN has anything but digits (nil if only digits)err_cat=' '..set_error('bad_lccn');-- set an error messageendelseif9==lenthen-- LCCN should be addddddddifnil==id:match("%l%d%d%d%d%d%d%d%d")then-- does it match our pattern?err_cat=' '..set_error('bad_lccn');-- set an error messageendelseif10==lenthen-- LCCN should be aadddddddd or ddddddddddifid:match("[^%d]")then-- if LCCN has anything but digits (nil if only digits) ...ifnil==id:match("^%l%l%d%d%d%d%d%d%d%d")then-- ... see if it matches our patternerr_cat=' '..set_error('bad_lccn');-- no match, set an error messageendendelseif11==lenthen-- LCCN should be aaadddddddd or addddddddddifnot(id:match("^%l%l%l%d%d%d%d%d%d%d%d")orid:match("^%l%d%d%d%d%d%d%d%d%d%d"))then-- see if it matches one of our patternserr_cat=' '..set_error('bad_lccn');-- no match, set an error messageendelseif12==lenthen-- LCCN should be aaddddddddddifnotid:match("^%l%l%d%d%d%d%d%d%d%d%d%d")then-- see if it matches our patternerr_cat=' '..set_error('bad_lccn');-- no match, set an error messageendelseerr_cat=' '..set_error('bad_lccn');-- wrong length, set an error messageendifnotis_set(err_cat)andnil~=lccn:find('%s')thenerr_cat=' '..set_error('bad_lccn');-- lccn contains a space, set an error messageendreturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=lccn,separator=handler.separator,encode=handler.encode})..err_cat;end--[[--------------------------< P M I D >----------------------------------------------------------------------Format PMID and do simple error checking. PMIDs are sequential numbers beginning at 1 and counting up. Thiscode checks the PMID to see that it contains only digits and is less than test_limit; the value in local variabletest_limit will need to be updated periodically as more PMIDs are issued.]]localfunctionpmid(id)localtest_limit=30000000;-- update this value as PMIDs approachlocalhandler=cfg.id_handlers['PMID'];localerr_cat='';-- presume that PMID is validifid:match("[^%d]")then-- if PMID has anything but digitserr_cat=' '..set_error('bad_pmid');-- set an error messageelse-- PMID is only digitslocalid_num=tonumber(id);-- convert id to a number for range testingif1>id_numortest_limit<id_numthen-- if PMID is outside test limit boundarieserr_cat=' '..set_error('bad_pmid');-- set an error messageendendreturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})..err_cat;end--[[--------------------------< I S _ E M B A R G O E D >------------------------------------------------------Determines if a PMC identifier's online version is embargoed. Compares the date in |embargo= against today's date. If embargo date isin the future, returns the content of |embargo=; otherwise, returns and empty string because the embargo has expired or because|embargo= was not set in this cite.]]localfunctionis_embargoed(embargo)ifis_set(embargo)thenlocallang=mw.getContentLanguage();localgood1,embargo_date,good2,todays_date;good1,embargo_date=pcall(lang.formatDate,lang,'U',embargo);good2,todays_date=pcall(lang.formatDate,lang,'U');ifgood1andgood2then-- if embargo date and today's date are good datesiftonumber(embargo_date)>=tonumber(todays_date)then-- is embargo date is in the future?returnembargo;-- still embargoedelseadd_maint_cat('embargo')return'';-- unset because embargo has expiredendendendreturn'';-- |embargo= not set return empty stringend--[[--------------------------< P M C >------------------------------------------------------------------------Format a PMC, do simple error checking, and check for embargoed articles.The embargo parameter takes a date for a value. If the embargo date is in the future the PMC identifier will notbe linked to the article. If the embargo date is today or in the past, or if it is empty or omitted, then thePMC identifier is linked to the article through the link at cfg.id_handlers['PMC'].prefix.PMC embargo date testing is done in function is_embargoed () which is called earlier because when the citationhas |pmc=<value> but does not have a |url= then |title= is linked with the PMC link. Function is_embargoed ()returns the embargo date if the PMC article is still embargoed, otherwise it returns an empty string.PMCs are sequential numbers beginning at 1 and counting up. This code checks the PMC to see that it contains only digits and is lessthan test_limit; the value in local variable test_limit will need to be updated periodically as more PMCs are issued.]]localfunctionpmc(id,embargo)localtest_limit=6000000;-- update this value as PMCs approachlocalhandler=cfg.id_handlers['PMC'];localerr_cat='';-- presume that PMC is validlocaltext;ifid:match("[^%d]")then-- if PMC has anything but digitserr_cat=' '..set_error('bad_pmc');-- set an error messageelse-- PMC is only digitslocalid_num=tonumber(id);-- convert id to a number for range testingif1>id_numortest_limit<id_numthen-- if PMC is outside test limit boundarieserr_cat=' '..set_error('bad_pmc');-- set an error messageendendifis_set(embargo)then-- is PMC is still embargoed?text="[["..handler.link.."|"..handler.label.."]]"..handler.separator..id..err_cat;-- still embargoed so no external linkelsetext=external_link_id({link=handler.link,label=handler.label,-- no embargo date or embargo has expired, ok to link to articleprefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=handler.access})..err_cat;endreturntext;end--[[--------------------------< D O I >------------------------------------------------------------------------Formats a DOI and checks for DOI errors.DOI names contain two parts: prefix and suffix separated by a forward slash. Prefix: directory indicator '10.' followed by a registrant code Suffix: character string of any length chosen by the registrantThis function checks a DOI name for: prefix/suffix. If the doi name contains spaces or endashes, or, if it endswith a period or a comma, this function will emit a bad_doi error message.DOI names are case-insensitive and can incorporate any printable Unicode characters so the test for spaces, endash,and terminal punctuation may not be technically correct but it appears, that in practice these characters are rarelyif ever used in doi names.]]localfunctiondoi(id,inactive,access)localcat=""localhandler=cfg.id_handlers['DOI'];localtext;ifis_set(inactive)thenlocalinactive_year=inactive:match("%d%d%d%d")or'';-- try to get the year portion from the inactive datetext="[["..handler.link.."|"..handler.label.."]]:"..id;ifis_set(inactive_year)thentable.insert(z.error_categories,"Pages with DOIs inactive since "..inactive_year);elsetable.insert(z.error_categories,"Pages with inactive DOIs");-- when inactive doesn't contain a recognizable yearendinactive=" ("..cfg.messages['inactive'].." "..inactive..")"elsetext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=access})inactive=""endifnil==id:match("^10%.[^%s–]-/[^%s–]-[^%.,]$")then-- doi must begin with '10.', must contain a fwd slash, must not contain spaces or endashes, and must not end with period or commacat=' '..set_error('bad_doi');endreturntext..inactive..catend--[[--------------------------< H D L >------------------------------------------------------------------------Formats an HDL with minor error checking.HDL names contain two parts: prefix and suffix separated by a forward slash. Prefix: character string using any character in the UCS-2 character set except '/' Suffix: character string of any length using any character in the UCS-2 character set chosen by the registrantThis function checks a HDL name for: prefix/suffix. If the HDL name contains spaces, endashes, or, if it endswith a period or a comma, this function will emit a bad_hdl error message.HDL names are case-insensitive and can incorporate any printable Unicode characters so the test for endashes andterminal punctuation may not be technically correct but it appears, that in practice these characters are rarelyif ever used in HDLs.]]localfunctionhdl(id,access)localhandler=cfg.id_handlers['HDL'];localtext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=access})ifnil==id:match("^[^%s–]-/[^%s–]-[^%.,]$")then-- hdl must contain a fwd slash, must not contain spaces, endashes, and must not end with period or commatext=text..' '..set_error('bad_hdl');endreturntext;end--[[--------------------------< O P E N L I B R A R Y >--------------------------------------------------------Formats an OpenLibrary link, and checks for associated errors.]]localfunctionopenlibrary(id,access)localcode=id:match("^%d+([AMW])$");-- only digits followed by 'A', 'M', or 'W'localhandler=cfg.id_handlers['OL'];if(code=="A")thenreturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix..'authors/OL',id=id,separator=handler.separator,encode=handler.encode,access=access})elseif(code=="M")thenreturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix..'books/OL',id=id,separator=handler.separator,encode=handler.encode,access=access})elseif(code=="W")thenreturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix..'works/OL',id=id,separator=handler.separator,encode=handler.encode,access=access})elsereturnexternal_link_id({link=handler.link,label=handler.label,prefix=handler.prefix..'OL',id=id,separator=handler.separator,encode=handler.encode,access=access})..' '..set_error('bad_ol');endend--[[--------------------------< M E S S A G E _ I D >----------------------------------------------------------Validate and format a usenet message id. Simple error checking, looks for 'id-left@id-right' not enclosed in'<' and/or '>' angle brackets.]]localfunctionmessage_id(id)localhandler=cfg.id_handlers['USENETID'];localtext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})ifnotid:match('^.+@.+$')ornotid:match('^[^<].*[^>]$')then-- doesn't have '@' or has one or first or last character is '< or '>'text=text..' '..set_error('bad_message_id')-- add an error message if the message id is invalidendreturntextend--[[--------------------------< O C L C >----------------------------------------------------------------------Validate and format an oclc id. https://www.oclc.org/batchload/controlnumber.en.html]]localfunctionoclc(id)localhandler=cfg.id_handlers['OCLC'];localnumber;localerr_msg='';-- empty string for concatenationifid:match('^ocm%d%d%d%d%d%d%d%d$')then-- ocm prefix and 8 digits; 001 field (12 characters)number=id:match('ocm(%d+)');-- get the numberelseifid:match('^ocn%d%d%d%d%d%d%d%d%d$')then-- ocn prefix and 9 digits; 001 field (12 characters)number=id:match('ocn(%d+)');-- get the numberelseifid:match('^on%d%d%d%d%d%d%d%d%d%d+$')then-- on prefix and 10 or more digits; 001 field (12 characters)number=id:match('^on(%d%d%d%d%d%d%d%d%d%d+)$');-- get the numberelseifid:match('^%(OCoLC%)[1-9]%d*$')then-- (OCoLC) prefix and variable number digits; no leading zeros; 035 fieldnumber=id:match('%(OCoLC%)([1-9]%d*)');-- get the numberif9<number:len()thennumber=nil;-- contrain to 1 to 9 digits; change this when oclc issues 10-digit numbersendelseifid:match('^%d+$')then-- no prefixnumber=id;-- get the numberif10<number:len()thennumber=nil;-- contrain to 1 to 10 digits; change this when oclc issues 11-digit numbersendendifnumberthen-- proper formatid=number;-- exclude prefix, if any, from external linkelseerr_msg=' '..set_error('bad_oclc')-- add an error message if the id is malformedendlocaltext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode})..err_msg;returntext;end--[[--------------------------< B I B C O D E >--------------------------------------------------------------------Validates (sort of) and formats a bibcode id.Format for bibcodes is specified here: http://adsabs.harvard.edu/abs_doc/help_pages/data.html#bibcodesBut, this: 2015arXiv151206696F is apparently valid so apparently, the only things that really matter are length, 19 charactersand first four digits must be a year. This function makes these tests: length must be 19 characters characters in position 1–4 must be digits and must represent a year in the range of 1000 – next year 5 must be a letter 6 must be letter, ampersand, or dot (ampersand cannot directly precede a dot; &. ) 7–8 must be letter, digit, ampersand, or dot (ampersand cannot directly precede a dot; &. ) 9–18 must be letter, digit, or dot 19 must be a letter or dot]]localfunctionbibcode(id,access)localhandler=cfg.id_handlers['BIBCODE'];localerr_type;localyear;localtext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=access});if19~=id:len()thenerr_type='length';elseyear=id:match("^(%d%d%d%d)[%a][%a&%.][%a&%.%d][%a&%.%d][%a%d%.]+[%a%.]$")-- ifnotyearthen-- if nil then no pattern matcherr_type='value';-- so value errorelselocalnext_year=tonumber(os.date('%Y'))+1;-- get the current year as a number and add one for next yearyear=tonumber(year);-- convert year portion of bibcode to a numberif(1000>year)or(year>next_year)thenerr_type='year';-- year out of boundsendifid:find('&%.')thenerr_type='journal';-- journal abbreviation must not have '&.' (if it does its missing a letter)endendendifis_set(err_type)then-- if there was an error detectedtext=text..' '..set_error('bad_bibcode',{err_type});endreturntext;end--[[--------------------------< C I T E S E E R X >------------------------------------------------------------CiteSeerX use their own notion of "doi" (not to be confused with the identifiers resolved via doi.org).The description of the structure of this identifier can be found at Help_talk:Citation_Style_1#CiteSeerX_id_structure]]localfunctionciteseerx(id)localhandler=cfg.id_handlers['CITESEERX'];localmatched;localtext=external_link_id({link=handler.link,label=handler.label,prefix=handler.prefix,id=id,separator=handler.separator,encode=handler.encode,access=handler.access});matched=id:match("^10%.1%.1%.[1-9]%d?%d?%d?%.[1-9]%d?%d?%d?$");ifnotmatchedthentext=text..' '..set_error('bad_citeseerx');endreturntext;end--[[--------------------------< B U I L D _ I D _ L I S T >--------------------------------------------------------Takes a table of IDs created by extract_ids() and turns it into a table of formatted ID outputs.inputs: id_list – table of identifiers built by extract_ids() options – table of various template parameter values used to modify some manually handled identifiers]]localfunctionbuild_id_list(id_list,options)localnew_list,handler={};localfunctionfallback(k)return{__index=function(t,i)returncfg.id_handlers[k][i]end}end;fork,vinpairs(id_list)do-- k is uc identifier name as index to cfg.id_handlers; e.g. cfg.id_handlers['ISBN'], v is a table-- fallback to read-only cfghandler=setmetatable({['id']=v,['access']=options.IdAccessLevels[k]},fallback(k));ifhandler.mode=='external'thentable.insert(new_list,{handler.label,external_link_id(handler)});elseifhandler.mode=='internal'thentable.insert(new_list,{handler.label,internal_link_id(handler)});elseifhandler.mode~='manual'thenerror(cfg.messages['unknown_ID_mode']);elseifk=='BIBCODE'thentable.insert(new_list,{handler.label,bibcode(v,handler.access)});elseifk=='BIORXIV'thentable.insert(new_list,{handler.label,biorxiv(v)});elseifk=='CITESEERX'thentable.insert(new_list,{handler.label,citeseerx(v)});elseifk=='DOI'thentable.insert(new_list,{handler.label,doi(v,options.DoiBroken,handler.access)});elseifk=='HDL'thentable.insert(new_list,{handler.label,hdl(v,handler.access)});elseifk=='ARXIV'thentable.insert(new_list,{handler.label,arxiv(v,options.Class)});elseifk=='ASIN'thentable.insert(new_list,{handler.label,amazon(v,options.ASINTLD)});elseifk=='LCCN'thentable.insert(new_list,{handler.label,lccn(v)});elseifk=='OL'ork=='OLA'thentable.insert(new_list,{handler.label,openlibrary(v,handler.access)});elseifk=='PMC'thentable.insert(new_list,{handler.label,pmc(v,options.Embargo)});elseifk=='PMID'thentable.insert(new_list,{handler.label,pmid(v)});elseifk=='OCLC'thentable.insert(new_list,{handler.label,oclc(v)});elseifk=='ISMN'thentable.insert(new_list,{handler.label,ismn(v)});elseifk=='ISSN'thentable.insert(new_list,{handler.label,issn(v)});elseifk=='EISSN'thentable.insert(new_list,{handler.label,issn(v,true)});-- true distinguishes eissn from issnelseifk=='ISBN'thenlocalISBN=internal_link_id(handler);localcheck;localerr_type='';-- if not check_isbn( v ) and not is_set(options.IgnoreISBN) then-- ISBN = ISBN .. set_error( 'bad_isbn', {}, false, " ", "" );-- endcheck,err_type=check_isbn(v);ifnotcheckthenifis_set(options.IgnoreISBN)then-- ISBN is invalid; if |ignore-isbn-error= setadd_maint_cat('ignore_isbn_err');-- ad a maint categoryelseISBN=ISBN..set_error('bad_isbn',{err_type},false," ","");-- else display an error messageendelseifis_set(options.IgnoreISBN)then-- ISBN is OK; if |ignore-isbn-error= setadd_maint_cat('ignore_isbn_err');-- because |ignore-isbn-error= unnecessaryendtable.insert(new_list,{handler.label,ISBN});elseifk=='USENETID'thentable.insert(new_list,{handler.label,message_id(v)});elseerror(cfg.messages['unknown_manual_ID']);endendlocalfunctioncomp(a,b)-- used in following table.sort()returna[1]<b[1];endtable.sort(new_list,comp);fork,vinipairs(new_list)donew_list[k]=v[2];endreturnnew_list;end--[[--------------------------< E X T R A C T _ I D S >------------------------------------------------------------Populates ID table from arguments using configuration settings. Loops through cfg.id_handlers and searches args forany of the parameters listed in each cfg.id_handlers['...'].parameters. If found, adds the parameter and value tothe identifier list. Emits redundant error message is more than one alias exists in args]]localfunctionextract_ids(args)localid_list={};-- list of identifiers found in argsfork,vinpairs(cfg.id_handlers)do-- k is uc identifier name as index to cfg.id_handlers; e.g. cfg.id_handlers['ISBN'], v is a tablev=select_one(args,v.parameters,'redundant_parameters');-- v.parameters is a table of aliases for k; here we pick one from args if presentifis_set(v)thenid_list[k]=v;end-- if found in args, add identifier to our listendreturnid_list;end--[[--------------------------< E X T R A C T _ I D _ A C C E S S _ L E V E L S >--------------------------------------Fetches custom id access levels from arguments using configuration settings.Parameters which have a predefined access level (e.g. arxiv) do not use thisfunction as they are directly rendered as free without using an additional parameter.]]localfunctionextract_id_access_levels(args,id_list)localid_accesses_list={};fork,vinpairs(cfg.id_handlers)dolocalaccess_param=v.custom_access;localk_lower=string.lower(k);ifis_set(access_param)thenlocalaccess_level=args[access_param];ifis_set(access_level)thenifnotin_array(access_level:lower(),cfg.keywords['id-access'])thentable.insert(z.message_tail,{set_error('invalid_param_val',{access_param,access_level},true)});access_level=nil;endifnotis_set(id_list[k])thentable.insert(z.message_tail,{set_error('param_access_requires_param',{k_lower},true)});endifis_set(access_level)thenaccess_level=access_level:lower();endid_accesses_list[k]=access_level;endendendreturnid_accesses_list;end--[[--------------------------< S E T _ S E L E C T E D _ M O D U L E S >--------------------------------------Sets local cfg table and imported functions table to same (live or sandbox) as that used by the other modules.]]localfunctionset_selected_modules(cfg_table_ptr,utilities_page_ptr)cfg=cfg_table_ptr;is_set=utilities_page_ptr.is_set;-- import functions from select Module:Citation/CS1/Utilities modulein_array=utilities_page_ptr.in_array;set_error=utilities_page_ptr.set_error;select_one=utilities_page_ptr.select_one;add_maint_cat=utilities_page_ptr.add_maint_cat;substitute=utilities_page_ptr.substitute;z=utilities_page_ptr.z;-- table of tables in Module:Citation/CS1/Utilitiesendreturn{build_id_list=build_id_list,extract_ids=extract_ids,extract_id_access_levels=extract_id_access_levels,is_embargoed=is_embargoed;set_selected_modules=set_selected_modules;}