Module:Cytuj

From Outreach Wiki
Jump to navigation Jump to search
local resources = mw.loadData("Module:Cytuj/dane")
local access = mw.loadData("Module:Cytuj/dostęp")

local function softNoWiki(text)
	local result, count = string.gsub(text, "['%[%]{|}\"]", { ["\""] = """, ["'"] = "'", ["["] = "[", ["]"] = "]", ["{"] = "{", ["|"] = "|", ["}"] = "}"})
	return result
end

local function escapeUrl(url)
	local result, count = string.gsub(url, "[ '%[%]]", { [" "] = "%20", ["'"] = "%27", ["["] = "%5B", ["]"] = "%5D"})
	return result
end

local function plainText(text)
	local result, count = string.gsub(text, "</?[Ss][Pp][Aa][Nn][^>]*>", "")
	return result
end

local function determineMode(p)
	local detector = {}
	local count = 0
	for i, v in ipairs(resources.modes) do
		detector[i] = v
		count = count + 1
	end
	
	detector[1] = false -- skip 'auto'
	count = count - 1
	for k, v in pairs(resources.params) do
		local arg = p.args[v.name]
		for i, w in ipairs(v.used) do
			if not w and arg then
				-- unexpected argument
				if detector[i] then
					detector[i] = false
					count = count - 1
					if count == 0 then
						-- the mode cannot be determined
						break
					end
				end
			end
		end
		if count == 0 then
			-- the mode cannot be determined
			break
		end
	end

	for i, v in ipairs(detector) do
		if detector[i] then
			-- the type is succesfully determined
			-- but in case more than one is possible
			-- use only the first one
			-- include COinS format if this is the only determined type
			return i, count == 1 and resources.COinS[i] or false
		end
	end

	-- in case nothing is selected
	-- use the auto mode as default fallback
	return 1
end

local authorMetatable = {}
local authorMethodtable = {}

authorMetatable.__index = authorMethodtable

local function checkPatterns(author, prefixes, suffixes)
	if author.exact then
		return false
	end
	
	if author.prefix and prefixes then
		for _, v in ipairs(prefixes) do
			if mw.ustring.match(author.prefix, v) then
				return true
			end
		end
	end
	
	if author.suffix and suffixes then
		for _, v in ipairs(suffixes) do
			if mw.ustring.match(author.suffix, v) then
				return true
			end
		end
	end

	return false
end

authorMethodtable.format = function(data, namefirst)
	if data.exact then
		return data.exact
	end

	if namefirst and data.familynamefirst then
		namefirst = false
	end
	
	local builder = mw.html.create()
	local name = data.name and (#data.name > 0)
	local initials = data.nameinitials and (#data.nameinitials > 0)
	local namehint = nil
	if name and initials and (data.name ~= data.nameinitials) then
		namehint = data.name
	end
	
	if not data.familynamefirst and (name or initials) then
		local before = namefirst and builder or builder:tag("span"):addClass("cite-name-before")
		if name then
			before:tag("span"):addClass("cite-name-full"):wikitext(softNoWiki(data.name))
		end
		if initials then
			before:tag("span"):css("display", "none"):addClass("cite-name-initials"):attr("title", namehint):wikitext(softNoWiki(data.nameinitials))
		end
		before:wikitext("&nbsp;")
	end

	builder:tag("span"):addClass("cite-lastname"):wikitext(softNoWiki(data.lastname))

	if not namefirst and (name or initials) then
		local after = data.familynamefirst and builder or builder:tag("span"):css("display", "none"):addClass("cite-name-after")
		after:wikitext("&nbsp;")
		if name then
			after:tag("span"):addClass("cite-name-full"):wikitext(softNoWiki(data.name))
		end
		if initials then
			after:tag("span"):addClass("cite-name-initials"):attr("title", namehint):wikitext(softNoWiki(data.nameinitials))
		end
		if data.js then
			after:wikitext(",")
		end
	end
	
	if data.js then
		builder:wikitext("&nbsp;", data.js)
	end
	
	return tostring(builder)
end

authorMethodtable.towiki = function(data)
	if data.exact then
		return data.exact
	end

	local result = {}
	local name = data.name and (#data.name > 0)
	if not data.familynamefirst and name then
		table.insert(result,softNoWiki(data.name))
		table.insert(result, " ")
	end
	
	table.insert(result, softNoWiki(data.lastname))

	if data.familynamefirst and name then
		table.insert(result, " ")
		table.insert(result, softNoWiki(data.name))
	end

	return table.concat(result)
end

local function makeInitials(name)
	local nameinitials = mw.ustring.gsub(name, "(%w[Hh]?)[%w]*%.?([%s%-–—]?)%s*", "%1. ") -- zostaw początki słów (jedna litera + opcjonalne następujące 'h')
	nameinitials = mw.ustring.gsub(nameinitials, "%f[%w]%l%.%s", "")               -- usuń inicjały z małych liter
	nameinitials = mw.ustring.gsub(nameinitials, "([^C%W])[Hh]%.?%s", "%1. ")      -- usuń drugie 'h' jeśli nie zaczyna się na 'C'
	nameinitials = mw.ustring.gsub(nameinitials, "(%u[Hh]?)[%.%s]*", "%1.")        -- dodaj brakujące kropki i usuń zbędne spacje
	return mw.text.trim(nameinitials)
end

local function parseAuthor(author)
	
	local result = {}
	
	if string.match(author, "\127") then -- wpisy z <nowiki> nie są analizowane
		result.exact = author
		setmetatable(result, authorMetatable)
		return result
	end
	
	local author = mw.text.trim(author)
	
	local a = string.gsub(author, "\\[\\%.:]", { ["\\\\"]="\\", ["\\."]=",", ["\\:"]=";", })
	if a ~= author then
		result.exact = a
		setmetatable(result, authorMetatable)
		return result
	end

	if resources.exactAuthors[author] then
		result.exact = author
		setmetatable(result, authorMetatable)
		return result
	end

	local exactName = mw.ustring.match(author, "^%s*%*%s*(.*)$")
	if exactName then
		result.exact = mw.text.trim(exactName)
		if #result.exact == 0 then
			return nil
		end
		
		setmetatable(result, authorMetatable)
		return result
	end

	local prefix0, link, description, suffix0 = mw.ustring.match(author, "^(.-)%[%[(.-)%|(.-)%]%](.*)$")
	if prefix0 then
		result.link = link
		author = description
	else
		prefix0, link, suffix0 = mw.ustring.match(author, "^(.-)%[%[(.-)%]%](.*)$")
		if prefix0 then
			author = link
			result.link = link
		else
			prefix0 = ""
			suffix0 = ""
		end
	end

	local prefix1, rest = mw.ustring.match(author, "^([%l%p%s]+)(.+)$")
	if not prefix1 then
		rest = author
		prefix1 = ""
	end

	local prefix = mw.text.trim(prefix0.." "..prefix1)
	if #prefix > 0 then
		if mw.ustring.sub(prefix, -1) == "#" then
			result.familynamefirst = true
			prefix = mw.text.trim(mw.ustring.match(prefix, "^(.-)#$"))
		end
		if #prefix > 0 then
			result.prefix = mw.ustring.gsub(prefix, "%s+", " ") -- collapse spaces
		end
	end

	local rest2, suffix = mw.ustring.match(rest, "^([%w%-%.%s]-)%s([%l%p%s]-)$")
	if not suffix then
		rest2 = rest
		suffix = ""
	end
	
	suffix = mw.text.trim(suffix.." "..suffix0)
	if #suffix > 0 then
		result.suffix = mw.ustring.gsub(suffix, "%s+", " ") -- collapse spaces
		suffix = " "..result.suffix
		for i, v in ipairs(resources.js) do
			if mw.ustring.match(suffix, v[1]) then
				result.suffix = mw.text.trim(mw.ustring.gsub(suffix, v[1], ""))
				result.js = v[2]
				break
			end
		end
	else
		for i, v in ipairs(resources.js) do
			if mw.ustring.match(rest2, v[1]) then
				rest2 = mw.text.trim(mw.ustring.gsub(rest2, v[1], ""))
				result.js = v[2]
				break
			end
		end
	end

	local lastname, name = mw.ustring.match(rest2, "%s*([^,]-)%s*,%s*(.-)%s*$")
	if not lastname then
		if result.familynamefirst then
			lastname, name = mw.ustring.match(rest2, "%s*(%u[%l%d%p]*)%s+(.-)%s*$")
		else
			local prefix2
			name, lastname, prefix2 = mw.ustring.match(rest2, "%s*(.-)%s+((%l[%l%p]%l?)%u[%w%p]-)%s*$")
			if not resources.lastnamePrefixes[prefix2] then
				name, lastname = mw.ustring.match(rest2, "%s*(.-)%s+(%u[%w%p]-)%s*$")
			end
		end
	elseif resources.lastnamePrefixes[prefix1] then
		lastname = prefix1 .. lastname
	elseif resources.lastnamePrefixes[prefix1] == false then
		name = name.." "..mw.text.trim(prefix1)
	end
	
	if not lastname then
		result.lastname = mw.text.trim(rest2)
	else
		result.name = name
		result.lastname = lastname
		result.nameinitials = makeInitials(name)
	end
	
	if #result.lastname == 0 then
		return nil
	end
	
	setmetatable(result, authorMetatable)
	return result
end

local function parseDate(date, month, year, patch)
	local result = {}
	
	-- parse full date
	local y, m, d = false, false, false
	y, m, d = mw.ustring.match(date, "(%d%d%d%d)[%-%s%./](%d%d?)[%-%s%./](%d%d?)")
	
	if y and patch and (date == (y.."-01-01")) then
		result.year = tonumber(y)
		result.month = false
		result.day = false
		return result, true
	end
	
	if not y then
		d, m, y = mw.ustring.match(date, "(%d%d?)[%-%s%.](%d%d?)[%-%s%.](%d%d%d%d)")
		if not y then
			y, m, d = mw.ustring.match(date, "(%d%d%d%d)%s*(%w+)%s*(%d%d?)")
			if not y then
				d, m, y = mw.ustring.match(date, "(%d%d?)%s*(%w+)%s*(%d%d%d%d)")
			end
			if m then
				m = resources.monthparser[mw.ustring.lower(m)]
				if not m then
					y = false
					m = false
					d = false
				end
			end
		end
	end

	if y then
		y = tonumber(y)
		m = tonumber(m)
		d = tonumber(d)
	end
	if y and ((d > 31) or (m > 12) or (d < 1) or (m < 1)) then 
		y = false
		m = false
		d = false
	elseif y then
		result.year = y
		result.month = m
		result.day = d
		return result, false
	end
	
	-- parse year and month
	y, m = mw.ustring.match(date, "(%d%d%d%d)[%-%s%./](%d%d?)")
	if not y then
		m, y = mw.ustring.match(date, "(%d%d?)[%-%s%./](%d%d%d%d)")
		if not y then
			y, m = mw.ustring.match(date, "(%d%d%d%d)%s*(%w+)")
			if not y then
				m, y = mw.ustring.match(date, "(%w+)%s*(%d%d%d%d)")
			end
			if m then
				m = resources.monthparser[mw.ustring.lower(m)]
				if not m then
					y = false
					m = false
				end
			end
		end
	end
	if y then
		y = tonumber(y)
		m = tonumber(m)
	end
	if y and ((m > 12) or (m < 1)) then 
		y = false
		m = false
	elseif y then
		result.year = y
		result.month = m
		return result, false
	end
	
	-- try any method to extract year or month
	if not y then
		y = mw.ustring.match(date, "[%s%p%-–]?(%d%d%d%d)[%s%p%-–]?")
		if y then
			y = tonumber(y)
		end
		if y then
			result.year = y
		end
	end

	if y then
		if not m then
			m = mw.ustring.match(date, "[%s%p%-–]?(%w+)[%s%p%-–]?")
			if m then
				m = resources.monthparser[mw.ustring.lower(m)]
			end
			if m then
				result.month = m
			end
		end
	else
		-- reset only month
		result.month = nil
	end
	
	if y then
		return result, false
	end
end

local function collectAuthors(author, checkForAltFormat)
	if not author then
		return
	end
	
	function decodeEntity(s)
		local result = nil
		local hex = string.match(s, "^&#[xX]([0-9A-Fa-f]+);$")
		if hex then
			result = mw.ustring.char(tonumber(hex, 16))
		else
			local dec = string.match(s, "^&#([0-9]+);$")
			if dec then
				result = mw.ustring.char(tonumber(dec, 10))
			elseif resources.htmlEntities[s] then
				result = mw.ustring.char(resources.htmlEntities[s])
			else
				return string.gsub(s, ";", "\\:")
			end
		end
		
		if result == ";" then
			return "\\:"
		elseif result == "," then
			return "\\."
		elseif result == "\\" then
			return "\\\\"
		else
			return result
		end
	end
	
	local result = {}
	local esc1 = string.gsub(author, "\\", "\\\\")
	local esc2 = string.gsub(esc1, "&#?[a-zA-Z0-9]+;", decodeEntity)
	local splitter = string.match(esc2, ";") and ";" or ","
	local authors = mw.text.split(esc2, splitter.."%s*", false)
	local alt = false
	if (splitter == ",") and checkForAltFormat then
		local altAuthors = {}
		alt = true
		for _, v in ipairs(authors) do
			local n0 = ""
			local s, n = mw.ustring.match(v, "^(%u%l+)%s(%u+)%.?$")
			if not s then
				s, n = mw.ustring.match(v, "^(%u%l+[%s%-–]%u%l+)%s(%u+)%.?$")
			end
			if not s then
				n0, s, n = mw.ustring.match(v, "^(%l%l%l?)%s(%u%l+)%s(%u+)%.?$") -- de, von, van, der etc.
			end
			if not s then
				alt = false
				break
			end
			local initials, _ = mw.ustring.gsub(n, "(%u)", "%1.")
			if #n0 > 0 then
				n0 = " "..n0
			end
			table.insert(altAuthors, s..", "..initials..n0)
		end
		
		if alt then
			authors = altAuthors
			splitter = ";"
		end
	end

	for _, v in ipairs(authors) do
		local author = parseAuthor(v)
		if author then
			table.insert(result, author)
		end
	end

	if #result == 0 then
		return
	end
	
	local check = false
	if alt then
		check = "alt"
	elseif (#result == 2) and (separator == ",") then
		check = true
	end
	
	return result, check
end
	
local function formatAuthors(authors, useDecorations, nextgroup)
	local count = #authors
	if count == 0 then
		return nil
	end
	
	local formatter = function(author)
		local a = author:format(nextgroup)
		local r = author.link and ("[["..author.link.."|"..a.."]]") or a
		local s = ""
		if useDecorations then
			for _, v in ipairs(resources.authorFunc) do
				if 	checkPatterns(author, v.prefixes, v.suffixes) then
					s = v.append
					break
				end
			end
		end
	
		return r..s
	end
	
	if count == 1 then
		return formatter(authors[1])
	end
	
	local result = {}
	table.insert(result, formatter(authors[1]))
	
	if count <= 3 then
		table.insert(result, ", ");
		table.insert(result, formatter(authors[2]))
		if count == 3 then
			table.insert(result, ", ");
			table.insert(result, formatter(authors[3]))
		end
		
		return table.concat(result, "")
	end
	
	local title = {}
	for i = 2, count do
		table.insert(title, authors[i]:towiki())
	end
	table.insert(result, "<span class=\"cite-at-al\" title=\"")
	table.insert(result, table.concat(title, ", "))
	table.insert(result, "\"> i inni</span>")
	return table.concat(result, "")
end

local function collectLanguages(value)
	if value then
		local result = {}
		local values = mw.text.split(value, "%s+")
		for _, v in ipairs(values) do
			if #v > 0 then
				table.insert(result, v)
			end
		end
	
		if #result > 0 then
			return result
		end
	end

	return nil
end

local function splitWikiLink(text)
	local link, description = mw.ustring.match(text, "^%[%[(.-)%|(.-)%]%]$")
	if link then
		return description, link, false
	end
	
	local link = mw.ustring.match(text, "^%[%[(.-)%]%]$")
	if link then
		return link, link, false
	end
	
	local link, description = mw.ustring.match(text, "^%[([hH][tT][tT][pP][sS]?://%S*)%s+(.-)%]$")
	if link then
		return description, false, link
	end
	
	return text, false, false
end

local function detectArchive(url)
	local y, m, d, link = mw.ustring.match(url, "^https?://web%.archive%.org/web/(%d%d%d%d)(%d%d)(%d%d)%d%d%d%d%d%d/(https?://.*)$")
	if y then
		return link, y.."-"..m.."-"..d
	end
	
	return false, false
end

local function isAutoGeneratedUrl(url)
	local address = string.gsub(url, "^https?:", "")
	for k, v in pairs(resources.params) do
		if v.link then
			local prefix = string.gsub(v.link, "^https?:", "")
			if (#address > #prefix) and (string.sub(address, 1, #prefix) == prefix) then
				return true
			end
		end
	end

	return false
end

local function loadCitation(frame, mode)
	local result = {}

	-- copy parameters
	for k, v in pairs(resources.params) do
		if v.used[mode] then
			local value = frame.args[v.name]
			if value then
				value = mw.text.trim(value)
				if #value > 0 then
					result[k] = value
				end
			end
		
			if (v.used[mode] == "!") and not result[k] then
				-- simulate missing mandatory parameter
				result[k] = "{{{"..v.name.."}}}"
				if not result.missing then
					result.missing = v.name
				end
			end
		end
	end

	-- translate some parameters
	local altAuthorParser = false
	if result.journal and result.pmid and result.author and not result.chapterauthor and not result.editor and not result.others then
		altAuthorParser = true
	end
	
	result.chapterauthor, result.chapterComma = collectAuthors(result.chapterauthor, false)
	result.author, result.authorComma = collectAuthors(result.author, altAuthorParser)
	result.lang = collectLanguages(result.lang)
	result.editor, result.editorComma = collectAuthors(result.editor, false)
	result.others, result.othersComma = collectAuthors(result.others, false)

	-- parse main bibliographic date
	if result.date then
		local bibDate = false
		local bibDateHint = false
		local coinsDate = false
		local odnDate = false
		for _, v in ipairs(resources.bibDates) do
			for _, p in ipairs(v.patterns) do
				local bib, c = mw.ustring.gsub(result.date, p, v.show)
				if bib and (c > 0) then
					bibDate = bib
					bibDateHint = v.hint
					if v.coins then
						local cd, cc = mw.ustring.gsub(result.date, p, v.coins)
						if cd and (cc > 0) then
							coinsDate = cd
						end
					end

					if v.odn then
						local od, oc = mw.ustring.gsub(result.date, p, v.odn)
						if od and (oc > 0) then
							odnDate = od
						end
					end
					
					break
				end
				
				if bibDate then
					break
				end
			end
		end

		if bibDate then
			result.date = { bib = bibDate, hint = bibDateHint, coins = coinsDate, odn = odnDate }
		else
			local date, patch = parseDate(result.date or "", false, false, true)
			if date then
				date.coins = (patch and date.year)
					or (date.day and string.format("%04d-%02d-%02d", date.year, date.month, date.day)) 
					or (date.month and string.format("%04d-%02d", date.year, date.month))
					or date.year
				date.odn = date.year
			end
			
			result.date = date
			result.patchCitoidDate = patch
		end
	end

	-- fix other dates
	result.accessdate = parseDate(result.accessdate or "", false, false, false)
	if result.accessdate and not result.accessdate.day then
		result.accessdate = nil
	end

	-- allow more ISBN numbers
	if result.isbn then
		-- TODO allow "(info)" for custom description followed each identifier
		result.isbn = mw.text.split(result.isbn, "%s+")
	end
	
	if result.title then
		local url
		result.title, result.titlelink, url = splitWikiLink(result.title)
		if url or result.titlelink then
			if result.url and (#result.url > 0) and (result.url ~= "{{{url}}}") then
				mw.logObject(result.url, "UNUSED URL")
				result.urlWarning = true
			end
			
			result.url = url
		end
	end
	if result.chapter then
		result.chapter, result.chapterlink, result.chapterurl = splitWikiLink(result.chapter)
	end
	if result.journal then
		local journalAbbr, _ = mw.ustring.gsub(result.journal, "[%.%s]+", " ")
		if mw.ustring.match(journalAbbr, "^[%a%s]+:?[%a%s]+$") then  -- kandydat na skrót powinien mieć tylko litery z opcjonalnymi odstępami i co najwyżej jednym dwukropkiem
			local expandedJournal = mw.loadData("Module:Cytuj/czasopisma")[mw.text.trim(journalAbbr)]
			if expandedJournal then
				result.originalJournal = result.journal
				result.journal = expandedJournal
			end
		end
		result.journal, result.journallink, result.journalurl = splitWikiLink(result.journal)
	end

	if result.url and isAutoGeneratedUrl(result.url) then
		result.rejectedurl = true
		result.url = false
	end
	if result.chapterurl and isAutoGeneratedUrl(result.chapterurl) then
		result.rejectedurl = true
		result.chapterurl = false
	end
	if result.journalurl and isAutoGeneratedUrl(result.journalurl) then
		result.rejectedurl = true
		result.journalurl = false
	end

	if not result.archive and result.url then
		local al, ad = detectArchive(result.url)
		if al then
			result.archive = result.url
			result.url = al
			if ad then result.archived = ad end
		end
	elseif not result.archive and result.chapterurl then
		local al, ad = detectArchive(result.chapterurl)
		if al then
			result.archive = result.chapterurl
			result.chapterurl = al
			if ad then result.archived = ad end
		end
	elseif result.archive and (not (result.url or result.chapterurl) or not result.archived) then
		local al, ad = detectArchive(result.archive)
		if al and not (result.url or result.chapterurl) then result.url = al end
		if ad and not result.archived then result.archived = ad	end
	end

	result.archived = parseDate(result.archived or "", false, false, false)
	if result.archived and not result.archived.day then
		result.archived = null
	end

	if result.edition and result.journal and not result.volume and not result.issue then
		local volume, issue = mw.ustring.match(result.edition, "^%s*([^%(]+)%s+%((.-)%)%s*$");
		if volume then
			result.volume = volume
			result.issue = issue
			result.edition = nil
		end
	end

	if result.pmc and (#result.pmc > 3) and (mw.ustring.sub(result.pmc, 1, 3) == "PMC") then
		result.pmc = mw.ustring.sub(result.pmc, 4, #result.pmc)
	end

	if result.accessKind then
		result.accessKind = access.choice[result.accessKind]
		result.unknownAccess = not result.accessKind
	else
		result.accessKind = (result.pmc and "open")
			or access.doi[doiPrefix]
			or access.journals[result.journal]
	end

	if result.doi then
		result.doi = mw.text.split(result.doi, '%s+', false)
		for i, v in ipairs(result.doi) do
			local doiPrefix
			local doiSuffix
			doiPrefix, doiSuffix = mw.ustring.match(v, "^10%.([^/]+)/(.+)$")
			if (doiPrefix == "2307") and not result.jstor then
				result.jstor = doiSuffix
			end
			if not result.accessKind and not result.unknownAccess then
				result.accessKind = access.doi[doiPrefix]
			end
		end
	end

	-- return collected parameters if there is any	
	for k, v in pairs(result) do
		return result
	end
	
	-- there are no supported parameters
	return nil
end

local function prepareOdnIdentifier(data)
	if not data.odn or (#data.odn == 0) or (data.odn == "nie") then
		return nil
	end

	data.diferentiator = mw.ustring.match(data.odn, "^([a-z])$") or false
	
	if data.odn ~= "tak" and not data.diferentiator then
		-- TODO return only CITEREF...
		return data.odn
	end
	
	local authors = data.chapterauthor or data.author or data.editor
	if not authors then
		-- required custom identifier
		return nil
	end
	
	return "CITEREF"
		.. (authors[1] and (authors[1].lastname or authors[1].exact) or "")
		.. (authors[2] and (authors[2].lastname or authors[2].exact) or "")
		.. (authors[3] and (authors[3].lastname or authors[3].exact) or "")
		.. (authors[4] and (authors[4].lastname or authors[4].exact) or "")
		.. (data.date and data.date.odn or "")
		.. (data.diferentiator or "")
end

local function bookCOinS(data)
	local authors = data.chapterauthor or data.author
	local result = {}
	result["rft_val_fmt"] = "info:ofi/fmt:kev:mtx:book"
	if data.chapter and (#data.chapter > 0) then
		result["rft.gengre"] = "bookitem"
		result["rft.atitle"] = plainText(data.chapter)
		result["rft.btitle"] = plainText(data.title)
	elseif data.work and (#data.work > 0) then
		result["rft.gengre"] = "bookitem"
		result["rft.atitle"] = plainText(data.title)
		result["rft.btitle"] = plainText(data.work)
	else
		result["rft.btitle"] = plainText(data.title)
		result["rft.gengre"] = "book"
	end
	if authors then
		if authors[1].lastname then result["rft.aulast"] = authors[1].lastname end
		if authors[1].name then result["rft.aufirst"] = authors[1].name end
		if authors[1].exact then result["rft.au"] = authors[1].exact end
	end
	if data.date and data.date.coins then
		result["rft.date"] = data.date.coins
	end
	if data.edition then result["rft.edition"] = data.edition end
	if data.publisher then result["rft.pub"] = data.publisher end
	if data.place then result["rft.place"] = data.place end
	if data.pages then result["rft.pages"] = data.pages end
	if data.isbn then result["rft.isbn"] = data.isbn[1] end
	if data.issn then result["rft.issn"] = data.issn end
	
	local params = {
		"ctx_ver=Z39.88-2004",
		mw.uri.buildQueryString(result),
	}

	if data.oclc then table.insert(params, mw.uri.buildQueryString( {rft_id = "info:oclcnum/"..data.oclc})) end
	if data.doi then
		for _, v in ipairs(data.doi) do
			table.insert(params, mw.uri.buildQueryString( {rft_id = "info:doi/"..v}))
		end
	end
	if data.url then table.insert(params, mw.uri.buildQueryString( {rft_id = data.url})) end
	if data.pmid then table.insert(params, mw.uri.buildQueryString( {rft_id = "info:pmid/"..data.pmid})) end
	if data.lccn then table.insert(params, mw.uri.buildQueryString( {rft_id = "info:lccn/"..data.lccn})) end
		
	local coinsData = table.concat(params, "&")
	return coinsData;
end

local function journalCOinS(data)
	local result = {}
	result["rft_val_fmt"] = "info:ofi/fmt:kev:mtx:journal"
	local gengre = (data.arxiv and (#data.arxiv > 0)) and "preprint" or "article"
	result["rft.gengre"] = data.title and gengre or "journal"
	if data.title then result["rft.atitle"] = plainText(data.title) end
	result["rft.jtitle"] = plainText(data.journal)
	if data.chapter then result["rft.atitle"] = plainText(data.chapter) end
	if data.date and data.date.coins then
		result["rft.date"] = data.date.coins
	end
	if data.title and author then
		if author[1].lastname then result["rft.aulast"] = author[1].lastname end
		if author[1].name then result["rft.aufirst"] = author[1].name end
		if author[1].exact then result["rft.au"] = author[1].exact end
	end
	if data.volume then result["rft.volume"] = data.volume end
	if data.issue then result["rft.edition"] = data.issue end
	if data.publisher then result["rft.pub"] = data.publisher end
	if data.place then result["rft.place"] = data.place end
	if data.pages then result["rft.pages"] = data.pages end
	if data.issn then result["rft.issn"] = data.issn end
	
	local params = {
		"ctx_ver=Z39.88-2004",
		mw.uri.buildQueryString(result),
	}

	if data.pmid then table.insert(params, mw.uri.buildQueryString( {rft_id = "info:pmid/"..data.pmid})) end
	if data.pmc then table.insert(params, mw.uri.buildQueryString( {rft_id = "info:pmc/"..data.pmc})) end
	if data.doi then
		for _, v in ipairs(data.doi) do
			table.insert(params, mw.uri.buildQueryString( {rft_id = "info:doi/"..v}))
		end
	end
	if data.url then table.insert(params, mw.uri.buildQueryString( {rft_id = data.url})) end
		
	local coinsData = table.concat(params, "&")
	return coinsData;
end

local function webCOinS(data)
	local result = {}
	result["rft_val_fmt"] = "info:ofi/fmt:kev:mtx:journal"
	result["rft.gengre"] = "unknown"
	if data.title then result["rft.atitle"] = plainText(data.title) end
	result["rft.jtitle"] = plainText(data.published)
	if data.date and data.date.coins then
		result["rft.date"] = data.date.coins
	end
	if data.title and author then
		if author[1].lastname then result["rft.aulast"] = author[1].lastname end
		if author[1].name then result["rft.aufirst"] = author[1].name end
		if author[1].exact then result["rft.au"] = author[1].exact end
	end
	local params = {
		"ctx_ver=Z39.88-2004",
		mw.uri.buildQueryString(result),
	}

	if data.url then table.insert(params, mw.uri.buildQueryString( {rft_id = data.url})) end
		
	local coinsData = table.concat(params, "&")
	return coinsData;
end

local function COinS(data, coinsFormat)
	if (coinsFormat == "info:ofi/fmt:kev:mtx:book") and data.title and (#data.title > 0) then
		-- title is mandatory element for books
		return bookCOinS(data)
	elseif coinsFormat == "info:ofi/fmt:kev:mtx:journal" and data.journal and (#data.journal > 0) and (not data.published or (data.journal ~= data.published)) then
		-- journal title is mandatory element for journals
		return journalCOinS(data)
	elseif coinsFormat == "info:ofi/fmt:kev:mtx:journal" and data.published and (#data.published > 0) then
		return webCOinS(data)
	elseif data.title and (#data.title > 0) then
		-- treat web or unrecognized citations as book
		return bookCOinS(data)
	else
		return false
	end
end

local function Cite(p, mode)
	-- debug helper
	if p.args[3] then mw.log(p.args[3]) end
	local customMode = mode

	-- try to determine type basing on passed parameters
	local coinsFormat = resources.COinS[mode]
	if not mode then
		mode, coinsFormat = determineMode(p)
	end
	
	local data = loadCitation(p, mode)
	if not data then
		return resources.categories.empty
	end
	if data.missing then
		-- do not produce any COiNS info
		-- if some mandatory argument is missing
		coinsFormat = false
	end

	local builder = mw.html.create("span")
	builder
		:addClass("citation")
		:attr("id", prepareOdnIdentifier(data))
		:wikitext(access.render[data.accessKind], ' ')

	local needDot = false
	local nextAuthorGroup = false
	if data.title then
		
		if data.chapter then
			local authors = data.editor and data.author or data.chapterauthor
			if authors then
				builder:wikitext(formatAuthors(authors, false, nextAuthorGroup), ", ")
				nextAuthorGroup = true
			end
			
			local title = softNoWiki(data.chapter)
			if data.chapterurl then
				builder:wikitext("[", escapeUrl((not data.url and data.archive) and data.archive or data.chapterurl), " ''", title, "'']")
			elseif data.chapterlink then
				builder:wikitext("[[", data.chapterlink, "|''", title, "'']]")
			else
				builder:wikitext("''", title, "''")
			end
			
			if data.format then
				builder:wikitext(" &#x5B;", data.format, "&#x5D;")
			end

			builder:wikitext(" [w:] ")
		end

		local authors = false
		local editor = false
		if not data.chapter and data.author then
			authors = data.author
		else
			authors = data.editor or data.author
			editor = data.editor
		end
		if authors then
			builder:wikitext(formatAuthors(authors, not (editor or false), nextAuthorGroup))
			nextAuthorGroup = true
			if editor then
				builder:wikitext(" (red.)")
			end
			builder:wikitext(", ")
		end
		if customMode and data.authorextra then
			builder:wikitext(data.authorextra, ", ")
		end
	
		local title = softNoWiki(data.title)
		if data.url then
			builder:wikitext("[", escapeUrl(data.archive or data.url), " ''", title, "'']")
		elseif data.titlelink then
			builder:wikitext("[[", data.titlelink, "|''", title, "'']]")
		else
			builder:wikitext("''", title, "''")
		end
		if not data.chapter and data.format then
			builder:wikitext(" &#x5B;", data.format, "&#x5D;")
			needDot = true
		elseif not mw.ustring.match(plainText(title), "%p$") then
			needDot = true
		end

		local showmediatype = data.mediatype and (#data.mediatype > 0)
		if showmediatype then
			builder:wikitext(" &#x5B;", data.mediatype, "&#x5D;")
			needDot = true
		end

		if not editor and data.editor then
			builder:wikitext(", ", formatAuthors(data.editor, false, true), " (red.)")
		end
		
		if data.others then
			builder:wikitext(", ", formatAuthors(data.others, true, true))
			needDot = true
		end
	end
	
	if data.work then
		builder:wikitext(" [w:] ", data.work, (not data.mediatype and data.url) and " [online]" or "")
		needDot = true
	end

	if data.journal and (not data.published or (data.journal ~= data.published)) then
		--builder:wikitext((data.title or data.work) and " [w:] „" or "„")
		builder:wikitext((data.title or data.work) and ", „" or "„")
		local title = softNoWiki(data.journal)
		if data.journalurl then
			builder:wikitext("[", escapeUrl(data.journalurl), " ", title, "]")
		elseif data.journallink then
			builder:wikitext("[[", data.journallink, "|", title, "]]")
		else
			builder:wikitext(title)
		end
		builder:wikitext("”")
		needDot = true
	end

	if data.responsibility then
		builder:wikitext(", ", data.responsibility)
		needDot = true
	end

	if data.edition then
		builder:wikitext(data.journal and ", " or ", wyd. ", data.edition)
		needDot = true
	end
	
	if data.volume then
		builder:wikitext(data.journal and ", " or ", t. ", data.volume)
		needDot = true
	end
	
	if data.series or data.issue then
		builder:wikitext(" (", data.series or "", (data.series and data.issue) and "; " or "", data.issue or "", ")")
		needDot = true
	end
	
	if data.description and (#data.description > 0) then
		builder:wikitext(", ", data.description)
		needDot = true
	end

	if data.published and not data.publisher then
		builder:wikitext(", ", data.published)
		needDot = true
	end

	local place = false
	if data.place then
		builder:wikitext(", ", data.place)
		needDot = true
		place = true
	end
	if data.publisher then
		builder:wikitext(place and ": " or ", ", data.publisher)
		needDot = true
		place = false
	end
	if data.date then
		builder:wikitext(place and " " or ", ")
		local shortDate = data.journal and (data.doi or data.pmid or data.pmc)
		if data.date.bib and data.date.hint then
			builder:tag("span"):attr("title", data.date.hint):wikitext(data.date.bib)
		elseif data.date.bib  then
			builder:wikitext(data.date.bib)
		elseif data.date.day and shortDate then
			builder:tag("span"):attr("title", tostring(data.date.day).." "..resources.months[data.date.month].d.." "..tostring(data.date.year)):wikitext(data.date.year)
		elseif data.date.month and shortDate then
			builder:tag("span"):attr("title", resources.months[data.date.month].m.." "..tostring(data.date.year)):wikitext(data.date.year)
		elseif data.date.day then
			builder:wikitext(place and ", " or "", tostring(data.date.day), " ", resources.months[data.date.month].d, " ", tostring(data.date.year))
		elseif data.date.month then
			builder:wikitext(place and ", " or "", resources.months[data.date.month].m, " ", tostring(data.date.year))
		else
			builder:wikitext(data.date.year)
		end
		builder:wikitext(data.diferentiator or "")
		needDot = true
	end

	if data.p and #data.p > 0 then
		local isNonStandardPageNumber = mw.ustring.match(data.p, "[^%s0-9,%-–]")
		builder:wikitext(isNonStandardPageNumber and ", " or ", s. ", data.p)
		needDot = true
	end
	
	if data.doi then
		local separator = "&nbsp;"
		builder:addClass("doi"):wikitext(", [[w:pl:DOI (identyfikator cyfrowy)|DOI]]:")
		for _, v in ipairs(data.doi) do
			builder:wikitext(separator, "[", resources.params.doi.link, mw.uri.encode(v), " ", softNoWiki(v), "]")
			separator = ", "
		end
		needDot = true
	end
	
	if data.isbn then
		for i,v in ipairs(data.isbn) do
			builder:wikitext(", ")
			require("Module:ISBN").link(builder, v)
		end

		needDot = true
	end

	if data.lccn then
		builder:wikitext(", [[w:pl:Biblioteka Kongresu|LCCN]] [", resources.params.lccn.link, mw.uri.encode(data.lccn), " ", data.lccn, "]")
		needDot = true
	end
	
	if data.issn then
		builder:tag("span"):addClass("issn"):wikitext(", [[w:International Standard Serial Number|ISSN]] [", resources.params.issn.link, data.issn, " ", data.issn, "]")
		needDot = true
	end
	
	if data.pmid then
		builder:addClass("pmid"):wikitext(", [[w:PMID|PMID]]:&nbsp;[", resources.params.pmid.link, data.pmid, " ", data.pmid, "]")
		needDot = true
	end
	
	if data.pmc then
		builder:addClass("pmc"):wikitext(", [[w:PMCID|PMCID]]:&nbsp;[", resources.params.pmc.link, data.pmc, "/ PMC", data.pmc, "]")
		needDot = true
	end
	
	if data.bibcode then
		builder:wikitext(", [[w:Bibcode|Bibcode]]:&nbsp;[", resources.params.bibcode.link, data.bibcode, " ", data.bibcode, "]")
		needDot = true
	end
	
	if data.oclc then
		builder:wikitext(", [[w:Online Computer Library Center|OCLC]]&nbsp;[", resources.params.oclc.link, mw.uri.encode(data.oclc), " ", data.oclc, "]")
		needDot = true
	end
	
	if data.arxiv then
		builder:wikitext(", [[w:arXiv|arXiv]]:")
		local eprint, class = mw.ustring.match(data.arxiv, "^(%S+)%s+%[([^%[%]]+)%]$")
		if eprint then
			builder:wikitext("[", resources.params.arxiv.link, eprint, " ", eprint, "] &#x5B;[//arxiv.org/archive/", class, " ", class, "]&#x5D;" )
		else
			builder:wikitext("[", resources.params.arxiv.link, data.arxiv, " ", data.arxiv, "]" )
		end
		needDot = true
	end
	
	if data.jstor then
		builder:tag("span"):addClass("jstor"):wikitext(", [[w:JSTOR|JSTOR]]:&nbsp;[", resources.params.jstor.link, data.jstor, " ", data.jstor, "]")
		needDot = true
	end
	
	if data.id then
		builder:wikitext(", ", data.id)
		needDot = true
	end
	
	if data.accessdate then
		builder:tag("span"):addClass("accessdate"):wikitext(" [dostęp ", string.format("%04d-%02d-%02d", data.accessdate.year, data.accessdate.month, data.accessdate.day), "]")
		needDot = true
	end
	
	if (data.url or data.chapterurl) and data.archive then
		builder:wikitext(" [zarchiwizowane z [", escapeUrl(data.url or data.chapterurl), " adresu]")
		if data.archived and data.archived.day then
			builder:wikitext(" ", string.format("%04d-%02d-%02d", data.archived.year, data.archived.month, data.archived.day))
		end
		builder:wikitext("]")
		needDot = true
	end
	
	if data.quotation then
		builder:wikitext(", Cytat: ", data.quotation)
		needDot = true
	end

	local coinsData = COinS(data, coinsFormat)
	if coinsData then
		builder:tag("span"):addClass("Z3988"):attr("title",coinsData):css("display","none"):wikitext("&nbsp;")
	end
	
	if data.lang then
		local languages = require("Module:Lang").lang({args = data.lang})
		builder:wikitext(" ", languages)
		needDot = true
	end

	if needDot then
		builder:wikitext(".")
	end

	-- categories
	local addCategories = mw.title.getCurrentTitle().namespace == 0
	local problems = {}
	if not customMode and (mode == 1) then
		builder:wikitext(resources.categories.undetermined)
		table.insert(problems, "???")
	end
	if data.publisher and data.published then
		table.insert(problems, "p?")
		if addCategories then
			table.insert(problems, resources.categories.unusedPublished)
		end
	end
	if data.journal and data.published and (data.journal == data.published) then
		table.insert(problems, "j?")
		if addCategories then
			table.insert(problems, resources.categories.sameJournalAndPublished)
		end
	end
	
	if not data.url and not data.chapterurl then
		builder:addClass("nourl")
	end
	
	local missing = false
	local needurl = ((resources.params.published.used[mode] == "*") and data.published) or (resources.params.url.used[mode] == "*")
	if data.missing then
		-- usually missing title, this is the first check for mandatory arguments
		table.insert(problems, data.missing)
		missing = true
	elseif needurl and not data.url and not data.chapterurl and not data.arxiv then
		-- build in support for missing external link for page citation
		table.insert(problems, resources.params.url.name)
		missing = true
	else
		-- any other missing value (first catch)
		for k, v in pairs(resources.params) do
			if (v.used[mode] == "!") and (not data[k] or (#data[k] == 0)) then
				table.insert(problems, v.name)
				missing = true
				break
			end
		end
	end

	if missing and addCategories then
		builder:wikitext(string.format(resources.categories.missingArg, resources.modes[mode]))
	end
	if data.chapterComma or (data.authorComma == true) or data.editorComma or data.othersComma or data.othersbookvolumeComma then
		table.insert(problems, "!!!")
		if addCategories then
			builder:wikitext(resources.categories.suspectedComma)
		end
	end
	if data.authorComma == "alt" then
		table.insert(problems, "a?")
		if addCategories then
			builder:wikitext(resources.categories.altAuthor)
		end
	end
	if data.originalJournal then
		table.insert(problems, "c?")
		if addCategories then
			builder:wikitext(resources.categories.altJournal)
		end
	end
	local citewiki = (data.journal and mw.ustring.match(data.journal, "[Ww]ikipedia"))
		or (data.publisher and mw.ustring.match(data.publisher, "[Ww]ikipedia"))
		or (data.published and mw.ustring.match(data.published, "[Ww]ikipedia"))
		or (data.url and mw.ustring.match(data.url, "%.wikipedia%.org"))
	if citewiki then
		table.insert(problems, "wiki?")
		if addCategories then
			builder:wikitext(resources.categories.wiki)
		end
	end
	if data.unknownAccess then
		table.insert(problems, "dostęp?")
		if addCategories then
			builder:wikitext(resources.categories.unknownAccess)
		end
	end
	if data.rejectedurl then
		table.insert(problems, "url")
		if addCategories then
			builder:wikitext(resources.categories.rejectedUrl)
		end
	end
	if data.urlWarning then
		table.insert(problems, "Url")
		if addCategories then
			builder:wikitext(resources.categories.unusedUrl)
		end
	end
	if data.patchCitoidDate then
		table.insert(problems, "1 stycznia")
	end

	if #problems > 0 then
		local info = builder:tag("span"):addClass("problemy-w-cytuj")
		if addCategories then
			info:css("display","none")
		else
			info:css("color", "red")
		end
		
		info:wikitext(table.concat(problems,", "))
	end
	
	return builder:done()
end

return {
	
auto = function(frame)
	return Cite(frame:getParent(), nil)
end,

custom = function(frame)
	return Cite(frame, 1)
end,

}