Module:Citation

From Wikisource
Jump to navigation Jump to search

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 = '[[' .. args['author_data'][i]['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] .. '; ' .. authors[2] .. ' & ' .. authors[3] .. ' et al.'
	elseif #authors > 1 then
		text = table.concat(authors, '; ', 1, #authors - 1) .. ' & ' .. 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, ': ') .. ')')
		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] .. ': ' .. args.at
		elseif args.at then
			table.insert(info, ': ' .. 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, ': '))
		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,
			'[[:d:Special:GoToLinkedPage/enwiki/Q131276|ISSN]] [https://worldcat.org/issn/' .. mw.uri.encode(args.issn) .. ' ' .. nowiki(args.issn) .. ']'
		)
	end
	
	-- OCLC
	if args.oclc then
		table.insert(
			citation_text,
			'[[:d:Special:GoToLinkedPage/enwiki/Q190593|OCLC]] [https://worldcat.org/oclc/' .. mw.uri.encode(args.oclc) .. ' ' .. nowiki(args.oclc) .. ']'
		)
	end
	
	-- PMID
	if args.pmid then
		table.insert(citation_text, '[[pmid:' .. args.pmid .. ']]')
	end
	
	-- DOI
	if args.doi then
		if args.includedworkurl then
			table.insert(
				citation_text,
				'[[:d:Special:GoToLinkedPage/enwiki/Q25670|doi]]:[https://doi.org/' .. mw.uri.encode(args.doi) .. ' ' .. nowiki(args.doi) .. ']'
			)
		elseif #citation_text > 0 then
			citation_text[#citation_text] = citation_text[#citation_text] .. '<span class="printonly">, DOI ' .. nowiki(args.doi) .. '</span>'
		else
			table.insert(citation_text, '<span class="printonly">DOI ' .. nowiki(args.doi) .. '</span>')
		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] .. '<span class="printonly">, ' .. bracketed_link .. '</span>' .. accessdate
			else
				table.insert(citation_text, '<span class="printonly">' .. bracketed_link .. '</span>' .. 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('<span style="display:none;">&nbsp;</span>')
	
	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