Moduł:Cytuj

Ze SileSłownik
Idź do: nawigacyjŏ, szukej

Dokumentacja dla tego modułu może zostać utworzona pod nazwą Moduł:Cytuj/opis

local resources = mw.loadData("Moduł:Cytuj/dane")
local access = mw.loadData("Moduł:Cytuj/dostymp")

local function checkUri(uri)
	local urilen = #uri
	for _,v in ipairs(resources.supportedUriSchemas) do
		if (#v < urilen) and (string.lower(string.sub(uri,1, #v)) == v) then
			return not string.match(uri, '%s')
		end
	end
end

local function softNoWiki(text)
	local result, count = string.gsub(text, "['%[%]{|}\"]", { ["\""] = "&#x22;", ["'"] = "&#x27;", ["["] = "&#x5B;", ["]"] = "&#x5D;", ["{"] = "&#x7B;", ["|"] = "&#x7C;", ["}"] = "&#x7D;"})
	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 first(data)
	return type(data) == "table" and data[1] or data
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. ") -- ôstŏw poczōntki słōw (jedna litera + ôpcyjōnalne 'h' pō nij)
	nameinitials = mw.ustring.gsub(nameinitials, "%f[%w]%l%.%s", "")               -- wyciep inicjały z małych liter
	nameinitials = mw.ustring.gsub(nameinitials, "([^C%W])[Hh]%.?%s", "%1. ")      -- wyciep druge 'h', jeźli nie zaczynŏ sie na 'C'
	nameinitials = mw.ustring.gsub(nameinitials, "(%u[Hh]?)[%.%s]*", "%1.")        -- dodej kropki, co brakujōm, i wyciep niyprzidajne spacyje
	return mw.text.trim(nameinitials)
end

local function fixInitials(name)
	local result, _ = mw.ustring.gsub(name, "^(%uh?)%.?%s+(%uh?)%.", "%1.%2.") -- poprŏw inicjały na poczōntku
	result, _ = mw.ustring.gsub(result, "%f[%a](%uh?)%.%s+(%uh?)%.", "%1.%2.") -- poprŏw inicjały we postrzodku
	result, _ = mw.ustring.gsub(result, "%f[%a](%uh?)%.%s+(%uh?)%.", "%1.%2.") -- poprŏw dalsze inicjały we postrzodku
	return result
end

local function parseAuthor(author)
	
	local result = {}
	
	if string.match(author, "\127") then -- wpisy z <nowiki> niy sōm 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 = fixInitials(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 findUtf8CharAt(text, at)
		local back = false
		if at < 0 then
			at = #text + at + 1
			back = true
		end
		
		while at > 1 do
			local b = string.byte(text, at, at)
			if (b < 128) or (b >= 192) then
				break
			end
			
			at = at - 1
		end
		
		return back and at - #text - 1 or at
	end
	
	local etal = false
	local authorTail = #author <= 50 and author or string.sub(author, findUtf8CharAt(author, -50))
	for i, p in ipairs(resources.etalPatterns) do
		local a, e = string.match(authorTail, p)
		if a then
			author = string.sub(author, 1, #author-#e)
			etal = e
			break
		end
	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 authorHead = #author <= 500 and author or string.sub(author, 1, findUtf8CharAt(author, 500) - 1)
	local result = {}
	local esc1 = string.gsub(authorHead, "\\", "\\\\")
	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 nth = false
	local count = #authors
	if (#authorHead < #author) and (count > 4) then
		if count > 5 then
			table.remove(authors, count)
			count = count - 1
		end
		
		local at, _ = string.find(authorHead, authors[count], 1, true)
		nth = string.sub(author, at)
		table.remove(authors, count)
		count = count - 1
	end
	
	local alt = false
	if (splitter == ",") and checkForAltFormat then
		local altAuthors = {}
		alt = true
		for i, 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 i, 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 { items = result, more=nth, comma = check, etal = etal, separator=splitter.." " }
end
	
local function formatAuthors(authors, useDecorations, nextgroup, etalForm)
	local count = #authors.items
	if count == 0 then
		return nil, false
	end
	
	local suffix = function(author)
		if useDecorations then
			for _, v in ipairs(resources.authorFunc) do
				if checkPatterns(author, v.prefixes, v.suffixes) then
					return v.append
				end
			end
		end
		
		return ""
	end
	
	local formatter = function(author)
		local a = author:format(nextgroup)
		local r = author.link and ("[["..author.link.."|"..a.."]]") or a
		return r..suffix(author)
	end
	
	if count == 1 then
		local a1 = formatter(authors.items[1])
		local etal = authors.etal and (etalForm or " i inni") or ""
		return a1..etal, false
	end
	
	local result = {}
	table.insert(result, formatter(authors.items[1]))
	if not authors.etal and (count <= 3) then
		table.insert(result, ", ");
		table.insert(result, formatter(authors.items[2]))
		if count == 3 then
			table.insert(result, ", ");
			table.insert(result, formatter(authors.items[3]))
		end
		
		return table.concat(result, ""), false
	end

	local title = {}
	for i = 1, count do
		table.insert(title, authors.items[i]:towiki()..suffix(authors.items[i]))
	end
	if authors.more then
		table.insert(title, authors.more)
	end
	
	table.insert(result, "<span class=\"cite-at-al\" title=\"")
	table.insert(result, table.concat(title, authors.separator))
	if authors.etal then
		table.insert(result, etalForm or " i inni")
	end
	table.insert(result, "\">")
	table.insert(result, etalForm or " i inni")
	table.insert(result, "</span>")
	return table.concat(result, ""), true
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, "^%[(%S*)%s+(.-)%]$")
	if link and checkUri(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
	
	local y, m, d, link = mw.ustring.match(url, "^https?://webarchive%.nationalarchives%.gov%.uk/(%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 links = type(v.link) == "table" and v.link or { v.link }
			for _, vlink in ipairs(links) do
				local prefix = string.gsub(vlink, "^https?:", "")
				if (#address > #prefix) and (string.sub(address, 1, #prefix) == prefix) then
					return true
				end
			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
	
	-- check url argument
	if result.url then
		if not checkUri(result.url) then
			local unstrip = mw.text.unstripNoWiki( result.url )
			result.url = false
			if unstrip then
				result.urlnowiki = checkUri(unstrip)
			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 = collectAuthors(result.chapterauthor, false)
	result.author = collectAuthors(result.author, altAuthorParser)
	result.lang = collectLanguages(result.lang)
	result.editor = collectAuthors(result.editor, false)
	result.others = 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
			elseif result.date then
				result.badDate = true
			end
			
			result.date = date
			result.patchCitoidDate = patch
		end
	end

	-- fix other dates
	if result.accessdate then
		result.accessdate = parseDate(result.accessdate or "", false, false, false)
		if result.accessdate and not result.accessdate.day then
			result.badAccessDate = true
			result.accessdate = nil
		elseif not result.accessdate then
			result.badAccessDate = true
		end
	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
				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 mŏ mieć ino litery z ôpcyjōnalnymi ôdstympami i nojwyżyj jednym dwukropkym
			local expandedJournal = mw.loadData("Moduł:Cytuj/cajtōngi")[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.journal and not result.journallink and not result.journalurl and not result.title and result.url then
		result.journalurl = result.url
		result.url = false
	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.archived then
		local al, ad = detectArchive(result.archive)
		if ad and not result.archived then result.archived = ad	end
	end

	if result.archived then
		result.archived = parseDate(result.archived or "", false, false, false)
		if result.archived and not result.archived.day then
			result.badArchivedDate = true
			result.archived = null
		elseif not result.archived then
			result.badArchivedDate = true
		end
	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.items[1] and (authors.items[1].lastname or authors.items[1].exact) or "")
		.. (authors.items[2] and (authors.items[2].lastname or authors.items[2].exact) or "")
		.. (authors.items[3] and (authors.items[3].lastname or authors.items[3].exact) or "")
		.. (authors.items[4] and (authors.items[4].lastname or authors.items[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.items[1].lastname then result["rft.aulast"] = authors.items[1].lastname end
		if authors.items[1].name then result["rft.aufirst"] = authors.items[1].name end
		if authors.items[1].exact then result["rft.au"] = authors.items[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
				local list, etal = formatAuthors(authors, false, nextAuthorGroup, nil)
				builder:wikitext(list, ", ")
				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
			local list, etal = formatAuthors(authors, not (editor or false), nextAuthorGroup, editor and " i inni red." or nil)
			builder:wikitext(list)
			nextAuthorGroup = true
			if editor and not etal and not authors.etal 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 or (not data.chapterurl and data.archive) 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
			local list, etal = formatAuthors(data.editor, false, true, " i inni red.")
			builder:wikitext(", ", list, (etal or data.editor.etal) and "" or " (red.)")
		end
		
		if data.others then
			local list, etal = formatAuthors(data.others, true, true, nil)
			builder:wikitext(", ", list)
			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 ", " 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
		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(", [[DOI (identyfikator cyfrowy)|DOI]]:")
		local doiLink = first(resources.params.doi.link)
		for _, v in ipairs(data.doi) do
			builder:wikitext(separator, "[", doiLink, 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("Moduł:ISBN").link(builder, v)
		end

		needDot = true
	end

	if data.lccn then
		builder:wikitext(", [[Biblioteka Kongresu|LCCN]] [", first(resources.params.lccn.link), mw.uri.encode(data.lccn), " ", data.lccn, "]")
		needDot = true
	end
	
	if data.issn then
		builder:tag("span"):addClass("issn"):wikitext(", [[International Standard Serial Number|ISSN]] [", first(resources.params.issn.link), data.issn, " ", data.issn, "]")
		needDot = true
	end
	
	if data.pmid then
		builder:addClass("pmid"):wikitext(", [[PMID]]:&nbsp;[", first(resources.params.pmid.link), data.pmid, " ", data.pmid, "]")
		needDot = true
	end
	
	if data.pmc then
		builder:addClass("pmc"):wikitext(", [[PMCID]]:&nbsp;[", first(resources.params.pmc.link), data.pmc, "/ PMC", data.pmc, "]")
		needDot = true
	end
	
	if data.bibcode then
		builder:wikitext(", [[Bibcode]]:&nbsp;[", first(resources.params.bibcode.link), data.bibcode, " ", data.bibcode, "]")
		needDot = true
	end
	
	if data.oclc then
		builder:wikitext(", [[Online Computer Library Center|OCLC]]&nbsp;[", first(resources.params.oclc.link), mw.uri.encode(data.oclc), " ", data.oclc, "]")
		needDot = true
	end
	
	if data.arxiv then
		builder:wikitext(", [[arXiv]]:")
		local eprint, class = mw.ustring.match(data.arxiv, "^(%S+)%s+%[([^%[%]]+)%]$")
		if eprint then
			builder:wikitext("[", first(resources.params.arxiv.link), eprint, " ", eprint, "] &#x5B;[//arxiv.org/archive/", class, " ", class, "]&#x5D;" )
		else
			builder:wikitext("[", first(resources.params.arxiv.link), data.arxiv, " ", data.arxiv, "]" )
		end
		needDot = true
	end
	
	if data.jstor then
		builder:tag("span"):addClass("jstor"):wikitext(", [[JSTOR]]:&nbsp;[", first(resources.params.jstor.link), data.jstor, " ", data.jstor, "]")
		needDot = true
	end
	
	if data.ol then
		builder:tag("span"):addClass("open-library"):wikitext(", [[Open Library|OL]]:&nbsp;[", first(resources.params.ol.link), data.ol, " ", data.ol, "]")
		needDot = true
	end
	
	if data.id then
		builder:wikitext(", ", data.id)
		needDot = true
	end
	
	if data.accessdate then
		builder:tag("span"):addClass("accessdate"):wikitext(" [dostymp ", string.format("%04d-%02d-%02d", data.accessdate.year, data.accessdate.month, data.accessdate.day), "]")
		needDot = true
	end
	
	if data.archive then
		builder:wikitext(" [zarchiwizowane")
		if (data.url or data.chapterurl) then
			builder:wikitext(" z [", escapeUrl(data.url or data.chapterurl), " adresy]")
		end
		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("Moduł: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) or (not data.title and not data.journalurl) then
		builder:addClass(data.urlnowiki and "urlnowiki" or "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 and not data.archive 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.chapterauthor and data.chapterauthor.comma)
	or (data.author and (data.author.comma == true))
	or (data.editor and data.editor.comma)
	or (data.others and data.others.comma) then
		table.insert(problems, "!!!")
		if addCategories then
			builder:wikitext(resources.categories.suspectedComma)
		end
	end
	if data.author and (data.author.comma == "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, "dostymp?")
		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 data.badDate then
		table.insert(problems, "data?")
	end
	if data.badAccessDate then
		table.insert(problems, "data dostympu?")
	end
	if data.badArchivedDate then
		table.insert(problems, "zarchiwizowano?")
	end
	if addCategories and (data.badDate or data.badAccessDate or data.badArchiveDate) then
		builder:wikitext(resources.categories.badDate)
	end
	if (data.author and data.author.etal)
	or (data.chapterauthor and data.chapterauthor.etal)
	or (data.editor and data.editor.etal)
	or (data.others and data.others.etal) then
		table.insert(problems, "i inni")
		if addCategories then
			builder:wikitext(resources.categories.etal)
		end
	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,

}