Module:Citation

require('strict')

local p = {}

local getArgs = require('Module:Arguments').getArgs local ISBN = require('Module:ISBN')._ISBN

local function nowiki(text) return mw.getCurrentFrame:callParserFunction('#tag', {'nowiki', text}) end

local function citation_id(args) if args.ref then if args.ref ~= 'none' then return mw.uri.anchorEncode(args.ref) end else local surnames = {} for i = 1, #args['author_data'] do			if args['author_data'][i]['surname'] then table.insert(surnames, args['author_data'][i]['surname']) end end if #surnames == 0 then for i = 1, #args['editor_data'] do				if args['editor_data'][i]['surname'] then table.insert(surnames, args['editor_data'][i]['surname']) end end end return mw.uri.anchorEncode('CITEREF' .. table.concat(surnames) .. (args.year or '')) end end

local function citation_authors(args) local authors = {} for i = 1, #args['author_data'] do		local text if args.swapall ~= false then text = args['author_data'][i]['surname'] if args['author_data'][i]['given'] then if text then text = text .. ', '				else text = '' end text = text .. args['author_data'][i]['given'] end else text = args['author_data'][i]['given'] if args['author_data'][i]['surname'] then if text then text = text .. ' '				else text = '' end text = text .. args['author_data'][i]['surname'] end end local link if args['author_data'][i]['link'] then link =  .. (text or ) .. '' else link = text end if link then table.insert(authors, link) end end local text if #authors > 3 then text = authors[1] .. '&semi; ' .. authors[2] .. ' &amp; ' .. authors[3] .. ' et al.' elseif #authors > 1 then text = table.concat(authors, '&semi; ', 1, #authors - 1) .. ' &amp; ' .. authors[#authors] elseif authors[1] then text = authors[1] end return text end

local function citation_patent(args) args.publicationnumber = args.publicationnumber or args.patentnumber local cite = mw.html.create('cite') :css({['font-style'] = 'normal'}) :attr('id', citation_id(args)) -- text local citation_text = {} args.swapall = false local authors = citation_authors(args) if authors then if args['date'] then text = text .. ' (' .. args['date'] .. ')' end table.insert(citation_text, authors) end if args.title then table.insert(citation_text, '"' .. args.title .. '"') end local citation_link = '[https://v3.espacenet.com/textdoc?DB=EPODOC&IDX=' .. (args.countrycode or '') .. (args.publicationnumber or '') .. ' ' .. (args.countrycode or '') if args.description then citation_link = citation_link .. ' ' .. args.description end if args.publicationnumber then citation_link = citation_link .. ' ' .. args.publicationnumber end citation_link = citation_link .. ']'	table.insert(citation_text, citation_link) if args.publicationdate then table.insert(citation_text, 'published in ' .. args.publicationdate) end if args.issuedate then table.insert(citation_text, 'issued ' .. args.issuedate) end if args.filingdate then table.insert(citation_text, 'filed ' .. args.filingdate) end if #citation_text > 0 then citation_text[#citation_text] = citation_text[#citation_text] .. '.'	end cite:wikitext(table.concat(citation_text, ', ')) return tostring(cite) end

local function work_link(args) local link_display if args.title then local link if args.url then link = args.url elseif args.doi then link = 'https://doi.org/' .. mw.uri.encode(args.doi) elseif args.pmid then link = 'https://www.ncbi.nlm.nih.gov/pubmed/' .. mw.uri.encode(args.pmid) end if link then link_display = '[' .. link .. ' ' .. args.title .. ']'		else link_display = args.title end if args.italics then link_display = "''" .. link_display .. "''"		else link_display = '"' .. link_display .. '"' end end return link_display end

local function citation_core(args) args.periodical = args.periodical or args.journal or args.newspaper or args.magazine args.includedworktitle = args.chapter or args.contribution or args.includedworktitle args.includedworkurl = args.chapterurl or args.contributionurl or args.includedworkurl or args.url args.place = args.place or args.location args.publicationplace = args.publicationplace or args.place if args.periodical then args.at = args.pages or args.page or args.at	elseif args.page then args.at = 'p. ' .. args.page elseif args.pages then args.at = 'pp. ' .. args.pages end local cite = mw.html.create('cite') :css({['font-style'] = 'normal'}) :attr('id', citation_id(args)) local citation_text = {} -- author or editor and date local authors = citation_authors(args) local editors = citation_authors({['author_data'] = args['editor_data']}) if authors or editors then local authed_text if authors then authed_text = authors elseif #args['editor_data'] > 1 then authed_text = editors .. ', eds.' else authed_text = editors .. ', ed.' end if args['date'] then authed_text = authed_text .. ' (' .. args['date'] .. ')' end table.insert(citation_text, authed_text) end -- title of included work local title = work_link({		['title'] = args.includedworktitle,		['url'] = args.includedworkurl,		['doi'] = args.doi,		['pmid'] = args.pmid,		['italics'] = (args.periodical ~= nil)	}) if title then table.insert(citation_text, title) end -- place (if different than publicationplace) if args.place and (args.place ~= args.publicationplace) then table.insert(citation_text, 'written at ' .. args.place) end -- editor of compilation if editors and authors then local editor_text = editors if args.includedworktitle then editor_text = 'in ' .. editor_text elseif #args['editor_data'] > 1 then editor_text = editor_text .. ', eds.' else editor_text = editor_text .. ', ed.' end table.insert(citation_text, editor_text) end -- periodicals if args.periodical then local periodical_title = work_link({			['title'] = args.title,			['url'] = args.url,			['doi'] = args.doi,			['pmid'] = args.pmid,			['italics'] = false		}) if periodical_title then table.insert(citation_text, periodical_title) end table.insert(citation_text, "" .. args.periodical .. "") local info = {} if args.series then table.insert(info, args.series) end local pub = {} if args.publicationplace then table.insert(pub, args.publicationplace) end if args.publisher then table.insert(pub, args.publisher) end if #pub > 0 then table.insert(info, '(' .. table.concat(pub, '&colon; ') .. ')')		end if args.volume and args.issue then table.insert(info, "" .. args.volume .. " (" .. args.issue .. ')')		elseif args.volume then table.insert(info, "" .. args.volume .. "") elseif args.issue then table.insert(info, '(no. ' .. args.issue .. ')')		end if args.at and #info > 0 then info[#info] = info[#info] .. '&colon; ' .. args.at		elseif args.at then table.insert(info, '&colon; ' .. args.at) end if #info > 0 then table.insert(citation_text, table.concat(info, ' ')) end -- anything else with a title, including books else local work_title = work_link({			['title'] = args.title,			['url'] = args.url,			['doi'] = args.doi,			['pmid'] = args.pmid,			['italics'] = true		}) if work_title then table.insert(citation_text, work_title) end local voled = {} if args.volume then table.insert(voled, 'vol. ' .. args.volume) end if args.edition then table.insert(voled, '(' .. args.edition .. ' ed.)') end if #voled > 0 then table.insert(citation_text, table.concat(voled, ' ')) end if args.series then table.insert(citation_text, args.series) end local pub = {} if args.publicationplace then table.insert(pub, args.publicationplace) end if args.publisher then table.insert(pub, args.publisher) end if #pub > 0 then table.insert(citation_text, table.concat(pub, '&colon; ')) end end -- date (if no author/editor) if not authors and not editors and args['date'] then table.insert(citation_text, args['date']) end -- publication date if args.publicationdate and args.publicationdate ~= args['date'] then if (editors and authors) or (not editors and args.periodical) then table.insert(citation_text, args.publicationdate) elseif #citation_text > 0 then citation_text[#citation_text] = citation_text[#citation_text] .. ' (published ' .. args.publicationdate .. ')' else table.insert(citation_text, '(published ' .. args.publicationdate .. ')')		end end -- page within included work if not args.periodical and args.at then table.insert(citation_text, args.at) end -- misc. identifier if args.id then table.insert(citation_text, args.id) end -- ISBN if args.isbn then table.insert(citation_text, ISBN({args.isbn})) end -- ISSN if args.issn then table.insert(			citation_text,			'ISSN .. mw.uri.encode(args.issn) .. ' ' .. nowiki(args.issn) .. ''		) end -- OCLC if args.oclc then table.insert(			citation_text,			'OCLC .. mw.uri.encode(args.oclc) .. ' ' .. nowiki(args.oclc) .. ''		) end -- PMID if args.pmid then table.insert(citation_text,  .. args.pmid .. ) end -- DOI if args.doi then if args.includedworkurl then table.insert(				citation_text,				'doi:.. mw.uri.encode(args.doi) .. ' ' .. nowiki(args.doi) .. ''			) elseif #citation_text > 0 then citation_text[#citation_text] = citation_text[#citation_text] .. ', DOI ' .. nowiki(args.doi) .. ' '		else table.insert(citation_text, ' DOI ' .. nowiki(args.doi) .. ' ') end end -- URL and accessdate if args.includedworkurl then local accessdate = '' if args.accessdate then accessdate = '. Retrieved on ' .. args.accessdate end if args.includedworktitle or args.title then -- local bracketed_link = '&lt;[' .. args.includedworkurl .. ' ' .. args.includedworkurl .. ']&gt;' local bracketed_link = '&lt;' .. args.includedworkurl .. '&gt;' if #citation_text > 0 then citation_text[#citation_text] = citation_text[#citation_text] .. ', ' .. bracketed_link .. ' ' .. accessdate else table.insert(citation_text, ' ' .. bracketed_link .. ' ' .. accessdate) end else table.insert(citation_text, bracketed_link .. accessdate) end end if #citation_text > 0 then citation_text[#citation_text] = citation_text[#citation_text] .. '.'	end cite:wikitext(table.concat(citation_text, ', ')) -- This is a COinS tag (http://ocoins.info), which allows automated tools to parse the citation information. local coins = mw.html.create('span') :addClass('Z3988') local coins_title = {'ctx_ver=Z39.88-2004&rft_val_fmt=' .. mw.uri.encode('info:ofi/fmt:kev:mtx:') .. 'book'} if args.periodical then table.insert(			coins_title,			'&rft.genre=article&rft.atitle=' .. mw.uri.encode(args.title or ) .. '&rft.title=' .. mw.uri.encode(args.periodical or )		) elseif args.includedworktitle then table.insert(			coins_title,			'&rft.genre=bookitem&rft.atitle=' .. mw.uri.encode(args.includedworktitle)		) else table.insert(coins_title, '&rft.genre=book') end table.insert(coins_title, '&rft.title=' .. mw.uri.encode(args.title or '')) if args['author_data'][1]['surname'] then table.insert(coins_title, '&rft.aulast=' .. mw.uri.encode(args['author_data'][1]['surname'])) end if args['author_data'][1]['given'] then table.insert(coins_title, '&rft.aufirst=' .. mw.uri.encode(args['author_data'][1]['given'])) end local coins_fragments = { ['at'] = '&rft.pages=', ['publicationplace'] = '&rft.place=', ['publisher'] = '&rft.pub=', ['doi'] = '&rft_id=info:doi/', ['pmid'] = '&rft_id=info:pmid/', ['includedworkurl'] = '&rft_id=' }	for k, v in pairs({'date', 'volume', 'issue', 'at', 'edition', 'place', 'publicationplace', 'publisher', 'doi', 'pmid', 'isbn', 'issn', 'includedworkurl'}) do		if args[v] then table.insert(				coins_title,				(coins_fragments[v] or '&rft.' .. v .. '=') .. mw.uri.encode(args[v])			) end end coins:attr('title', table.concat(coins_title)) coins:wikitext(' ') return tostring(cite) .. tostring(coins) end

function p._citation(args) -- lowercase args and remove hyphens local alias_args = {} for k, v in pairs(args) do		local newk = string.gsub(string.lower(k), '-', '') mw.logObject(k .. ' to ' .. newk) alias_args[newk] = v	end for k, v in pairs(alias_args) do		args[k] = args[k] or v	end -- date args['date'] = args['date'] or args['year'] or args.publicationdate -- accessdate -- FIXME: use Lua if args.accessdate then args.accessdate = mw.getContentLanguage:formatDate('F j, Y', args.accessdate, true) end -- year if not args.year then if args['date'] then -- FIXME: use Lua local success, datevalue = pcall(function		       return mw.getContentLanguage:formatDate('Y', args['date'], true)			end) if success then args['year'] = datevalue end end if args.publicationdate then -- FIXME: use Lua local success, datevalue = pcall(function		       return mw.getContentLanguage:formatDate('Y', args.publicationdate, true)			end) if success then args['year'] = datevalue end end end if not args.year then args.year = args['date'] end -- inventor to author alias_args = {} local patent = false for k, v in pairs(args) do		if mw.ustring.len(k) >= 8 and mw.ustring.sub(k, 1, 8) == 'inventor' then local akey = 'author' .. mw.ustring.sub(k, 9) alias_args[akey] = v			patent = true end end for k, v in pairs(alias_args) do		args[k] = args[k] or v	end -- organize author and editor info local author_data = {} local editor_data = {} for k, v in pairs(args) do		local b, n, a = mw.ustring.match(k, '(%l*)(%d*)(%l*)') n = tonumber(n) --[=[		mw.logObject('b: ' .. (b or '')) mw.logObject('n: ' .. (n or '')) mw.logObject('a: ' .. (a or '')) ]=]		if n then if b == 'author' or b == 'surname' or b == 'last' or b == 'given' or b == 'first' or b == 'authorlink' then author_data[n] = author_data[n] or {} if b == 'surname' or b == 'last' then author_data[n]['surname'] = author_data[n]['surname'] or v				elseif b == 'given' or b == 'first' then author_data[n]['given'] = author_data[n]['given'] or v				elseif b == 'authorlink' then author_data[n]['link'] = author_data[n]['link'] or v				else if a == 'surname' or a == 'last' then author_data[n]['surname'] = author_data[n]['surname'] or v					elseif a == 'given' or a == 'first' then author_data[n]['given'] = author_data[n]['given'] or v					elseif a == 'link' then author_data[n]['link'] = author_data[n]['link'] or v					end end elseif b == 'editor' or b == 'editorlink' then editor_data[n] = editor_data[n] or {} if a == 'surname' or a == 'last' then editor_data[n]['surname'] = editor_data[n]['surname'] or v				elseif a == 'given' or a == 'first' then editor_data[n]['given'] = editor_data[n]['given'] or v				elseif a == 'link' then editor_data[n]['link'] = editor_data[n]['link'] or v				end end end end -- special cases for first author/editor -- author author_data[1] = author_data[1] or {} author_data[1]['surname'] = author_data[1]['surname'] or args.authorsurname or args.authorlast or args.surname or args.last or args.author or args.authors author_data[1]['given'] = author_data[1]['given'] or args.authorgiven or args.authorfirst or args.given or args.first author_data[1]['link'] = author_data[1]['link'] or args.authorlink -- editor editor_data[1] = editor_data[1] or {} editor_data[1]['surname'] = editor_data[1]['surname'] or args.editorsurname or args.editorlast or args.editor or args.editors editor_data[1]['given'] = editor_data[1]['given'] or args.editorgiven or args.editorfirst editor_data[1]['link'] = editor_data[1]['link'] or args.editorlink -- add missing tables to sequence for i = 1, table.maxn(author_data) do		if not author_data[i] then author_data[i] = {} end end for i = 1, table.maxn(editor_data) do		if not editor_data[i] then editor_data[i] = {} end end if author_data == –  then author_data = nil end if editor_data == –  then editor_data = nil end args.author_data = author_data args.editor_data = editor_data -- return citation mw.logObject(args) if patent then return citation_patent(args) else return citation_core(args) end end

function p.citation(frame) return p._citation(getArgs(frame)) end

return p