PhotosLocation

Permanently protected module
From Wikipedia, the free encyclopedia


-- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].



require("strict")

local p = {}

local arg = ...

local i18n



local function loadI18n(aliasesP, frame)

	local title



	if frame then

		-- current module invoked by page/template, get its title from frame

		title = frame:getTitle()

	else

		-- current module included by other module, get its title from ...

		title = arg

	end



	if not i18n then

		i18n = require(title .. "/i18n").init(aliasesP)

	end

end



p.claimCommands = {

	property   = "property",

	properties = "properties",

	qualifier  = "qualifier",

	qualifiers = "qualifiers",

	reference  = "reference",

	references = "references"

}



p.generalCommands = {

	label       = "label",

	title       = "title",

	description = "description",

	alias       = "alias",

	aliases     = "aliases",

	badge       = "badge",

	badges      = "badges"

}



p.flags = {

	linked        = "linked",

	short         = "short",

	raw           = "raw",

	multilanguage = "multilanguage",

	unit          = "unit",

	-------------

	preferred     = "preferred",

	normal        = "normal",

	deprecated    = "deprecated",

	best          = "best",

	future        = "future",

	current       = "current",

	former        = "former",

	          = "edit",

	editAtEnd     = "edit@end",

	mdy           = "mdy",

	single        = "single",

	sourced       = "sourced"

}



p.args = {

	eid  = "eid",

	page = "page",

	date = "date",

	globalSiteId = "globalSiteId"

}



local aliasesP = {

	coord                   = "P625",

	-----------------------

	image                   = "P18",

	author                  = "P50",

	authorNameString        = "P2093",

	publisher               = "P123",

	importedFrom            = "P143",

	wikimediaImportURL      = "P4656",

	statedIn                = "P248",

	pages                   = "P304",

	language                = "P407",

	hasPart                 = "P527",

	publicationDate         = "P577",

	startTime               = "P580",

	endTime                 = "P582",

	chapter                 = "P792",

	retrieved               = "P813",

	referenceURL            = "P854",

	sectionVerseOrParagraph = "P958",

	archiveURL              = "P1065",

	title                   = "P1476",

	formatterURL            = "P1630",

	quote                   = "P1683",

	shortName               = "P1813",

	definingFormula         = "P2534",

	archiveDate             = "P2960",

	inferredFrom            = "P3452",

	typeOfReference         = "P3865",

	column                  = "P3903",

	subjectNamedAs          = "P1810",

	wikidataProperty        = "P1687",

	publishedIn             = "P1433"

}



local aliasesQ = {

	percentage              = "Q11229",

	prolepticJulianCalendar = "Q1985786",

	citeWeb                 = "Q5637226",

	citeQ                   = "Q22321052"

}



local parameters = {

	property  = "%p",

	qualifier = "%q",

	reference = "%r",

	alias     = "%a",

	badge     = "%b",

	separator = "%s",

	general   = "%x"

}



local formats = {

	property              = "%p[%s][%r]",

	qualifier             = "%q[%s][%r]",

	reference             = "%r",

	propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]",

	alias                 = "%a[%s]",

	badge                 = "%b[%s]"

}



local hookNames = {              -- {level_1, level_2}

	parameters.property         = {"getProperty"},

	parameters.reference        = {"getReferences", "getReference"},

	parameters.qualifier        = {"getAllQualifiers"},

	parameters.qualifier.."\\d" = {"getQualifiers", "getQualifier"},

	parameters.alias            = {"getAlias"},

	parameters.badge            = {"getBadge"}

}



-- default value objects, should NOT be mutated but instead copied

local defaultSeparators = {

	"sep"      = {" "},

	"sep%s"    = {","},

	"sep%q"    = {"; "},

	"sep%q\\d" = {", "},

	"sep%r"    = nil,  -- none

	"punc"     = nil   -- none

}



local rankTable = {

	"preferred"  = 1,

	"normal"     = 2,

	"deprecated" = 3

}



local function replaceAlias(id)

	if aliasesPid then

		id = aliasesPid

	end



	return id

end



local function errorText(code, param)

	local text = i18n"errors"][code

	if param then text = mw.ustring.gsub(text, "$1", param) end

	return text

end



local function throwError(errorMessage, param)

	error(errorText(errorMessage, param))

end



local function replaceDecimalMark(num)

	return mw.ustring.gsub(num, "[.]", i18n'numeric']['decimal-mark'], 1)

end



local function padZeros(num, numDigits)

	local numZeros

	local negative = false



	if num < 0 then

		negative = true

		num = num * -1

	end



	num = tostring(num)

	numZeros = numDigits - num:len()



	for _ = 1, numZeros do

		num = "0"..num

	end



	if negative then

		num = "-"..num

	end



	return num

end



local function replaceSpecialChar(chr)

	if chr == '_' then

		-- replace underscores with spaces

		return ' '

	else

		return chr

	end

end



local function replaceSpecialChars(str)

	local chr

	local esc = false

	local strOut = ""



	for i = 1, #str do

		chr = str:sub(i,i)



		if not esc then

			if chr == '\\' then

				esc = true

			else

				strOut = strOut .. replaceSpecialChar(chr)

			end

		else

			strOut = strOut .. chr

			esc = false

		end

	end



	return strOut

end



local function buildWikilink(target, label)

	if not label or target == label then

		return "[[" .. target .. "]]"

	else

		return "[[" .. target .. "|" .. label .. "]]"

	end

end



-- used to make frame.args mutable, to replace #frame.args (which is always 0)

-- with the actual amount and to simply copy tables

local function copyTable(tIn)

	if not tIn then

		return nil

	end



	local tOut = {}



	for i, v in pairs(tIn) do

		tOuti = v

	end



	return tOut

end



-- used to merge output arrays together;

-- note that it currently mutates the first input array

local function mergeArrays(a1, a2)

	for i = 1, #a2 do

		a1#a1 + 1 = a2i

	end



	return a1

end



local function split(str, del)

	local out = {}

	local i, j = str:find(del)



	if i and j then

		out1 = str:sub(1, i - 1)

		out2 = str:sub(j + 1)

	else

		out1 = str

	end



	return out

end



local function parseWikidataURL(url)

	local id



	if url:match('^http[s]?://') then

		id = split(url, "Q")



		if id2 then

			return "Q" .. id2

		end

	end



	return nil

end



local function parseDate(dateStr, precision)

	precision = precision or "d"



	local i, j, index, ptr

	local parts = {nil, nil, nil}



	if dateStr == nil then

		return parts1], parts2], parts3  -- year, month, day

	end



	-- 'T' for snak values, '/' for outputs with '/Julian' attached

	i, j = dateStr:find("[T/]")



	if i then

		dateStr = dateStr:sub(1, i-1)

	end



	local from = 1



	if dateStr:sub(1,1) == "-" then

		-- this is a negative number, look further ahead

		from = 2

	end



	index = 1

	ptr = 1



	i, j = dateStr:find("-", from)



	if i then

		-- year

		partsindex = tonumber(dateStr:sub(ptr, i-1), 10)  -- explicitly give base 10 to prevent error



		if partsindex == -0 then

			partsindex = tonumber("0")  -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead

		end



		if precision == "y" then

			-- we're done

			return parts1], parts2], parts3  -- year, month, day

		end



		index = index + 1

		ptr = i + 1



		i, j = dateStr:find("-", ptr)



		if i then

			-- month

			partsindex = tonumber(dateStr:sub(ptr, i-1), 10)



			if precision == "m" then

				-- we're done

				return parts1], parts2], parts3  -- year, month, day

			end



			index = index + 1

			ptr = i + 1

		end

	end



	if dateStr:sub(ptr) ~= "" then

		-- day if we have month, month if we have year, or year

		partsindex = tonumber(dateStr:sub(ptr), 10)

	end



	return parts1], parts2], parts3  -- year, month, day

end



local function datePrecedesDate(aY, aM, aD, bY, bM, bD)

	if aY == nil or bY == nil then

		return nil

	end

	aM = aM or 1

	aD = aD or 1

	bM = bM or 1

	bD = bD or 1



	if aY < bY then

		return true

	end



	if aY > bY then

		return false

	end



	if aM < bM then

		return true

	end



	if aM > bM then

		return false

	end



	if aD < bD then

		return true

	end



	return false

end



local function getHookName(param, index)

	if hookNamesparam then

		return hookNamesparam][index

	elseif param:len() > 2 then

		return hookNamesparam:sub(1, 2).."\\d"][index

	else

		return nil

	end

end



local function alwaysTrue()

	return true

end



-- The following function parses a format string.

--

-- The example below shows how a parsed string is structured in memory.

-- Variables other than 'str' and 'child' are left out for clarity's sake.

--

-- Example:

-- "A %p B [%s[%q1]] C [%r] D"

--

-- Structure:

-- [

--   {

--     str = "A "

--   },

--   {

--     str = "%p"

--   },

--   {

--     str = " B ",

--     child =

--     [

--       {

--         str = "%s",

--         child =

--         [

--           {

--             str = "%q1"

--           }

--         ]

--       }

--     ]

--   },

--   {

--     str = " C ",

--     child =

--     [

--       {

--         str = "%r"

--       }

--     ]

--   },

--   {

--     str = " D"

--   }

-- ]

--

local function parseFormat(str)

	local chr, esc, param, root, cur, prev, new

	local params = {}



	local function newObject(array)

		local obj = {}  -- new object

		obj.str = ""



		array#array + 1 = obj  -- array{object}

		obj.parent = array



		return obj

	end



	local function endParam()

		if param > 0 then

			if cur.str ~= "" then

				cur.str = "%"..cur.str

				cur.param = true

				paramscur.str = true

				cur.parent.reqcur.str = true

				prev = cur

				cur = newObject(cur.parent)

			end

			param = 0

		end

	end



	root = {}  -- array

	root.req = {}

	cur = newObject(root)

	prev = nil



	esc = false

	param = 0



	for i = 1, #str do

		chr = str:sub(i,i)



		if not esc then

			if chr == '\\' then

				endParam()

				esc = true

			elseif chr == '%' then

				endParam()

				if cur.str ~= "" then

					cur = newObject(cur.parent)

				end

				param = 2

			elseif chr == '[' then

				endParam()

				if prev and cur.str == "" then

					table.remove(cur.parent)

					cur = prev

				end

				cur.child = {}  -- new array

				cur.child.req = {}

				cur.child.parent = cur

				cur = newObject(cur.child)

			elseif chr == ']' then

				endParam()

				if cur.parent.parent then

					new = newObject(cur.parent.parent.parent)

					if cur.str == "" then

						table.remove(cur.parent)

					end

					cur = new

				end

			else

				if param > 1 then

					param = param - 1

				elseif param == 1 then

					if not chr:match('%d') then

						endParam()

					end

				end



				cur.str = cur.str .. replaceSpecialChar(chr)

			end

		else

			cur.str = cur.str .. chr

			esc = false

		end



		prev = nil

	end



	endParam()



	-- make sure that at least one required parameter has been defined

	if not next(root.req) then

		throwError("missing-required-parameter")

	end



	-- make sure that the separator parameter "%s" is not amongst the required parameters

	if root.reqparameters.separator then

		throwError("extra-required-parameter", parameters.separator)

	end



	return root, params

end



local function sortOnRank(claims)

	local rankPos

	local ranks = {{}, {}, {}, {}}  -- preferred, normal, deprecated, (default)

	local sorted = {}



	for _, v in ipairs(claims) do

		rankPos = rankTablev.rank or 4

		ranksrankPos][#ranksrankPos + 1 = v

	end



	sorted = ranks1

	sorted = mergeArrays(sorted, ranks2])

	sorted = mergeArrays(sorted, ranks3])



	return sorted

end



local Config = {}



-- allows for recursive calls

function Config:new()

	local cfg = {}

	setmetatable(cfg, self)

	self.__index = self



	cfg.separators = {

		-- single value objects wrapped in arrays so that we can pass by reference

		"sep"   = {copyTable(defaultSeparators"sep"])},

		"sep%s" = {copyTable(defaultSeparators"sep%s"])},

		"sep%q" = {copyTable(defaultSeparators"sep%q"])},

		"sep%r" = {copyTable(defaultSeparators"sep%r"])},

		"punc"  = {copyTable(defaultSeparators"punc"])}

	}



	cfg.entity = nil

	cfg.entityID = nil

	cfg.propertyID = nil

	cfg.propertyValue = nil

	cfg.qualifierIDs = {}

	cfg.qualifierIDsAndValues = {}



	cfg.bestRank = true

	cfg.ranks = {true, true, false}  -- preferred = true, normal = true, deprecated = false

	cfg.foundRank = #cfg.ranks

	cfg.flagBest = false

	cfg.flagRank = false



	cfg.periods = {true, true, true}  -- future = true, current = true, former = true

	cfg.flagPeriod = false

	cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))}  -- today as {year, month, day}



	cfg.mdyDate = false

	cfg.singleClaim = false

	cfg.sourcedOnly = false

	cfg.editable = false

	cfg.editAtEnd = false



	cfg.inSitelinks = false



	cfg.langCode = mw.language.getContentLanguage().code

	cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode)

	cfg.langObj = mw.language.new(cfg.langCode)



	cfg.siteID = mw.wikibase.getGlobalSiteId()



	cfg.states = {}

	cfg.states.qualifiersCount = 0

	cfg.curState = nil



	cfg.prefetchedRefs = nil



	return cfg

end



local State = {}



function State:new(cfg, type)

	local stt = {}

	setmetatable(stt, self)

	self.__index = self



	stt.conf = cfg

	stt.type = type



	stt.results = {}



	stt.parsedFormat = {}

	stt.separator = {}

	stt.movSeparator = {}

	stt.puncMark = {}



	stt.linked = false

	stt.rawValue = false

	stt.shortName = false

	stt.anyLanguage = false

	stt.unitOnly = false

	stt.singleValue = false



	return stt

end



-- if id == nil then item connected to current page is used

function Config:getLabel(id, raw, link, short)

	local label = nil

	local prefix, title= "", nil



	if not id then

		id = mw.wikibase.getEntityIdForCurrentPage()



		if not id then

			return ""

		end

	end



	id = id:upper()  -- just to be sure



	if raw then

		-- check if given id actually exists

		if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then

			label = id

		end



		prefix, title = "d:Special:EntityPage/", label -- may be nil

	else

		-- try short name first if requested

		if short then

			label = p._property{aliasesP.shortName, p.args.eid = id}  -- get short name



			if label == "" then

				label = nil

			end

		end



		-- get label

		if not label then

			label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels?

		end

	end



	if not label then

		label = ""

	elseif link then

		-- build a link if requested

		if not title then

			if id:sub(1,1) == "Q" then

				title = mw.wikibase.getSitelink(id)

			elseif id:sub(1,1) == "P" then

				-- properties have no sitelink, link to Wikidata instead

				prefix, title = "d:Special:EntityPage/", id

			end

		end



		label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup

		if title then

			label = buildWikilink(prefix .. title, label)

		end

	end



	return label

end



function Config:getEditIcon()

	local value = ""

	local prefix = ""

	local front = "&nbsp;"

	local back = ""



	if self.entityID:sub(1,1) == "P" then

		prefix = "Property:"

	end



	if self.editAtEnd then

		front = '<span style="float:'



		if self.langObj:isRTL() then

			front = front .. 'left'

		else

			front = front .. 'right'

		end



		front = front .. '">'

		back = '</span>'

	end



	value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n'info']['edit-on-wikidata' .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode



	if self.propertyID then

		value = value .. "#" .. self.propertyID

	elseif self.inSitelinks then

		value = value .. "#sitelinks-wikipedia"

	end



	value = value .. "|" .. i18n'info']['edit-on-wikidata' .. "]]"



	return front .. value .. back

end



-- used to create the final output string when it's all done, so that for references the

-- function extensionTag("ref", ...) is only called when they really ended up in the final output

function Config:concatValues(valuesArray)

	local outString = ""

	local j, skip



	for i = 1, #valuesArray do

		-- check if this is a reference

		if valuesArrayi].refHash then

			j = i - 1

			skip = false



			-- skip this reference if it is part of a continuous row of references that already contains the exact same reference

			while valuesArrayj and valuesArrayj].refHash do

				if valuesArrayi].refHash == valuesArrayj].refHash then

					skip = true

					break

				end

				j = j - 1

			end



			if not skip then

				-- add <ref> tag with the reference's hash as its name (to deduplicate references)

				outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArrayi][1], {name = valuesArrayi].refHash})

			end

		else

			outString = outString .. valuesArrayi][1

		end

	end



	return outString

end



function Config:convertUnit(unit, raw, link, short, unitOnly)

	local space = " "

	local label = ""

	local itemID



	if unit == "" or unit == "1" then

		return nil

	end



	if unitOnly then

		space = ""

	end



	itemID = parseWikidataURL(unit)



	if itemID then

		if itemID == aliasesQ.percentage then

			return "%"

		else

			label = self:getLabel(itemID, raw, link, short)



			if label ~= "" then

				return space .. label

			end

		end

	end



	return ""

end



function State:getValue(snak)

	return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2))

end



function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type)

	if snak.snaktype == 'value' then

		local datatype = snak.datavalue.type

		local subtype = snak.datatype

		local datavalue = snak.datavalue.value



		if datatype == 'string' then

			if subtype == 'url' and link then

				-- create link explicitly

				if raw then

					-- will render as a linked number like [1]

					return "[" .. datavalue .. "]"

				else

					return "[" .. datavalue .. " " .. datavalue .. "]"

				end

			elseif subtype == 'commonsMedia' then

				if link then

					return buildWikilink("c:File:" .. datavalue, datavalue)

				elseif not raw then

					return "[[File:" .. datavalue .. "]]"

				else

					return datavalue

				end

			elseif subtype == 'geo-shape' and link then

				return buildWikilink("c:" .. datavalue, datavalue)

			elseif subtype == 'math' and not raw then

				local attribute = nil



				if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then

					attribute = {qid = self.entityID}

				end



				return mw.getCurrentFrame():extensionTag("math", datavalue, attribute)

			elseif subtype == 'external-id' and link then

				local url = p._property{aliasesP.formatterURL, p.args.eid = snak.property}  -- get formatter URL



				if url ~= "" then

					url = mw.ustring.gsub(url, "$1", datavalue)

					return "[" .. url .. " " .. datavalue .. "]"

				else

					return datavalue

				end

			else

				return datavalue

			end

		elseif datatype == 'monolingualtext' then

			if anyLang or datavalue'language' == self.langCode then

				return datavalue'text'

			else

				return nil

			end

		elseif datatype == 'quantity' then

			local value = ""

			local unit



			if not unitOnly then

				-- get value and strip + signs from front

				value = mw.ustring.gsub(datavalue'amount'], "^%+(.+)$", "%1")



				if raw then

					return value

				end



				-- replace decimal mark based on locale

				value = replaceDecimalMark(value)



				-- add delimiters for readability

				value = i18n.addDelimiters(value)

			end



			unit = self:convertUnit(datavalue'unit'], raw, link, short, unitOnly)



			if unit then

				value = value .. unit

			end



			return value

		elseif datatype == 'time' then

			local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr

			local yFactor = 1

			local sign = 1

			local prefix = ""

			local suffix = ""

			local mayAddCalendar = false

			local calendar = ""

			local precision = datavalue'precision'



			if precision == 11 then

				p = "d"

			elseif precision == 10 then

				p = "m"

			else

				p = "y"

				yFactor = 10^(9-precision)

			end



			y, m, d = parseDate(datavalue'time'], p)



			if y < 0 then

				sign = -1

				y = y * sign

			end



			-- if precision is tens/hundreds/thousands/millions/billions of years

			if precision <= 8 then

				yDiv = y / yFactor



				-- if precision is tens/hundreds/thousands of years

				if precision >= 6 then

					mayAddCalendar = true



					if precision <= 7 then

						-- round centuries/millenniums up (e.g. 20th century or 3rd millennium)

						yRound = math.ceil(yDiv)



						if not raw then

							if precision == 6 then

								suffix = i18n'datetime']['suffixes']['millennium'

							else

								suffix = i18n'datetime']['suffixes']['century'

							end



							suffix = i18n.getOrdinalSuffix(yRound) .. suffix

						else

							-- if not verbose, take the first year of the century/millennium

							-- (e.g. 1901 for 20th century or 2001 for 3rd millennium)

							yRound = (yRound - 1) * yFactor + 1

						end

					else

						-- precision == 8

						-- round decades down (e.g. 2010s)

						yRound = math.floor(yDiv) * yFactor



						if not raw then

							prefix = i18n'datetime']['prefixes']['decade-period'

							suffix = i18n'datetime']['suffixes']['decade-period'

						end

					end



					if raw and sign < 0 then

						-- if BCE then compensate for "counting backwards"

						-- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE)

						yRound = yRound + yFactor - 1

					end

				else

					local yReFactor, yReDiv, yReRound



					-- round to nearest for tens of thousands of years or more

					yRound = math.floor(yDiv + 0.5)



					if yRound == 0 then

						if precision <= 2 and y ~= 0 then

							yReFactor = 1e6

							yReDiv = y / yReFactor

							yReRound = math.floor(yReDiv + 0.5)



							if yReDiv == yReRound then

								-- change precision to millions of years only if we have a whole number of them

								precision = 3

								yFactor = yReFactor

								yRound = yReRound

							end

						end



						if yRound == 0 then

							-- otherwise, take the unrounded (original) number of years

							precision = 5

							yFactor = 1

							yRound = y

							mayAddCalendar = true

						end

					end



					if precision >= 1 and y ~= 0 then

						yFull = yRound * yFactor



						yReFactor = 1e9

						yReDiv = yFull / yReFactor

						yReRound = math.floor(yReDiv + 0.5)



						if yReDiv == yReRound then

							-- change precision to billions of years if we're in that range

							precision = 0

							yFactor = yReFactor

							yRound = yReRound

						else

							yReFactor = 1e6

							yReDiv = yFull / yReFactor

							yReRound = math.floor(yReDiv + 0.5)



							if yReDiv == yReRound then

								-- change precision to millions of years if we're in that range

								precision = 3

								yFactor = yReFactor

								yRound = yReRound

							end

						end

					end



					if not raw then

						if precision == 3 then

							suffix = i18n'datetime']['suffixes']['million-years'

						elseif precision == 0 then

							suffix = i18n'datetime']['suffixes']['billion-years'

						else

							yRound = yRound * yFactor

							if yRound == 1 then

								suffix = i18n'datetime']['suffixes']['year'

							else

								suffix = i18n'datetime']['suffixes']['years'

							end

						end

					else

						yRound = yRound * yFactor

					end

				end

			else

				yRound = y

				mayAddCalendar = true

			end



			if mayAddCalendar then

				calendarID = parseWikidataURL(datavalue'calendarmodel'])



				if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then

					if not raw then

						if link then

							calendar = " ("..buildWikilink(i18n'datetime']['julian-calendar'], i18n'datetime']['julian'])..")"

						else

							calendar = " ("..i18n'datetime']['julian'..")"

						end

					else

						calendar = "/"..i18n'datetime']['julian'

					end

				end

			end



			if not raw then

				local ce = nil



				if sign < 0 then

					ce = i18n'datetime']['BCE'

				elseif precision <= 5 then

					ce = i18n'datetime']['CE'

				end



				if ce then

					if link then

						ce = buildWikilink(i18n'datetime']['common-era'], ce)

					end

					suffix = suffix .. " " .. ce

				end



				value = tostring(yRound)



				if m then

					dateStr = self.langObj:formatDate("F", "1-"..m.."-1")



					if d then

						if self.mdyDate then

							dateStr = dateStr .. " " .. d .. ","

						else

							dateStr = d .. " " .. dateStr

						end

					end



					value = dateStr .. " " .. value

				end



				value = prefix .. value .. suffix .. calendar

			else

				value = padZeros(yRound * sign, 4)



				if m then

					value = value .. "-" .. padZeros(m, 2)



					if d then

						value = value .. "-" .. padZeros(d, 2)

					end

				end



				value = value .. calendar

			end



			return value

		elseif datatype == 'globecoordinate' then

			-- logic from https://github.com/DataValues/Geo (v4.0.1)



			local precision, unitsPerDegree, numDigits, strFormat, value, globe

			local latitude, latConv, latValue, latLink

			local longitude, lonConv, lonValue, lonLink

			local latDirection, latDirectionN, latDirectionS, latDirectionEN

			local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN

			local degSymbol, minSymbol, secSymbol, separator



			local latDegrees = nil

			local latMinutes = nil

			local latSeconds = nil

			local lonDegrees = nil

			local lonMinutes = nil

			local lonSeconds = nil



			local latDegSym = ""

			local latMinSym = ""

			local latSecSym = ""

			local lonDegSym = ""

			local lonMinSym = ""

			local lonSecSym = ""



			local latDirectionEN_N = "N"

			local latDirectionEN_S = "S"

			local lonDirectionEN_E = "E"

			local lonDirectionEN_W = "W"



			if not raw then

				latDirectionN = i18n'coord']['latitude-north'

				latDirectionS = i18n'coord']['latitude-south'

				lonDirectionE = i18n'coord']['longitude-east'

				lonDirectionW = i18n'coord']['longitude-west'



				degSymbol = i18n'coord']['degrees'

				minSymbol = i18n'coord']['minutes'

				secSymbol = i18n'coord']['seconds'

				separator = i18n'coord']['separator'

			else

				latDirectionN = latDirectionEN_N

				latDirectionS = latDirectionEN_S

				lonDirectionE = lonDirectionEN_E

				lonDirectionW = lonDirectionEN_W



				degSymbol = "/"

				minSymbol = "/"

				secSymbol = "/"

				separator = "/"

			end



			latitude = datavalue'latitude'

			longitude = datavalue'longitude'



			if latitude < 0 then

				latDirection = latDirectionS

				latDirectionEN = latDirectionEN_S

				latitude = math.abs(latitude)

			else

				latDirection = latDirectionN

				latDirectionEN = latDirectionEN_N

			end



			if longitude < 0 then

				lonDirection = lonDirectionW

				lonDirectionEN = lonDirectionEN_W

				longitude = math.abs(longitude)

			else

				lonDirection = lonDirectionE

				lonDirectionEN = lonDirectionEN_E

			end



			precision = datavalue'precision'



			if not precision or precision <= 0 then

				precision = 1 / 3600  -- precision not set (correctly), set to arcsecond

			end



			-- remove insignificant detail

			latitude = math.floor(latitude / precision + 0.5) * precision

			longitude = math.floor(longitude / precision + 0.5) * precision



			if precision >= 1 - (1 / 60) and precision < 1 then

				precision = 1

			elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then

				precision = 1 / 60

			end



			if precision >= 1 then

				unitsPerDegree = 1

			elseif precision >= (1 / 60)  then

				unitsPerDegree = 60

			else

				unitsPerDegree = 3600

			end



			numDigits = math.ceil(-math.log10(unitsPerDegree * precision))



			if numDigits <= 0 then

				numDigits = tonumber("0")  -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead

			end



			strFormat = "%." .. numDigits .. "f"



			if precision >= 1 then

				latDegrees = strFormat:format(latitude)

				lonDegrees = strFormat:format(longitude)



				if not raw then

					latDegSym = replaceDecimalMark(latDegrees) .. degSymbol

					lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol

				else

					latDegSym = latDegrees .. degSymbol

					lonDegSym = lonDegrees .. degSymbol

				end

			else

				latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits

				lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits



				if precision >= (1 / 60) then

					latMinutes = latConv

					lonMinutes = lonConv

				else

					latSeconds = latConv

					lonSeconds = lonConv



					latMinutes = math.floor(latSeconds / 60)

					lonMinutes = math.floor(lonSeconds / 60)



					latSeconds = strFormat:format(latSeconds - (latMinutes * 60))

					lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60))



					if not raw then

						latSecSym = replaceDecimalMark(latSeconds) .. secSymbol

						lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol

					else

						latSecSym = latSeconds .. secSymbol

						lonSecSym = lonSeconds .. secSymbol

					end

				end



				latDegrees = math.floor(latMinutes / 60)

				lonDegrees = math.floor(lonMinutes / 60)



				latDegSym = latDegrees .. degSymbol

				lonDegSym = lonDegrees .. degSymbol



				latMinutes = latMinutes - (latDegrees * 60)

				lonMinutes = lonMinutes - (lonDegrees * 60)



				if precision >= (1 / 60) then

					latMinutes = strFormat:format(latMinutes)

					lonMinutes = strFormat:format(lonMinutes)



					if not raw then

						latMinSym = replaceDecimalMark(latMinutes) .. minSymbol

						lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol

					else

						latMinSym = latMinutes .. minSymbol

						lonMinSym = lonMinutes .. minSymbol

					end

				else

					latMinSym = latMinutes .. minSymbol

					lonMinSym = lonMinutes .. minSymbol

				end

			end



			latValue = latDegSym .. latMinSym .. latSecSym .. latDirection

			lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection



			value = latValue .. separator .. lonValue



			if link then

				globe = parseWikidataURL(datavalue'globe'])



				if globe then

					globe = mw.wikibase.getLabelByLang(globe, "en"):lower()

				else

					globe = "earth"

				end



				latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_")

				lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_")



				value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]"

			end



			return value

		elseif datatype == 'wikibase-entityid' then

			local label

			local itemID = datavalue'numeric-id'



			if subtype == 'wikibase-item' then

				itemID = "Q" .. itemID

			elseif subtype == 'wikibase-property' then

				itemID = "P" .. itemID

			else

				return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>'

			end



			label = self:getLabel(itemID, raw, link, short)



			if label == "" then

				label = nil

			end



			return label

		else

			return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>'

		end

	elseif snak.snaktype == 'somevalue' and not noSpecial then

		if raw then

			return " "  -- single space represents 'somevalue'

		else

			return i18n'values']['unknown'

		end

	elseif snak.snaktype == 'novalue' and not noSpecial then

		if raw then

			return ""  -- empty string represents 'novalue'

		else

			return i18n'values']['none'

		end

	else

		return nil

	end

end



function Config:getSingleRawQualifier(claim, qualifierID)

	local qualifiers



	if claim.qualifiers then qualifiers = claim.qualifiersqualifierID end



	if qualifiers and qualifiers1 then

		return self:getValue(qualifiers1], true)  -- raw = true

	else

		return nil

	end

end



function Config:snakEqualsValue(snak, value)

	local snakValue = self:getValue(snak, true)  -- raw = true



	if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end



	return snakValue == value

end



function Config:setRank(rank)

	local rankPos



	if rank == p.flags.best then

		self.bestRank = true

		self.flagBest = true  -- mark that 'best' flag was given

		return

	end



	if rank:sub(1,9) == p.flags.preferred then

		rankPos = 1

	elseif rank:sub(1,6) == p.flags.normal then

		rankPos = 2

	elseif rank:sub(1,10) == p.flags.deprecated then

		rankPos = 3

	else

		return

	end



	-- one of the rank flags was given, check if another one was given before

	if not self.flagRank then

		self.ranks = {false, false, false}  -- no other rank flag given before, so unset ranks

		self.bestRank = self.flagBest       -- unsets bestRank only if 'best' flag was not given before

		self.flagRank = true                -- mark that a rank flag was given

	end



	if rank:sub(-1) == "+" then

		for i = rankPos, 1, -1 do

			self.ranksi = true

		end

	elseif rank:sub(-1) == "-" then

		for i = rankPos, #self.ranks do

			self.ranksi = true

		end

	else

		self.ranksrankPos = true

	end

end



function Config:setPeriod(period)

	local periodPos



	if period == p.flags.future then

		periodPos = 1

	elseif period == p.flags.current then

		periodPos = 2

	elseif period == p.flags.former then

		periodPos = 3

	else

		return

	end



	-- one of the period flags was given, check if another one was given before

	if not self.flagPeriod then

		self.periods = {false, false, false}  -- no other period flag given before, so unset periods

		self.flagPeriod = true                -- mark that a period flag was given

	end



	self.periodsperiodPos = true

end



function Config:qualifierMatches(claim, id, value)

	local qualifiers



	if claim.qualifiers then qualifiers = claim.qualifiersid end

	if qualifiers then

		for _, v in pairs(qualifiers) do

			if self:snakEqualsValue(v, value) then

				return true

			end

		end

	elseif value == "" then

		-- if the qualifier is not present then treat it the same as the special value 'novalue'

		return true

	end



	return false

end



function Config:rankMatches(rankPos)

	if self.bestRank then

		return (self.ranksrankPos and self.foundRank >= rankPos)

	else

		return self.ranksrankPos

	end

end



function Config:timeMatches(claim)

	local startTime = nil

	local startTimeY = nil

	local startTimeM = nil

	local startTimeD = nil

	local endTime = nil

	local endTimeY = nil

	local endTimeM = nil

	local endTimeD = nil



	if self.periods1 and self.periods2 and self.periods3 then

		-- any time

		return true

	end



	startTime = self:getSingleRawQualifier(claim, aliasesP.startTime)

	if startTime and startTime ~= "" and startTime ~= " " then

		startTimeY, startTimeM, startTimeD = parseDate(startTime)

	end



	endTime = self:getSingleRawQualifier(claim, aliasesP.endTime)

	if endTime and endTime ~= "" and endTime ~= " " then

		endTimeY, endTimeM, endTimeD = parseDate(endTime)

	end



	if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then

		-- invalidate end time if it precedes start time

		endTimeY = nil

		endTimeM = nil

		endTimeD = nil

	end



	if self.periods1 then

		-- future

		if startTimeY and datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], startTimeY, startTimeM, startTimeD) then

			return true

		end

	end



	if self.periods2 then

		-- current

		if (startTimeY == nil or not datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], startTimeY, startTimeM, startTimeD)) and

		   (endTimeY == nil or datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], endTimeY, endTimeM, endTimeD)) then

			return true

		end

	end



	if self.periods3 then

		-- former

		if endTimeY and not datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], endTimeY, endTimeM, endTimeD) then

			return true

		end

	end



	return false

end



function Config:processFlag(flag)

	if not flag then

		return false

	end



	if flag == p.flags.linked then

		self.curState.linked = true

		return true

	elseif flag == p.flags.raw then

		self.curState.rawValue = true



		if self.curState == self.statesparameters.reference then

			-- raw reference values end with periods and require a separator (other than none)

			self.separators"sep%r"][1 = {" "}

		end



		return true

	elseif flag == p.flags.short then

		self.curState.shortName = true

		return true

	elseif flag == p.flags.multilanguage then

		self.curState.anyLanguage = true

		return true

	elseif flag == p.flags.unit then

		self.curState.unitOnly = true

		return true

	elseif flag == p.flags.mdy then

		self.mdyDate = true

		return true

	elseif flag == p.flags.single then

		self.singleClaim = true

		return true

	elseif flag == p.flags.sourced then

		self.sourcedOnly = true

		return true

	elseif flag == p.flags. then

		self.editable = true

		return true

	elseif flag == p.flags.editAtEnd then

		self.editable = true

		self.editAtEnd = true

		return true

	elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then

		self:setRank(flag)

		return true

	elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then

		self:setPeriod(flag)

		return true

	elseif flag == "" then

		-- ignore empty flags and carry on

		return true

	else

		return false

	end

end



function Config:processFlagOrCommand(flag)

	local param = ""



	if not flag then

		return false

	end



	if flag == p.claimCommands.property or flag == p.claimCommands.properties then

		param = parameters.property

	elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then

		self.states.qualifiersCount = self.states.qualifiersCount + 1

		param = parameters.qualifier .. self.states.qualifiersCount

		self.separators"sep"..param = {copyTable(defaultSeparators"sep%q\\d"])}

	elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then

		param = parameters.reference

	else

		return self:processFlag(flag)

	end



	if self.statesparam then

		return false

	end



	-- create a new state for each command

	self.statesparam = State:new(self, param)



	-- use "%x" as the general parameter name

	self.statesparam].parsedFormat = parseFormat(parameters.general)  -- will be overwritten for param=="%p"



	-- set the separator

	self.statesparam].separator = self.separators"sep"..param  -- will be nil for param=="%p", which will be set separately



	if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then

		self.statesparam].singleValue = true

	end



	self.curState = self.statesparam



	return true

end



function Config:processSeparators(args)

	local sep



	for i, v in pairs(self.separators) do

		if argsi then

			sep = replaceSpecialChars(argsi])



			if sep ~= "" then

				self.separatorsi][1 = {sep}

			else

				self.separatorsi][1 = nil

			end

		end

	end

end



function Config:setFormatAndSeparators(state, parsedFormat)

	state.parsedFormat = parsedFormat

	state.separator = self.separators"sep"

	state.movSeparator = self.separators"sep"..parameters.separator

	state.puncMark = self.separators"punc"

end



-- determines if a claim has references by prefetching them from the claim using getReferences,

-- which applies some filtering that determines if a reference is actually returned,

-- and caches the references for later use

function State:isSourced(claim)

	self.conf.prefetchedRefs = self:getReferences(claim)

	return (#self.conf.prefetchedRefs > 0)

end



function State:resetCaches()

	-- any prefetched references of the previous claim must not be used

	self.conf.prefetchedRefs = nil

end



function State:claimMatches(claim)

	local matches, rankPos



	-- first of all, reset any cached values used for the previous claim

	self:resetCaches()



	-- if a property value was given, check if it matches the claim's property value

	if self.conf.propertyValue then

		matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue)

	else

		matches = true

	end



	-- if any qualifier values were given, check if each matches one of the claim's qualifier values

	for i, v in pairs(self.conf.qualifierIDsAndValues) do

		matches = (matches and self.conf:qualifierMatches(claim, i, v))

	end



	-- check if the claim's rank and time period match

	rankPos = rankTableclaim.rank or 4

	matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim))



	-- if only claims with references must be returned, check if this one has any

	if self.conf.sourcedOnly then

		matches = (matches and self:isSourced(claim))  -- prefetches and caches references

	end



	return matches, rankPos

end



function State:out()

	local result  -- collection of arrays with value objects

	local valuesArray  -- array with value objects

	local sep = nil  -- value object

	local out = {}  -- array with value objects



	local function walk(formatTable, result)

		local valuesArray = {}  -- array with value objects



		for i, v in pairs(formatTable.req) do

			if not resulti or not resulti][1 then

				-- we've got no result for a parameter that is required on this level,

				-- so skip this level (and its children) by returning an empty result

				return {}

			end

		end



		for _, v in ipairs(formatTable) do

			if v.param then

				valuesArray = mergeArrays(valuesArray, resultv.str])

			elseif v.str ~= "" then

				valuesArray#valuesArray + 1 = {v.str}

			end



			if v.child then

				valuesArray = mergeArrays(valuesArray, walk(v.child, result))

			end

		end



		return valuesArray

	end



	-- iterate through the results from back to front, so that we know when to add separators

	for i = #self.results, 1, -1 do

		result = self.resultsi



		-- if there is already some output, then add the separators

		if #out > 0 then

			sep = self.separator1  -- fixed separator

			resultparameters.separator = {self.movSeparator1]}  -- movable separator

		else

			sep = nil

			resultparameters.separator = {self.puncMark1]}  -- optional punctuation mark

		end



		valuesArray = walk(self.parsedFormat, result)



		if #valuesArray > 0 then

			if sep then

				valuesArray#valuesArray + 1 = sep

			end



			out = mergeArrays(valuesArray, out)

		end

	end



	-- reset state before next iteration

	self.results = {}



	return out

end



-- level 1 hook

function State:getProperty(claim)

	local value = {self:getValue(claim.mainsnak)}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



-- level 1 hook

function State:getQualifiers(claim, param)

	local qualifiers



	if claim.qualifiers then qualifiers = claim.qualifiersself.conf.qualifierIDsparam]] end

	if qualifiers then

		-- iterate through claim's qualifier statements to collect their values;

		-- return array with multiple value objects

		return self.conf.statesparam]:iterate(qualifiers, {[parameters.general = hookNamesparameters.qualifier.."\\d"][2], count = 1})  -- pass qualifier state with level 2 hook

	else

		return {}  -- return empty array

	end

end



-- level 2 hook

function State:getQualifier(snak)

	local value = {self:getValue(snak)}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



-- level 1 hook

function State:getAllQualifiers(claim, param, result, hooks)

	local out = {}  -- array with value objects

	local sep = self.conf.separators"sep"..parameters.qualifier][1  -- value object



	-- iterate through the output of the separate "qualifier(s)" commands

	for i = 1, self.conf.states.qualifiersCount do



		-- if a hook has not been called yet, call it now

		if not resultparameters.qualifier..i then

			self:callHook(parameters.qualifier..i, hooks, claim, result)

		end



		-- if there is output for this particular "qualifier(s)" command, then add it

		if resultparameters.qualifier..i and resultparameters.qualifier..i][1 then



			-- if there is already some output, then add the separator

			if #out > 0 and sep then

				out#out + 1 = sep

			end



			out = mergeArrays(out, resultparameters.qualifier..i])

		end

	end



	return out

end



-- level 1 hook

function State:getReferences(claim)

	if self.conf.prefetchedRefs then

		-- return references that have been prefetched by isSourced

		return self.conf.prefetchedRefs

	end



	if claim.references then

		-- iterate through claim's reference statements to collect their values;

		-- return array with multiple value objects

		return self.conf.statesparameters.reference]:iterate(claim.references, {[parameters.general = hookNamesparameters.reference][2], count = 1})  -- pass reference state with level 2 hook

	else

		return {}  -- return empty array

	end

end



-- level 2 hook

function State:getReference(statement)

	local key, citeWeb, citeQ, label

	local params = {}

	local citeParams = {['web' = {}, 'q' = {}}

	local citeMismatch = {}

	local useCite = nil

	local useParams = nil

	local value = ""

	local ref = {}

	local referenceEmpty = true  -- will be set to false if at least one parameter is left unremoved

    local numAuthorParameters = 0

    local numAuthorNameStringParameters = 0

    local tempLink

    local additionalRefProperties = {}  -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and link from an external ID

    local wikidataPropertiesOfSource  -- will contain "Wikidata property" properties of the item in stated in, if any



	local version = 6  -- increment this each time the below logic is changed to avoid conflict errors



	if statement.snaks then

		-- don't include "imported from", which is added by a bot

		if statement.snaksaliasesP.importedFrom then

			statement.snaksaliasesP.importedFrom = nil

		end

		

		-- don't include "Wikimedia import URL"

		if statement.snaksaliasesP.wikimediaImportURL then

			statement.snaksaliasesP.wikimediaImportURL = nil

			-- don't include "retrieved" if no "referenceURL" is present,

			-- as "retrieved" probably belongs to "Wikimedia import URL"

			if statement.snaksaliasesP.retrieved and not statement.snaksaliasesP.referenceURL then

				statement.snaksaliasesP.retrieved = nil

			end

		end



		-- don't include "inferred from", which is added by a bot

		if statement.snaksaliasesP.inferredFrom then

			statement.snaksaliasesP.inferredFrom = nil

		end



		-- don't include "type of reference"

		if statement.snaksaliasesP.typeOfReference then

			statement.snaksaliasesP.typeOfReference = nil

		end



		-- don't include "image" to prevent littering

		if statement.snaksaliasesP.image then

			statement.snaksaliasesP.image = nil

		end



		-- don't include "language" if it is equal to the local one

		if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then

			statement.snaksaliasesP.language = nil

		end

        

        if statement.snaksaliasesP.statedIn and not statement.snaksaliasesP.referenceURL then

        	-- "stated in" was given but "reference URL" was not.

        	-- get "Wikidata property" properties from the item in "stated in"

        	-- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference

        	

        	-- find the "Wikidata property" properties in the item from "stated in"

        	wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, p.args.eid = self.conf:getValue(statement.snaksaliasesP.statedIn][1], true, false)}, ", ", true)

        	for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do

        		if statement.snakswikidataPropertyOfSource and statement.snakswikidataPropertyOfSource][1].datatype == "external-id" then

        			tempLink = self.conf:getValue(statement.snakswikidataPropertyOfSource][1], false, true)  -- not raw, linked

        			if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then  -- getValue returned a URL.

        		    	additionalRefPropertiesaliasesP.referenceURL = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1")  -- the link is in wiki markup, so strip the square brackets and the display text

        			    statement.snakswikidataPropertyOfSource = nil

        			    break

        			end

        		end

        	end

        end

        

        -- don't include "subject named as", but use it as the title when "title" is not present but a URL is

        if statement.snaksaliasesP.subjectNamedAs then

        	if not statement.snaksaliasesP.title and (statement.snaksaliasesP.referenceURL or additionalRefPropertiesaliasesP.referenceURL]) then

        		additionalRefPropertiesaliasesP.title = statement.snaksaliasesP.subjectNamedAs][1].datavalue.value

        	end

        	statement.snaksaliasesP.subjectNamedAs = nil

        end



		-- retrieve all the parameters

		for i in pairs(statement.snaks) do

			label = ""



			-- multiple authors may be given

			if i == aliasesP.author or i == aliasesP.authorNameString then

				paramsi = self:getReferenceDetails(statement.snaks, i, false, self.linked, true)  -- link = true/false, anyLang = true

			elseif i == aliasesP.statedIn then	

				-- Get "stated in" raw, as it is wanted (for Cite Q) even if it doesn't have a local language label.

				paramsaliasesP.statedIn = {self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)}  -- raw = true

			else

				paramsi = {self:getReferenceDetail(statement.snaks, i, false, self.linked and (statement.snaksi][1].datatype ~= 'url'), true)}  -- link = true/false, anyLang = true

			end

			

			if #paramsi == 0 then

				paramsi = nil

			else

				referenceEmpty = false

				

				if statement.snaksi][1].datatype == 'external-id' then

					key = "external-id"

					label = self.conf:getLabel(i)



					if label ~= "" then

						label = label .. " "

					end

				else

					key = i

				end



				-- add the parameter to each matching type of citation

				for j in pairs(citeParams) do

					-- do so if there was no mismatch with a previous parameter

					if not citeMismatchj then

						-- check if this parameter is not mismatching itself

						if i18n'cite'][j][key then

							-- continue if an option is available in the corresponding cite template

							if i18n'cite'][j][key ~= "" then

                                -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter)

                                if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n'cite'][j][aliasesP.author ~= i18n'cite'][j][aliasesP.authorNameString]) then

								    citeParamsj][i18n'cite'][j][key]] = label .. paramsi][1

                                    -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained

								    for k=2, #paramsi do

                                        citeParamsj][i18n'cite'][j][key..k = label .. paramsi][k

							    	end

                                -- handle "author" and "author name string" specially if they use the same template parameter

                                elseif i == aliasesP.author or i == aliasesP.authorNameString then

                                    if paramsaliasesP.author ~= nil then

                                        numAuthorParameters = #paramsaliasesP.author

                                    else

                                        numAuthorParameters = 0

                                    end



                                    if paramsaliasesP.authorNameString ~= nil then

                                        numAuthorNameStringParameters = #paramsaliasesP.authorNameString

                                    else

                                        numAuthorNameStringParameters = 0

                                    end



                                    -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks

                                    -- reason: parameters are added to params each iteration of the loop, not before the loop

                                    if ((statement.snaksaliasesP.author == nil) == (numAuthorParameters == 0)) and ((statement.snaksaliasesP.authorNameString == nil) == (numAuthorNameStringParameters == 0)) then

                                        for k=1, numAuthorParameters + numAuthorNameStringParameters do

                                            if k <= numAuthorParameters then  -- now handling the authors from the "author" property

                                                citeParamsj][i18n'cite'][j][aliasesP.author..k = label .. paramsaliasesP.author][k

                                            else  -- now handling the authors from "author name string"

                                                citeParamsj][i18n'cite'][j][aliasesP.authorNameString..k = label .. paramsaliasesP.authorNameString][k - numAuthorParameters

                                            end

							        	end

                                    end

                                end

							end

						else

							citeMismatchj = true

						end

					end

				end

			end

		end

		

		-- use additional properties

		for i in pairs(additionalRefProperties) do

			for j in pairs(citeParams) do

				if not citeMismatchj and i18n"cite"][j][i then

					citeParamsj][i18n"cite"][j][i]] = additionalRefPropertiesi

				else

					citeMismatchj = true

				end

			end

		end



		-- get title of general template for citing web references

		citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2  -- split off namespace from front



		-- get title of template that expands stated-in references into citations

		citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2  -- split off namespace from front



		-- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present

		if citeWeb and not citeMismatch'web' and citeParams'web'][i18n'cite']['web'][aliasesP.referenceURL]] and citeParams'web'][i18n'cite']['web'][aliasesP.title]] then

			-- we need a processed "stated in" for this template

			citeParams'web'][i18n'cite']['web'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, self.linked, true)

			useCite = citeWeb

			useParams = citeParams'web'



		-- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present

		elseif citeQ and not citeMismatch'q' and citeParams'q'][i18n'cite']['q'][aliasesP.statedIn]] then

			useCite = citeQ

			useParams = citeParams'q'

		end



		if useCite and useParams then

			-- if this module is being substituted then build a regular template call, otherwise expand the template

			if mw.isSubsting() then

				for i, v in pairs(useParams) do

					value = value .. "|" .. i .. "=" .. v

				end



				value = "{{" .. useCite .. value .. "}}"

			else

				value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams}

			end



		-- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error

		elseif not referenceEmpty then

			value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>"

		end

	

		if value ~= "" then

			value = {value}  -- create one value object



			if not self.rawValue then

				-- this should become a <ref> tag, so save the reference's hash for later

				value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n'cite']['version']) + version)

			end



			ref = {value}  -- wrap the value object in an array

		end

	end



	return ref

end



-- gets a detail of one particular type for a reference

function State:getReferenceDetail(snaks, dType, raw, link, anyLang)

	local switchLang = anyLang

	local value = nil



	if not snaksdType then

		return nil

	end



	-- if anyLang, first try the local language and otherwise any language

	repeat

		for _, v in ipairs(snaksdType]) do

			value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true)  -- noSpecial = true



			if value then

				break

			end

		end



		if value or not anyLang then

			break

		end



		switchLang = not switchLang

	until anyLang and switchLang



	return value

end



-- gets the details of one particular type for a reference

function State:getReferenceDetails(snaks, dType, raw, link, anyLang)

	local values = {}



	if not snaksdType then

		return {}

	end



	for _, v in ipairs(snaksdType]) do

		-- if nil is returned then it will not be added to the table

		values#values + 1 = self.conf:getValue(v, raw, link, false, anyLang, false, true)  -- noSpecial = true

	end



	return values

end



-- level 1 hook

function State:getAlias(object)

	local value = object.value

	local title = nil



	if value and self.linked then

		if self.conf.entityID:sub(1,1) == "Q" then

			title = mw.wikibase.getSitelink(self.conf.entityID)

		elseif self.conf.entityID:sub(1,1) == "P" then

			title = "d:Property:" .. self.conf.entityID

		end



		if title then

			value = buildWikilink(title, value)

		end

	end



	value = {value}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



-- level 1 hook

function State:getBadge(value)

	value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName)



	if value == "" then

		value = nil

	end



	value = {value}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



function State:callHook(param, hooks, statement, result)

	local valuesArray, refHash



	-- call a parameter's hook if it has been defined and if it has not been called before

	if not resultparam and hooksparam then

		valuesArray = selfhooksparam]](self, statement, param, result, hooks)  -- array with value objects



		-- add to the result

		if #valuesArray > 0 then

			resultparam = valuesArray

			result.count = result.count + 1

		else

			resultparam = {}  -- an empty array to indicate that we've tried this hook already

			return true  -- miss == true

		end

	end



	return false

end



-- iterate through claims, claim's qualifiers or claim's references to collect values

function State:iterate(statements, hooks, matchHook)

	matchHook = matchHook or alwaysTrue



	local matches = false

	local rankPos = nil

	local result, gotRequired



	for _, v in ipairs(statements) do

		-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.)

		matches, rankPos = matchHook(self, v)



		if matches then

			result = {count = 0}  -- collection of arrays with value objects



			local function walk(formatTable)

				local miss



				for i2, v2 in pairs(formatTable.req) do

					-- call a hook, adding its return value to the result

					miss = self:callHook(i2, hooks, v, result)



					if miss then

						-- we miss a required value for this level, so return false

						return false

					end



					if result.count == hooks.count then

						-- we're done if all hooks have been called;

						-- returning at this point breaks the loop

						return true

					end

				end



				for _, v2 in ipairs(formatTable) do

					if result.count == hooks.count then

						-- we're done if all hooks have been called;

						-- returning at this point prevents further childs from being processed

						return true

					end



					if v2.child then

						walk(v2.child)

					end

				end



				return true

			end

			gotRequired = walk(self.parsedFormat)



			-- only append the result if we got values for all required parameters on the root level

			if gotRequired then

				-- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank

				if rankPos and self.conf.foundRank > rankPos then

					self.conf.foundRank = rankPos

				end



				-- append the result

				self.results#self.results + 1 = result



				-- break if we only need a single value

				if self.singleValue then

					break

				end

			end

		end

	end



	return self:out()

end



local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId)

	local id = nil

	local prop = nil



	if arg then

		if arg:sub(1,1) == ":" then

			page = arg

			eid = nil

		elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then

			eid = arg

			page = nil

		else

			prop = arg

		end

	end



	if eid then

		if eid:sub(1,9):lower() == "property:" then

			id = replaceAlias(mw.text.trim(eid:sub(10)))



			if id:sub(1,1):upper() ~= "P" then

				id = ""

			end

		else

			id = replaceAlias(eid)

		end

	elseif page then

		if page:sub(1,1) == ":" then

			page = mw.text.trim(page:sub(2))

		end



		id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or ""

	end



	if not id then

		id = mw.wikibase.getEntityIdForCurrentPage() or ""

	end



	id = id:upper()



	if not mw.wikibase.isValidEntityId(id) then

		id = ""

	end



	return id, prop

end



local function nextArg(args)

	local arg = argsargs.pointer



	if arg then

		args.pointer = args.pointer + 1

		return mw.text.trim(arg)

	else

		return nil

	end

end



local function claimCommand(args, funcName)

	local cfg = Config:new()

	cfg:processFlagOrCommand(funcName)  -- process first command (== function name)



	local lastArg, parsedFormat, formatParams, claims, value

	local hooks = {count = 0}



	-- set the date if given;

	-- must come BEFORE processing the flags

	if argsp.args.date then

		cfg.atDate = {parseDate(argsp.args.date])}

		cfg.periods = {false, true, false}  -- change default time constraint to 'current'

	end



	-- process flags and commands

	repeat

		lastArg = nextArg(args)

	until not cfg:processFlagOrCommand(lastArg)



	-- get the entity ID from either the positional argument, the eid argument or the page argument

	cfg.entityID, cfg.propertyID = getEntityId(lastArg, argsp.args.eid], argsp.args.page], false, argsp.args.globalSiteId])



	if cfg.entityID == "" then

		return ""  -- we cannot continue without a valid entity ID

	end



	cfg.entity = mw.wikibase.getEntity(cfg.entityID)



	if not cfg.propertyID then

		cfg.propertyID = nextArg(args)

	end



	cfg.propertyID = replaceAlias(cfg.propertyID)



	if not cfg.entity or not cfg.propertyID then

		return ""  -- we cannot continue without an entity or a property ID

	end



	cfg.propertyID = cfg.propertyID:upper()



	if not cfg.entity.claims or not cfg.entity.claimscfg.propertyID then

		return ""  -- there is no use to continue without any claims

	end



	claims = cfg.entity.claimscfg.propertyID



	if cfg.states.qualifiersCount > 0 then

		-- do further processing if "qualifier(s)" command was given



		if #args - args.pointer + 1 > cfg.states.qualifiersCount then

			-- claim ID or literal value has been given



			cfg.propertyValue = nextArg(args)

		end



		for i = 1, cfg.states.qualifiersCount do

			-- check if given qualifier ID is an alias and add it

			cfg.qualifierIDsparameters.qualifier..i = replaceAlias(nextArg(args) or ""):upper()

		end

	elseif cfg.statesparameters.reference then

		-- do further processing if "reference(s)" command was given



		cfg.propertyValue = nextArg(args)

	end



	-- check for special property value 'somevalue' or 'novalue'

	if cfg.propertyValue then

		cfg.propertyValue = replaceSpecialChars(cfg.propertyValue)



		if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then

			cfg.propertyValue = " "  -- single space represents 'somevalue', whereas empty string represents 'novalue'

		else

			cfg.propertyValue = mw.text.trim(cfg.propertyValue)

		end

	end



	-- parse the desired format, or choose an appropriate format

	if args"format" then

		parsedFormat, formatParams = parseFormat(args"format"])

	elseif cfg.states.qualifiersCount > 0 then  -- "qualifier(s)" command given

		if cfg.statesparameters.property then  -- "propert(y|ies)" command given

			parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier)

		else

			parsedFormat, formatParams = parseFormat(formats.qualifier)

		end

	elseif cfg.statesparameters.property then  -- "propert(y|ies)" command given

		parsedFormat, formatParams = parseFormat(formats.property)

	else  -- "reference(s)" command given

		parsedFormat, formatParams = parseFormat(formats.reference)

	end



	-- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon

	if cfg.states.qualifiersCount > 0 and not cfg.statesparameters.property then

		cfg.separators"sep"..parameters.separator][1 = {";"}

	end



	-- if only "reference(s)" has been given, set the default separator to none (except when raw)

	if cfg.statesparameters.reference and not cfg.statesparameters.property and cfg.states.qualifiersCount == 0

	   and not cfg.statesparameters.reference].rawValue then

		cfg.separators"sep"][1 = nil

	end



	-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent

	if cfg.states.qualifiersCount == 1 then

		cfg.separators"sep"..parameters.qualifier = cfg.separators"sep"..parameters.qualifier.."1"

	end



	-- process overridden separator values;

	-- must come AFTER tweaking the default separators

	cfg:processSeparators(args)



	-- define the hooks that should be called (getProperty, getQualifiers, getReferences);

	-- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given

	for i, v in pairs(cfg.states) do

		-- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q"

		if formatParamsi or formatParamsi:sub(1, 2)] then

			hooksi = getHookName(i, 1)

			hooks.count = hooks.count + 1

		end

	end



	-- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...);

	-- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given

	if formatParamsparameters.qualifier and cfg.states.qualifiersCount > 0 then

		hooksparameters.qualifier = getHookName(parameters.qualifier, 1)

		hooks.count = hooks.count + 1

	end



	-- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration;

	-- must come AFTER defining the hooks

	if not cfg.statesparameters.property then

		cfg.statesparameters.property = State:new(cfg, parameters.property)



		-- if the "single" flag has been given then this state should be equivalent to "property" (singular)

		if cfg.singleClaim then

			cfg.statesparameters.property].singleValue = true

		end

	end



	-- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values,

	-- which must exist in order to be able to determine if a claim has any references;

	-- must come AFTER defining the hooks

	if cfg.sourcedOnly and not cfg.statesparameters.reference then

		cfg:processFlagOrCommand(p.claimCommands.reference)  -- use singular "reference" to minimize overhead

	end



	-- set the parsed format and the separators (and optional punctuation mark);

	-- must come AFTER creating the additonal states

	cfg:setFormatAndSeparators(cfg.statesparameters.property], parsedFormat)



	-- process qualifier matching values, analogous to cfg.propertyValue

	for i, v in pairs(args) do

		i = tostring(i)



		if i:match('^[Pp]%d+$') or aliasesPi then

			v = replaceSpecialChars(v)



			-- check for special qualifier value 'somevalue'

			if v ~= "" and mw.text.trim(v) == "" then

				v = " "  -- single space represents 'somevalue'

			end



			cfg.qualifierIDsAndValuesreplaceAlias(i):upper()] = v

		end

	end



	-- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated)

	claims = sortOnRank(claims)



	-- then iterate through the claims to collect values

	value = cfg:concatValues(cfg.statesparameters.property]:iterate(claims, hooks, State.claimMatches))  -- pass property state with level 1 hooks and matchHook



	-- if desired, add a clickable icon that may be used to edit the returned values on Wikidata

	if cfg.editable and value ~= "" then

		value = value .. cfg:getEditIcon()

	end



	return value

end



local function generalCommand(args, funcName)

	local cfg = Config:new()

	cfg.curState = State:new(cfg)



	local lastArg

	local value = nil



	repeat

		lastArg = nextArg(args)

	until not cfg:processFlag(lastArg)



	-- get the entity ID from either the positional argument, the eid argument or the page argument

	cfg.entityID = getEntityId(lastArg, argsp.args.eid], argsp.args.page], true, argsp.args.globalSiteId])



	if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then

		return ""  -- we cannot continue without an entity

	end



	-- serve according to the given command

	if funcName == p.generalCommands.label then

		value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName)

	elseif funcName == p.generalCommands.title then

		cfg.inSitelinks = true



		if cfg.entityID:sub(1,1) == "Q" then

			value = mw.wikibase.getSitelink(cfg.entityID)

		end



		if cfg.curState.linked and value then

			value = buildWikilink(value)

		end

	elseif funcName == p.generalCommands.description then

		value = mw.wikibase.getDescription(cfg.entityID)

	else

		local parsedFormat, formatParams

		local hooks = {count = 0}



		cfg.entity = mw.wikibase.getEntity(cfg.entityID)



		if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then

			cfg.curState.singleValue = true

		end



		if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then

			if not cfg.entity.aliases or not cfg.entity.aliasescfg.langCode then

				return ""  -- there is no use to continue without any aliasses

			end



			local aliases = cfg.entity.aliasescfg.langCode



			-- parse the desired format, or parse the default aliases format

			if args"format" then

				parsedFormat, formatParams = parseFormat(args"format"])

			else

				parsedFormat, formatParams = parseFormat(formats.alias)

			end



			-- process overridden separator values;

			-- must come AFTER tweaking the default separators

			cfg:processSeparators(args)



			-- define the hook that should be called (getAlias);

			-- only define the hook if the parameter ("%a") has been given

			if formatParamsparameters.alias then

				hooksparameters.alias = getHookName(parameters.alias, 1)

				hooks.count = hooks.count + 1

			end



			-- set the parsed format and the separators (and optional punctuation mark)

			cfg:setFormatAndSeparators(cfg.curState, parsedFormat)



			-- iterate to collect values

			value = cfg:concatValues(cfg.curState:iterate(aliases, hooks))

		elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then

			if not cfg.entity.sitelinks or not cfg.entity.sitelinkscfg.siteID or not cfg.entity.sitelinkscfg.siteID].badges then

				return ""  -- there is no use to continue without any badges

			end



			local badges = cfg.entity.sitelinkscfg.siteID].badges



			cfg.inSitelinks = true



			-- parse the desired format, or parse the default aliases format

			if args"format" then

				parsedFormat, formatParams = parseFormat(args"format"])

			else

				parsedFormat, formatParams = parseFormat(formats.badge)

			end



			-- process overridden separator values;

			-- must come AFTER tweaking the default separators

			cfg:processSeparators(args)



			-- define the hook that should be called (getBadge);

			-- only define the hook if the parameter ("%b") has been given

			if formatParamsparameters.badge then

				hooksparameters.badge = getHookName(parameters.badge, 1)

				hooks.count = hooks.count + 1

			end



			-- set the parsed format and the separators (and optional punctuation mark)

			cfg:setFormatAndSeparators(cfg.curState, parsedFormat)



			-- iterate to collect values

			value = cfg:concatValues(cfg.curState:iterate(badges, hooks))

		end

	end



	value = value or ""



	if cfg.editable and value ~= "" then

		-- if desired, add a clickable icon that may be used to edit the returned value on Wikidata

		value = value .. cfg:getEditIcon()

	end



	return value

end



-- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args)

local function establishCommands(commandList, commandFunc)

	for _, commandName in pairs(commandList) do

		local function wikitextWrapper(frame)

			local args = copyTable(frame.args)

			args.pointer = 1

			loadI18n(aliasesP, frame)

			return commandFunc(args, commandName)

		end

		pcommandName = wikitextWrapper



		local function luaWrapper(args)

			args = copyTable(args)

			args.pointer = 1

			loadI18n(aliasesP)

			return commandFunc(args, commandName)

		end

		p"_" .. commandName = luaWrapper

	end

end



establishCommands(p.claimCommands, claimCommand)

establishCommands(p.generalCommands, generalCommand)



-- main function that is supposed to be used by wrapper templates

function p.main(frame)

	if not mw.wikibase then return nil end



	local f, args



	loadI18n(aliasesP, frame)



	-- get the parent frame to take the arguments that were passed to the wrapper template

	frame = frame:getParent() or frame



	if not frame.args1 then

		throwError("no-function-specified")

	end



	f = mw.text.trim(frame.args1])



	if f == "main" then

		throwError("main-called-twice")

	end



	assert(p"_"..f], errorText('no-such-function', f))



	-- copy arguments from immutable to mutable table

	args = copyTable(frame.args)



	-- remove the function name from the list

	table.remove(args, 1)



	return p"_"..f](args)

end



return p
Permanently protected module
From Wikipedia, the free encyclopedia


-- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]].



require("strict")

local p = {}

local arg = ...

local i18n



local function loadI18n(aliasesP, frame)

	local title



	if frame then

		-- current module invoked by page/template, get its title from frame

		title = frame:getTitle()

	else

		-- current module included by other module, get its title from ...

		title = arg

	end



	if not i18n then

		i18n = require(title .. "/i18n").init(aliasesP)

	end

end



p.claimCommands = {

	property   = "property",

	properties = "properties",

	qualifier  = "qualifier",

	qualifiers = "qualifiers",

	reference  = "reference",

	references = "references"

}



p.generalCommands = {

	label       = "label",

	title       = "title",

	description = "description",

	alias       = "alias",

	aliases     = "aliases",

	badge       = "badge",

	badges      = "badges"

}



p.flags = {

	linked        = "linked",

	short         = "short",

	raw           = "raw",

	multilanguage = "multilanguage",

	unit          = "unit",

	-------------

	preferred     = "preferred",

	normal        = "normal",

	deprecated    = "deprecated",

	best          = "best",

	future        = "future",

	current       = "current",

	former        = "former",

	          = "edit",

	editAtEnd     = "edit@end",

	mdy           = "mdy",

	single        = "single",

	sourced       = "sourced"

}



p.args = {

	eid  = "eid",

	page = "page",

	date = "date",

	globalSiteId = "globalSiteId"

}



local aliasesP = {

	coord                   = "P625",

	-----------------------

	image                   = "P18",

	author                  = "P50",

	authorNameString        = "P2093",

	publisher               = "P123",

	importedFrom            = "P143",

	wikimediaImportURL      = "P4656",

	statedIn                = "P248",

	pages                   = "P304",

	language                = "P407",

	hasPart                 = "P527",

	publicationDate         = "P577",

	startTime               = "P580",

	endTime                 = "P582",

	chapter                 = "P792",

	retrieved               = "P813",

	referenceURL            = "P854",

	sectionVerseOrParagraph = "P958",

	archiveURL              = "P1065",

	title                   = "P1476",

	formatterURL            = "P1630",

	quote                   = "P1683",

	shortName               = "P1813",

	definingFormula         = "P2534",

	archiveDate             = "P2960",

	inferredFrom            = "P3452",

	typeOfReference         = "P3865",

	column                  = "P3903",

	subjectNamedAs          = "P1810",

	wikidataProperty        = "P1687",

	publishedIn             = "P1433"

}



local aliasesQ = {

	percentage              = "Q11229",

	prolepticJulianCalendar = "Q1985786",

	citeWeb                 = "Q5637226",

	citeQ                   = "Q22321052"

}



local parameters = {

	property  = "%p",

	qualifier = "%q",

	reference = "%r",

	alias     = "%a",

	badge     = "%b",

	separator = "%s",

	general   = "%x"

}



local formats = {

	property              = "%p[%s][%r]",

	qualifier             = "%q[%s][%r]",

	reference             = "%r",

	propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]",

	alias                 = "%a[%s]",

	badge                 = "%b[%s]"

}



local hookNames = {              -- {level_1, level_2}

	parameters.property         = {"getProperty"},

	parameters.reference        = {"getReferences", "getReference"},

	parameters.qualifier        = {"getAllQualifiers"},

	parameters.qualifier.."\\d" = {"getQualifiers", "getQualifier"},

	parameters.alias            = {"getAlias"},

	parameters.badge            = {"getBadge"}

}



-- default value objects, should NOT be mutated but instead copied

local defaultSeparators = {

	"sep"      = {" "},

	"sep%s"    = {","},

	"sep%q"    = {"; "},

	"sep%q\\d" = {", "},

	"sep%r"    = nil,  -- none

	"punc"     = nil   -- none

}



local rankTable = {

	"preferred"  = 1,

	"normal"     = 2,

	"deprecated" = 3

}



local function replaceAlias(id)

	if aliasesPid then

		id = aliasesPid

	end



	return id

end



local function errorText(code, param)

	local text = i18n"errors"][code

	if param then text = mw.ustring.gsub(text, "$1", param) end

	return text

end



local function throwError(errorMessage, param)

	error(errorText(errorMessage, param))

end



local function replaceDecimalMark(num)

	return mw.ustring.gsub(num, "[.]", i18n'numeric']['decimal-mark'], 1)

end



local function padZeros(num, numDigits)

	local numZeros

	local negative = false



	if num < 0 then

		negative = true

		num = num * -1

	end



	num = tostring(num)

	numZeros = numDigits - num:len()



	for _ = 1, numZeros do

		num = "0"..num

	end



	if negative then

		num = "-"..num

	end



	return num

end



local function replaceSpecialChar(chr)

	if chr == '_' then

		-- replace underscores with spaces

		return ' '

	else

		return chr

	end

end



local function replaceSpecialChars(str)

	local chr

	local esc = false

	local strOut = ""



	for i = 1, #str do

		chr = str:sub(i,i)



		if not esc then

			if chr == '\\' then

				esc = true

			else

				strOut = strOut .. replaceSpecialChar(chr)

			end

		else

			strOut = strOut .. chr

			esc = false

		end

	end



	return strOut

end



local function buildWikilink(target, label)

	if not label or target == label then

		return "[[" .. target .. "]]"

	else

		return "[[" .. target .. "|" .. label .. "]]"

	end

end



-- used to make frame.args mutable, to replace #frame.args (which is always 0)

-- with the actual amount and to simply copy tables

local function copyTable(tIn)

	if not tIn then

		return nil

	end



	local tOut = {}



	for i, v in pairs(tIn) do

		tOuti = v

	end



	return tOut

end



-- used to merge output arrays together;

-- note that it currently mutates the first input array

local function mergeArrays(a1, a2)

	for i = 1, #a2 do

		a1#a1 + 1 = a2i

	end



	return a1

end



local function split(str, del)

	local out = {}

	local i, j = str:find(del)



	if i and j then

		out1 = str:sub(1, i - 1)

		out2 = str:sub(j + 1)

	else

		out1 = str

	end



	return out

end



local function parseWikidataURL(url)

	local id



	if url:match('^http[s]?://') then

		id = split(url, "Q")



		if id2 then

			return "Q" .. id2

		end

	end



	return nil

end



local function parseDate(dateStr, precision)

	precision = precision or "d"



	local i, j, index, ptr

	local parts = {nil, nil, nil}



	if dateStr == nil then

		return parts1], parts2], parts3  -- year, month, day

	end



	-- 'T' for snak values, '/' for outputs with '/Julian' attached

	i, j = dateStr:find("[T/]")



	if i then

		dateStr = dateStr:sub(1, i-1)

	end



	local from = 1



	if dateStr:sub(1,1) == "-" then

		-- this is a negative number, look further ahead

		from = 2

	end



	index = 1

	ptr = 1



	i, j = dateStr:find("-", from)



	if i then

		-- year

		partsindex = tonumber(dateStr:sub(ptr, i-1), 10)  -- explicitly give base 10 to prevent error



		if partsindex == -0 then

			partsindex = tonumber("0")  -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead

		end



		if precision == "y" then

			-- we're done

			return parts1], parts2], parts3  -- year, month, day

		end



		index = index + 1

		ptr = i + 1



		i, j = dateStr:find("-", ptr)



		if i then

			-- month

			partsindex = tonumber(dateStr:sub(ptr, i-1), 10)



			if precision == "m" then

				-- we're done

				return parts1], parts2], parts3  -- year, month, day

			end



			index = index + 1

			ptr = i + 1

		end

	end



	if dateStr:sub(ptr) ~= "" then

		-- day if we have month, month if we have year, or year

		partsindex = tonumber(dateStr:sub(ptr), 10)

	end



	return parts1], parts2], parts3  -- year, month, day

end



local function datePrecedesDate(aY, aM, aD, bY, bM, bD)

	if aY == nil or bY == nil then

		return nil

	end

	aM = aM or 1

	aD = aD or 1

	bM = bM or 1

	bD = bD or 1



	if aY < bY then

		return true

	end



	if aY > bY then

		return false

	end



	if aM < bM then

		return true

	end



	if aM > bM then

		return false

	end



	if aD < bD then

		return true

	end



	return false

end



local function getHookName(param, index)

	if hookNamesparam then

		return hookNamesparam][index

	elseif param:len() > 2 then

		return hookNamesparam:sub(1, 2).."\\d"][index

	else

		return nil

	end

end



local function alwaysTrue()

	return true

end



-- The following function parses a format string.

--

-- The example below shows how a parsed string is structured in memory.

-- Variables other than 'str' and 'child' are left out for clarity's sake.

--

-- Example:

-- "A %p B [%s[%q1]] C [%r] D"

--

-- Structure:

-- [

--   {

--     str = "A "

--   },

--   {

--     str = "%p"

--   },

--   {

--     str = " B ",

--     child =

--     [

--       {

--         str = "%s",

--         child =

--         [

--           {

--             str = "%q1"

--           }

--         ]

--       }

--     ]

--   },

--   {

--     str = " C ",

--     child =

--     [

--       {

--         str = "%r"

--       }

--     ]

--   },

--   {

--     str = " D"

--   }

-- ]

--

local function parseFormat(str)

	local chr, esc, param, root, cur, prev, new

	local params = {}



	local function newObject(array)

		local obj = {}  -- new object

		obj.str = ""



		array#array + 1 = obj  -- array{object}

		obj.parent = array



		return obj

	end



	local function endParam()

		if param > 0 then

			if cur.str ~= "" then

				cur.str = "%"..cur.str

				cur.param = true

				paramscur.str = true

				cur.parent.reqcur.str = true

				prev = cur

				cur = newObject(cur.parent)

			end

			param = 0

		end

	end



	root = {}  -- array

	root.req = {}

	cur = newObject(root)

	prev = nil



	esc = false

	param = 0



	for i = 1, #str do

		chr = str:sub(i,i)



		if not esc then

			if chr == '\\' then

				endParam()

				esc = true

			elseif chr == '%' then

				endParam()

				if cur.str ~= "" then

					cur = newObject(cur.parent)

				end

				param = 2

			elseif chr == '[' then

				endParam()

				if prev and cur.str == "" then

					table.remove(cur.parent)

					cur = prev

				end

				cur.child = {}  -- new array

				cur.child.req = {}

				cur.child.parent = cur

				cur = newObject(cur.child)

			elseif chr == ']' then

				endParam()

				if cur.parent.parent then

					new = newObject(cur.parent.parent.parent)

					if cur.str == "" then

						table.remove(cur.parent)

					end

					cur = new

				end

			else

				if param > 1 then

					param = param - 1

				elseif param == 1 then

					if not chr:match('%d') then

						endParam()

					end

				end



				cur.str = cur.str .. replaceSpecialChar(chr)

			end

		else

			cur.str = cur.str .. chr

			esc = false

		end



		prev = nil

	end



	endParam()



	-- make sure that at least one required parameter has been defined

	if not next(root.req) then

		throwError("missing-required-parameter")

	end



	-- make sure that the separator parameter "%s" is not amongst the required parameters

	if root.reqparameters.separator then

		throwError("extra-required-parameter", parameters.separator)

	end



	return root, params

end



local function sortOnRank(claims)

	local rankPos

	local ranks = {{}, {}, {}, {}}  -- preferred, normal, deprecated, (default)

	local sorted = {}



	for _, v in ipairs(claims) do

		rankPos = rankTablev.rank or 4

		ranksrankPos][#ranksrankPos + 1 = v

	end



	sorted = ranks1

	sorted = mergeArrays(sorted, ranks2])

	sorted = mergeArrays(sorted, ranks3])



	return sorted

end



local Config = {}



-- allows for recursive calls

function Config:new()

	local cfg = {}

	setmetatable(cfg, self)

	self.__index = self



	cfg.separators = {

		-- single value objects wrapped in arrays so that we can pass by reference

		"sep"   = {copyTable(defaultSeparators"sep"])},

		"sep%s" = {copyTable(defaultSeparators"sep%s"])},

		"sep%q" = {copyTable(defaultSeparators"sep%q"])},

		"sep%r" = {copyTable(defaultSeparators"sep%r"])},

		"punc"  = {copyTable(defaultSeparators"punc"])}

	}



	cfg.entity = nil

	cfg.entityID = nil

	cfg.propertyID = nil

	cfg.propertyValue = nil

	cfg.qualifierIDs = {}

	cfg.qualifierIDsAndValues = {}



	cfg.bestRank = true

	cfg.ranks = {true, true, false}  -- preferred = true, normal = true, deprecated = false

	cfg.foundRank = #cfg.ranks

	cfg.flagBest = false

	cfg.flagRank = false



	cfg.periods = {true, true, true}  -- future = true, current = true, former = true

	cfg.flagPeriod = false

	cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))}  -- today as {year, month, day}



	cfg.mdyDate = false

	cfg.singleClaim = false

	cfg.sourcedOnly = false

	cfg.editable = false

	cfg.editAtEnd = false



	cfg.inSitelinks = false



	cfg.langCode = mw.language.getContentLanguage().code

	cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode)

	cfg.langObj = mw.language.new(cfg.langCode)



	cfg.siteID = mw.wikibase.getGlobalSiteId()



	cfg.states = {}

	cfg.states.qualifiersCount = 0

	cfg.curState = nil



	cfg.prefetchedRefs = nil



	return cfg

end



local State = {}



function State:new(cfg, type)

	local stt = {}

	setmetatable(stt, self)

	self.__index = self



	stt.conf = cfg

	stt.type = type



	stt.results = {}



	stt.parsedFormat = {}

	stt.separator = {}

	stt.movSeparator = {}

	stt.puncMark = {}



	stt.linked = false

	stt.rawValue = false

	stt.shortName = false

	stt.anyLanguage = false

	stt.unitOnly = false

	stt.singleValue = false



	return stt

end



-- if id == nil then item connected to current page is used

function Config:getLabel(id, raw, link, short)

	local label = nil

	local prefix, title= "", nil



	if not id then

		id = mw.wikibase.getEntityIdForCurrentPage()



		if not id then

			return ""

		end

	end



	id = id:upper()  -- just to be sure



	if raw then

		-- check if given id actually exists

		if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then

			label = id

		end



		prefix, title = "d:Special:EntityPage/", label -- may be nil

	else

		-- try short name first if requested

		if short then

			label = p._property{aliasesP.shortName, p.args.eid = id}  -- get short name



			if label == "" then

				label = nil

			end

		end



		-- get label

		if not label then

			label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels?

		end

	end



	if not label then

		label = ""

	elseif link then

		-- build a link if requested

		if not title then

			if id:sub(1,1) == "Q" then

				title = mw.wikibase.getSitelink(id)

			elseif id:sub(1,1) == "P" then

				-- properties have no sitelink, link to Wikidata instead

				prefix, title = "d:Special:EntityPage/", id

			end

		end



		label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup

		if title then

			label = buildWikilink(prefix .. title, label)

		end

	end



	return label

end



function Config:getEditIcon()

	local value = ""

	local prefix = ""

	local front = "&nbsp;"

	local back = ""



	if self.entityID:sub(1,1) == "P" then

		prefix = "Property:"

	end



	if self.editAtEnd then

		front = '<span style="float:'



		if self.langObj:isRTL() then

			front = front .. 'left'

		else

			front = front .. 'right'

		end



		front = front .. '">'

		back = '</span>'

	end



	value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n'info']['edit-on-wikidata' .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode



	if self.propertyID then

		value = value .. "#" .. self.propertyID

	elseif self.inSitelinks then

		value = value .. "#sitelinks-wikipedia"

	end



	value = value .. "|" .. i18n'info']['edit-on-wikidata' .. "]]"



	return front .. value .. back

end



-- used to create the final output string when it's all done, so that for references the

-- function extensionTag("ref", ...) is only called when they really ended up in the final output

function Config:concatValues(valuesArray)

	local outString = ""

	local j, skip



	for i = 1, #valuesArray do

		-- check if this is a reference

		if valuesArrayi].refHash then

			j = i - 1

			skip = false



			-- skip this reference if it is part of a continuous row of references that already contains the exact same reference

			while valuesArrayj and valuesArrayj].refHash do

				if valuesArrayi].refHash == valuesArrayj].refHash then

					skip = true

					break

				end

				j = j - 1

			end



			if not skip then

				-- add <ref> tag with the reference's hash as its name (to deduplicate references)

				outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArrayi][1], {name = valuesArrayi].refHash})

			end

		else

			outString = outString .. valuesArrayi][1

		end

	end



	return outString

end



function Config:convertUnit(unit, raw, link, short, unitOnly)

	local space = " "

	local label = ""

	local itemID



	if unit == "" or unit == "1" then

		return nil

	end



	if unitOnly then

		space = ""

	end



	itemID = parseWikidataURL(unit)



	if itemID then

		if itemID == aliasesQ.percentage then

			return "%"

		else

			label = self:getLabel(itemID, raw, link, short)



			if label ~= "" then

				return space .. label

			end

		end

	end



	return ""

end



function State:getValue(snak)

	return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2))

end



function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type)

	if snak.snaktype == 'value' then

		local datatype = snak.datavalue.type

		local subtype = snak.datatype

		local datavalue = snak.datavalue.value



		if datatype == 'string' then

			if subtype == 'url' and link then

				-- create link explicitly

				if raw then

					-- will render as a linked number like [1]

					return "[" .. datavalue .. "]"

				else

					return "[" .. datavalue .. " " .. datavalue .. "]"

				end

			elseif subtype == 'commonsMedia' then

				if link then

					return buildWikilink("c:File:" .. datavalue, datavalue)

				elseif not raw then

					return "[[File:" .. datavalue .. "]]"

				else

					return datavalue

				end

			elseif subtype == 'geo-shape' and link then

				return buildWikilink("c:" .. datavalue, datavalue)

			elseif subtype == 'math' and not raw then

				local attribute = nil



				if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then

					attribute = {qid = self.entityID}

				end



				return mw.getCurrentFrame():extensionTag("math", datavalue, attribute)

			elseif subtype == 'external-id' and link then

				local url = p._property{aliasesP.formatterURL, p.args.eid = snak.property}  -- get formatter URL



				if url ~= "" then

					url = mw.ustring.gsub(url, "$1", datavalue)

					return "[" .. url .. " " .. datavalue .. "]"

				else

					return datavalue

				end

			else

				return datavalue

			end

		elseif datatype == 'monolingualtext' then

			if anyLang or datavalue'language' == self.langCode then

				return datavalue'text'

			else

				return nil

			end

		elseif datatype == 'quantity' then

			local value = ""

			local unit



			if not unitOnly then

				-- get value and strip + signs from front

				value = mw.ustring.gsub(datavalue'amount'], "^%+(.+)$", "%1")



				if raw then

					return value

				end



				-- replace decimal mark based on locale

				value = replaceDecimalMark(value)



				-- add delimiters for readability

				value = i18n.addDelimiters(value)

			end



			unit = self:convertUnit(datavalue'unit'], raw, link, short, unitOnly)



			if unit then

				value = value .. unit

			end



			return value

		elseif datatype == 'time' then

			local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr

			local yFactor = 1

			local sign = 1

			local prefix = ""

			local suffix = ""

			local mayAddCalendar = false

			local calendar = ""

			local precision = datavalue'precision'



			if precision == 11 then

				p = "d"

			elseif precision == 10 then

				p = "m"

			else

				p = "y"

				yFactor = 10^(9-precision)

			end



			y, m, d = parseDate(datavalue'time'], p)



			if y < 0 then

				sign = -1

				y = y * sign

			end



			-- if precision is tens/hundreds/thousands/millions/billions of years

			if precision <= 8 then

				yDiv = y / yFactor



				-- if precision is tens/hundreds/thousands of years

				if precision >= 6 then

					mayAddCalendar = true



					if precision <= 7 then

						-- round centuries/millenniums up (e.g. 20th century or 3rd millennium)

						yRound = math.ceil(yDiv)



						if not raw then

							if precision == 6 then

								suffix = i18n'datetime']['suffixes']['millennium'

							else

								suffix = i18n'datetime']['suffixes']['century'

							end



							suffix = i18n.getOrdinalSuffix(yRound) .. suffix

						else

							-- if not verbose, take the first year of the century/millennium

							-- (e.g. 1901 for 20th century or 2001 for 3rd millennium)

							yRound = (yRound - 1) * yFactor + 1

						end

					else

						-- precision == 8

						-- round decades down (e.g. 2010s)

						yRound = math.floor(yDiv) * yFactor



						if not raw then

							prefix = i18n'datetime']['prefixes']['decade-period'

							suffix = i18n'datetime']['suffixes']['decade-period'

						end

					end



					if raw and sign < 0 then

						-- if BCE then compensate for "counting backwards"

						-- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE)

						yRound = yRound + yFactor - 1

					end

				else

					local yReFactor, yReDiv, yReRound



					-- round to nearest for tens of thousands of years or more

					yRound = math.floor(yDiv + 0.5)



					if yRound == 0 then

						if precision <= 2 and y ~= 0 then

							yReFactor = 1e6

							yReDiv = y / yReFactor

							yReRound = math.floor(yReDiv + 0.5)



							if yReDiv == yReRound then

								-- change precision to millions of years only if we have a whole number of them

								precision = 3

								yFactor = yReFactor

								yRound = yReRound

							end

						end



						if yRound == 0 then

							-- otherwise, take the unrounded (original) number of years

							precision = 5

							yFactor = 1

							yRound = y

							mayAddCalendar = true

						end

					end



					if precision >= 1 and y ~= 0 then

						yFull = yRound * yFactor



						yReFactor = 1e9

						yReDiv = yFull / yReFactor

						yReRound = math.floor(yReDiv + 0.5)



						if yReDiv == yReRound then

							-- change precision to billions of years if we're in that range

							precision = 0

							yFactor = yReFactor

							yRound = yReRound

						else

							yReFactor = 1e6

							yReDiv = yFull / yReFactor

							yReRound = math.floor(yReDiv + 0.5)



							if yReDiv == yReRound then

								-- change precision to millions of years if we're in that range

								precision = 3

								yFactor = yReFactor

								yRound = yReRound

							end

						end

					end



					if not raw then

						if precision == 3 then

							suffix = i18n'datetime']['suffixes']['million-years'

						elseif precision == 0 then

							suffix = i18n'datetime']['suffixes']['billion-years'

						else

							yRound = yRound * yFactor

							if yRound == 1 then

								suffix = i18n'datetime']['suffixes']['year'

							else

								suffix = i18n'datetime']['suffixes']['years'

							end

						end

					else

						yRound = yRound * yFactor

					end

				end

			else

				yRound = y

				mayAddCalendar = true

			end



			if mayAddCalendar then

				calendarID = parseWikidataURL(datavalue'calendarmodel'])



				if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then

					if not raw then

						if link then

							calendar = " ("..buildWikilink(i18n'datetime']['julian-calendar'], i18n'datetime']['julian'])..")"

						else

							calendar = " ("..i18n'datetime']['julian'..")"

						end

					else

						calendar = "/"..i18n'datetime']['julian'

					end

				end

			end



			if not raw then

				local ce = nil



				if sign < 0 then

					ce = i18n'datetime']['BCE'

				elseif precision <= 5 then

					ce = i18n'datetime']['CE'

				end



				if ce then

					if link then

						ce = buildWikilink(i18n'datetime']['common-era'], ce)

					end

					suffix = suffix .. " " .. ce

				end



				value = tostring(yRound)



				if m then

					dateStr = self.langObj:formatDate("F", "1-"..m.."-1")



					if d then

						if self.mdyDate then

							dateStr = dateStr .. " " .. d .. ","

						else

							dateStr = d .. " " .. dateStr

						end

					end



					value = dateStr .. " " .. value

				end



				value = prefix .. value .. suffix .. calendar

			else

				value = padZeros(yRound * sign, 4)



				if m then

					value = value .. "-" .. padZeros(m, 2)



					if d then

						value = value .. "-" .. padZeros(d, 2)

					end

				end



				value = value .. calendar

			end



			return value

		elseif datatype == 'globecoordinate' then

			-- logic from https://github.com/DataValues/Geo (v4.0.1)



			local precision, unitsPerDegree, numDigits, strFormat, value, globe

			local latitude, latConv, latValue, latLink

			local longitude, lonConv, lonValue, lonLink

			local latDirection, latDirectionN, latDirectionS, latDirectionEN

			local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN

			local degSymbol, minSymbol, secSymbol, separator



			local latDegrees = nil

			local latMinutes = nil

			local latSeconds = nil

			local lonDegrees = nil

			local lonMinutes = nil

			local lonSeconds = nil



			local latDegSym = ""

			local latMinSym = ""

			local latSecSym = ""

			local lonDegSym = ""

			local lonMinSym = ""

			local lonSecSym = ""



			local latDirectionEN_N = "N"

			local latDirectionEN_S = "S"

			local lonDirectionEN_E = "E"

			local lonDirectionEN_W = "W"



			if not raw then

				latDirectionN = i18n'coord']['latitude-north'

				latDirectionS = i18n'coord']['latitude-south'

				lonDirectionE = i18n'coord']['longitude-east'

				lonDirectionW = i18n'coord']['longitude-west'



				degSymbol = i18n'coord']['degrees'

				minSymbol = i18n'coord']['minutes'

				secSymbol = i18n'coord']['seconds'

				separator = i18n'coord']['separator'

			else

				latDirectionN = latDirectionEN_N

				latDirectionS = latDirectionEN_S

				lonDirectionE = lonDirectionEN_E

				lonDirectionW = lonDirectionEN_W



				degSymbol = "/"

				minSymbol = "/"

				secSymbol = "/"

				separator = "/"

			end



			latitude = datavalue'latitude'

			longitude = datavalue'longitude'



			if latitude < 0 then

				latDirection = latDirectionS

				latDirectionEN = latDirectionEN_S

				latitude = math.abs(latitude)

			else

				latDirection = latDirectionN

				latDirectionEN = latDirectionEN_N

			end



			if longitude < 0 then

				lonDirection = lonDirectionW

				lonDirectionEN = lonDirectionEN_W

				longitude = math.abs(longitude)

			else

				lonDirection = lonDirectionE

				lonDirectionEN = lonDirectionEN_E

			end



			precision = datavalue'precision'



			if not precision or precision <= 0 then

				precision = 1 / 3600  -- precision not set (correctly), set to arcsecond

			end



			-- remove insignificant detail

			latitude = math.floor(latitude / precision + 0.5) * precision

			longitude = math.floor(longitude / precision + 0.5) * precision



			if precision >= 1 - (1 / 60) and precision < 1 then

				precision = 1

			elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then

				precision = 1 / 60

			end



			if precision >= 1 then

				unitsPerDegree = 1

			elseif precision >= (1 / 60)  then

				unitsPerDegree = 60

			else

				unitsPerDegree = 3600

			end



			numDigits = math.ceil(-math.log10(unitsPerDegree * precision))



			if numDigits <= 0 then

				numDigits = tonumber("0")  -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead

			end



			strFormat = "%." .. numDigits .. "f"



			if precision >= 1 then

				latDegrees = strFormat:format(latitude)

				lonDegrees = strFormat:format(longitude)



				if not raw then

					latDegSym = replaceDecimalMark(latDegrees) .. degSymbol

					lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol

				else

					latDegSym = latDegrees .. degSymbol

					lonDegSym = lonDegrees .. degSymbol

				end

			else

				latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits

				lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits



				if precision >= (1 / 60) then

					latMinutes = latConv

					lonMinutes = lonConv

				else

					latSeconds = latConv

					lonSeconds = lonConv



					latMinutes = math.floor(latSeconds / 60)

					lonMinutes = math.floor(lonSeconds / 60)



					latSeconds = strFormat:format(latSeconds - (latMinutes * 60))

					lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60))



					if not raw then

						latSecSym = replaceDecimalMark(latSeconds) .. secSymbol

						lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol

					else

						latSecSym = latSeconds .. secSymbol

						lonSecSym = lonSeconds .. secSymbol

					end

				end



				latDegrees = math.floor(latMinutes / 60)

				lonDegrees = math.floor(lonMinutes / 60)



				latDegSym = latDegrees .. degSymbol

				lonDegSym = lonDegrees .. degSymbol



				latMinutes = latMinutes - (latDegrees * 60)

				lonMinutes = lonMinutes - (lonDegrees * 60)



				if precision >= (1 / 60) then

					latMinutes = strFormat:format(latMinutes)

					lonMinutes = strFormat:format(lonMinutes)



					if not raw then

						latMinSym = replaceDecimalMark(latMinutes) .. minSymbol

						lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol

					else

						latMinSym = latMinutes .. minSymbol

						lonMinSym = lonMinutes .. minSymbol

					end

				else

					latMinSym = latMinutes .. minSymbol

					lonMinSym = lonMinutes .. minSymbol

				end

			end



			latValue = latDegSym .. latMinSym .. latSecSym .. latDirection

			lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection



			value = latValue .. separator .. lonValue



			if link then

				globe = parseWikidataURL(datavalue'globe'])



				if globe then

					globe = mw.wikibase.getLabelByLang(globe, "en"):lower()

				else

					globe = "earth"

				end



				latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_")

				lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_")



				value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]"

			end



			return value

		elseif datatype == 'wikibase-entityid' then

			local label

			local itemID = datavalue'numeric-id'



			if subtype == 'wikibase-item' then

				itemID = "Q" .. itemID

			elseif subtype == 'wikibase-property' then

				itemID = "P" .. itemID

			else

				return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>'

			end



			label = self:getLabel(itemID, raw, link, short)



			if label == "" then

				label = nil

			end



			return label

		else

			return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>'

		end

	elseif snak.snaktype == 'somevalue' and not noSpecial then

		if raw then

			return " "  -- single space represents 'somevalue'

		else

			return i18n'values']['unknown'

		end

	elseif snak.snaktype == 'novalue' and not noSpecial then

		if raw then

			return ""  -- empty string represents 'novalue'

		else

			return i18n'values']['none'

		end

	else

		return nil

	end

end



function Config:getSingleRawQualifier(claim, qualifierID)

	local qualifiers



	if claim.qualifiers then qualifiers = claim.qualifiersqualifierID end



	if qualifiers and qualifiers1 then

		return self:getValue(qualifiers1], true)  -- raw = true

	else

		return nil

	end

end



function Config:snakEqualsValue(snak, value)

	local snakValue = self:getValue(snak, true)  -- raw = true



	if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end



	return snakValue == value

end



function Config:setRank(rank)

	local rankPos



	if rank == p.flags.best then

		self.bestRank = true

		self.flagBest = true  -- mark that 'best' flag was given

		return

	end



	if rank:sub(1,9) == p.flags.preferred then

		rankPos = 1

	elseif rank:sub(1,6) == p.flags.normal then

		rankPos = 2

	elseif rank:sub(1,10) == p.flags.deprecated then

		rankPos = 3

	else

		return

	end



	-- one of the rank flags was given, check if another one was given before

	if not self.flagRank then

		self.ranks = {false, false, false}  -- no other rank flag given before, so unset ranks

		self.bestRank = self.flagBest       -- unsets bestRank only if 'best' flag was not given before

		self.flagRank = true                -- mark that a rank flag was given

	end



	if rank:sub(-1) == "+" then

		for i = rankPos, 1, -1 do

			self.ranksi = true

		end

	elseif rank:sub(-1) == "-" then

		for i = rankPos, #self.ranks do

			self.ranksi = true

		end

	else

		self.ranksrankPos = true

	end

end



function Config:setPeriod(period)

	local periodPos



	if period == p.flags.future then

		periodPos = 1

	elseif period == p.flags.current then

		periodPos = 2

	elseif period == p.flags.former then

		periodPos = 3

	else

		return

	end



	-- one of the period flags was given, check if another one was given before

	if not self.flagPeriod then

		self.periods = {false, false, false}  -- no other period flag given before, so unset periods

		self.flagPeriod = true                -- mark that a period flag was given

	end



	self.periodsperiodPos = true

end



function Config:qualifierMatches(claim, id, value)

	local qualifiers



	if claim.qualifiers then qualifiers = claim.qualifiersid end

	if qualifiers then

		for _, v in pairs(qualifiers) do

			if self:snakEqualsValue(v, value) then

				return true

			end

		end

	elseif value == "" then

		-- if the qualifier is not present then treat it the same as the special value 'novalue'

		return true

	end



	return false

end



function Config:rankMatches(rankPos)

	if self.bestRank then

		return (self.ranksrankPos and self.foundRank >= rankPos)

	else

		return self.ranksrankPos

	end

end



function Config:timeMatches(claim)

	local startTime = nil

	local startTimeY = nil

	local startTimeM = nil

	local startTimeD = nil

	local endTime = nil

	local endTimeY = nil

	local endTimeM = nil

	local endTimeD = nil



	if self.periods1 and self.periods2 and self.periods3 then

		-- any time

		return true

	end



	startTime = self:getSingleRawQualifier(claim, aliasesP.startTime)

	if startTime and startTime ~= "" and startTime ~= " " then

		startTimeY, startTimeM, startTimeD = parseDate(startTime)

	end



	endTime = self:getSingleRawQualifier(claim, aliasesP.endTime)

	if endTime and endTime ~= "" and endTime ~= " " then

		endTimeY, endTimeM, endTimeD = parseDate(endTime)

	end



	if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then

		-- invalidate end time if it precedes start time

		endTimeY = nil

		endTimeM = nil

		endTimeD = nil

	end



	if self.periods1 then

		-- future

		if startTimeY and datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], startTimeY, startTimeM, startTimeD) then

			return true

		end

	end



	if self.periods2 then

		-- current

		if (startTimeY == nil or not datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], startTimeY, startTimeM, startTimeD)) and

		   (endTimeY == nil or datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], endTimeY, endTimeM, endTimeD)) then

			return true

		end

	end



	if self.periods3 then

		-- former

		if endTimeY and not datePrecedesDate(self.atDate1], self.atDate2], self.atDate3], endTimeY, endTimeM, endTimeD) then

			return true

		end

	end



	return false

end



function Config:processFlag(flag)

	if not flag then

		return false

	end



	if flag == p.flags.linked then

		self.curState.linked = true

		return true

	elseif flag == p.flags.raw then

		self.curState.rawValue = true



		if self.curState == self.statesparameters.reference then

			-- raw reference values end with periods and require a separator (other than none)

			self.separators"sep%r"][1 = {" "}

		end



		return true

	elseif flag == p.flags.short then

		self.curState.shortName = true

		return true

	elseif flag == p.flags.multilanguage then

		self.curState.anyLanguage = true

		return true

	elseif flag == p.flags.unit then

		self.curState.unitOnly = true

		return true

	elseif flag == p.flags.mdy then

		self.mdyDate = true

		return true

	elseif flag == p.flags.single then

		self.singleClaim = true

		return true

	elseif flag == p.flags.sourced then

		self.sourcedOnly = true

		return true

	elseif flag == p.flags. then

		self.editable = true

		return true

	elseif flag == p.flags.editAtEnd then

		self.editable = true

		self.editAtEnd = true

		return true

	elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then

		self:setRank(flag)

		return true

	elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then

		self:setPeriod(flag)

		return true

	elseif flag == "" then

		-- ignore empty flags and carry on

		return true

	else

		return false

	end

end



function Config:processFlagOrCommand(flag)

	local param = ""



	if not flag then

		return false

	end



	if flag == p.claimCommands.property or flag == p.claimCommands.properties then

		param = parameters.property

	elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then

		self.states.qualifiersCount = self.states.qualifiersCount + 1

		param = parameters.qualifier .. self.states.qualifiersCount

		self.separators"sep"..param = {copyTable(defaultSeparators"sep%q\\d"])}

	elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then

		param = parameters.reference

	else

		return self:processFlag(flag)

	end



	if self.statesparam then

		return false

	end



	-- create a new state for each command

	self.statesparam = State:new(self, param)



	-- use "%x" as the general parameter name

	self.statesparam].parsedFormat = parseFormat(parameters.general)  -- will be overwritten for param=="%p"



	-- set the separator

	self.statesparam].separator = self.separators"sep"..param  -- will be nil for param=="%p", which will be set separately



	if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then

		self.statesparam].singleValue = true

	end



	self.curState = self.statesparam



	return true

end



function Config:processSeparators(args)

	local sep



	for i, v in pairs(self.separators) do

		if argsi then

			sep = replaceSpecialChars(argsi])



			if sep ~= "" then

				self.separatorsi][1 = {sep}

			else

				self.separatorsi][1 = nil

			end

		end

	end

end



function Config:setFormatAndSeparators(state, parsedFormat)

	state.parsedFormat = parsedFormat

	state.separator = self.separators"sep"

	state.movSeparator = self.separators"sep"..parameters.separator

	state.puncMark = self.separators"punc"

end



-- determines if a claim has references by prefetching them from the claim using getReferences,

-- which applies some filtering that determines if a reference is actually returned,

-- and caches the references for later use

function State:isSourced(claim)

	self.conf.prefetchedRefs = self:getReferences(claim)

	return (#self.conf.prefetchedRefs > 0)

end



function State:resetCaches()

	-- any prefetched references of the previous claim must not be used

	self.conf.prefetchedRefs = nil

end



function State:claimMatches(claim)

	local matches, rankPos



	-- first of all, reset any cached values used for the previous claim

	self:resetCaches()



	-- if a property value was given, check if it matches the claim's property value

	if self.conf.propertyValue then

		matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue)

	else

		matches = true

	end



	-- if any qualifier values were given, check if each matches one of the claim's qualifier values

	for i, v in pairs(self.conf.qualifierIDsAndValues) do

		matches = (matches and self.conf:qualifierMatches(claim, i, v))

	end



	-- check if the claim's rank and time period match

	rankPos = rankTableclaim.rank or 4

	matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim))



	-- if only claims with references must be returned, check if this one has any

	if self.conf.sourcedOnly then

		matches = (matches and self:isSourced(claim))  -- prefetches and caches references

	end



	return matches, rankPos

end



function State:out()

	local result  -- collection of arrays with value objects

	local valuesArray  -- array with value objects

	local sep = nil  -- value object

	local out = {}  -- array with value objects



	local function walk(formatTable, result)

		local valuesArray = {}  -- array with value objects



		for i, v in pairs(formatTable.req) do

			if not resulti or not resulti][1 then

				-- we've got no result for a parameter that is required on this level,

				-- so skip this level (and its children) by returning an empty result

				return {}

			end

		end



		for _, v in ipairs(formatTable) do

			if v.param then

				valuesArray = mergeArrays(valuesArray, resultv.str])

			elseif v.str ~= "" then

				valuesArray#valuesArray + 1 = {v.str}

			end



			if v.child then

				valuesArray = mergeArrays(valuesArray, walk(v.child, result))

			end

		end



		return valuesArray

	end



	-- iterate through the results from back to front, so that we know when to add separators

	for i = #self.results, 1, -1 do

		result = self.resultsi



		-- if there is already some output, then add the separators

		if #out > 0 then

			sep = self.separator1  -- fixed separator

			resultparameters.separator = {self.movSeparator1]}  -- movable separator

		else

			sep = nil

			resultparameters.separator = {self.puncMark1]}  -- optional punctuation mark

		end



		valuesArray = walk(self.parsedFormat, result)



		if #valuesArray > 0 then

			if sep then

				valuesArray#valuesArray + 1 = sep

			end



			out = mergeArrays(valuesArray, out)

		end

	end



	-- reset state before next iteration

	self.results = {}



	return out

end



-- level 1 hook

function State:getProperty(claim)

	local value = {self:getValue(claim.mainsnak)}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



-- level 1 hook

function State:getQualifiers(claim, param)

	local qualifiers



	if claim.qualifiers then qualifiers = claim.qualifiersself.conf.qualifierIDsparam]] end

	if qualifiers then

		-- iterate through claim's qualifier statements to collect their values;

		-- return array with multiple value objects

		return self.conf.statesparam]:iterate(qualifiers, {[parameters.general = hookNamesparameters.qualifier.."\\d"][2], count = 1})  -- pass qualifier state with level 2 hook

	else

		return {}  -- return empty array

	end

end



-- level 2 hook

function State:getQualifier(snak)

	local value = {self:getValue(snak)}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



-- level 1 hook

function State:getAllQualifiers(claim, param, result, hooks)

	local out = {}  -- array with value objects

	local sep = self.conf.separators"sep"..parameters.qualifier][1  -- value object



	-- iterate through the output of the separate "qualifier(s)" commands

	for i = 1, self.conf.states.qualifiersCount do



		-- if a hook has not been called yet, call it now

		if not resultparameters.qualifier..i then

			self:callHook(parameters.qualifier..i, hooks, claim, result)

		end



		-- if there is output for this particular "qualifier(s)" command, then add it

		if resultparameters.qualifier..i and resultparameters.qualifier..i][1 then



			-- if there is already some output, then add the separator

			if #out > 0 and sep then

				out#out + 1 = sep

			end



			out = mergeArrays(out, resultparameters.qualifier..i])

		end

	end



	return out

end



-- level 1 hook

function State:getReferences(claim)

	if self.conf.prefetchedRefs then

		-- return references that have been prefetched by isSourced

		return self.conf.prefetchedRefs

	end



	if claim.references then

		-- iterate through claim's reference statements to collect their values;

		-- return array with multiple value objects

		return self.conf.statesparameters.reference]:iterate(claim.references, {[parameters.general = hookNamesparameters.reference][2], count = 1})  -- pass reference state with level 2 hook

	else

		return {}  -- return empty array

	end

end



-- level 2 hook

function State:getReference(statement)

	local key, citeWeb, citeQ, label

	local params = {}

	local citeParams = {['web' = {}, 'q' = {}}

	local citeMismatch = {}

	local useCite = nil

	local useParams = nil

	local value = ""

	local ref = {}

	local referenceEmpty = true  -- will be set to false if at least one parameter is left unremoved

    local numAuthorParameters = 0

    local numAuthorNameStringParameters = 0

    local tempLink

    local additionalRefProperties = {}  -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and link from an external ID

    local wikidataPropertiesOfSource  -- will contain "Wikidata property" properties of the item in stated in, if any



	local version = 6  -- increment this each time the below logic is changed to avoid conflict errors



	if statement.snaks then

		-- don't include "imported from", which is added by a bot

		if statement.snaksaliasesP.importedFrom then

			statement.snaksaliasesP.importedFrom = nil

		end

		

		-- don't include "Wikimedia import URL"

		if statement.snaksaliasesP.wikimediaImportURL then

			statement.snaksaliasesP.wikimediaImportURL = nil

			-- don't include "retrieved" if no "referenceURL" is present,

			-- as "retrieved" probably belongs to "Wikimedia import URL"

			if statement.snaksaliasesP.retrieved and not statement.snaksaliasesP.referenceURL then

				statement.snaksaliasesP.retrieved = nil

			end

		end



		-- don't include "inferred from", which is added by a bot

		if statement.snaksaliasesP.inferredFrom then

			statement.snaksaliasesP.inferredFrom = nil

		end



		-- don't include "type of reference"

		if statement.snaksaliasesP.typeOfReference then

			statement.snaksaliasesP.typeOfReference = nil

		end



		-- don't include "image" to prevent littering

		if statement.snaksaliasesP.image then

			statement.snaksaliasesP.image = nil

		end



		-- don't include "language" if it is equal to the local one

		if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then

			statement.snaksaliasesP.language = nil

		end

        

        if statement.snaksaliasesP.statedIn and not statement.snaksaliasesP.referenceURL then

        	-- "stated in" was given but "reference URL" was not.

        	-- get "Wikidata property" properties from the item in "stated in"

        	-- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference

        	

        	-- find the "Wikidata property" properties in the item from "stated in"

        	wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, p.args.eid = self.conf:getValue(statement.snaksaliasesP.statedIn][1], true, false)}, ", ", true)

        	for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do

        		if statement.snakswikidataPropertyOfSource and statement.snakswikidataPropertyOfSource][1].datatype == "external-id" then

        			tempLink = self.conf:getValue(statement.snakswikidataPropertyOfSource][1], false, true)  -- not raw, linked

        			if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then  -- getValue returned a URL.

        		    	additionalRefPropertiesaliasesP.referenceURL = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1")  -- the link is in wiki markup, so strip the square brackets and the display text

        			    statement.snakswikidataPropertyOfSource = nil

        			    break

        			end

        		end

        	end

        end

        

        -- don't include "subject named as", but use it as the title when "title" is not present but a URL is

        if statement.snaksaliasesP.subjectNamedAs then

        	if not statement.snaksaliasesP.title and (statement.snaksaliasesP.referenceURL or additionalRefPropertiesaliasesP.referenceURL]) then

        		additionalRefPropertiesaliasesP.title = statement.snaksaliasesP.subjectNamedAs][1].datavalue.value

        	end

        	statement.snaksaliasesP.subjectNamedAs = nil

        end



		-- retrieve all the parameters

		for i in pairs(statement.snaks) do

			label = ""



			-- multiple authors may be given

			if i == aliasesP.author or i == aliasesP.authorNameString then

				paramsi = self:getReferenceDetails(statement.snaks, i, false, self.linked, true)  -- link = true/false, anyLang = true

			elseif i == aliasesP.statedIn then	

				-- Get "stated in" raw, as it is wanted (for Cite Q) even if it doesn't have a local language label.

				paramsaliasesP.statedIn = {self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)}  -- raw = true

			else

				paramsi = {self:getReferenceDetail(statement.snaks, i, false, self.linked and (statement.snaksi][1].datatype ~= 'url'), true)}  -- link = true/false, anyLang = true

			end

			

			if #paramsi == 0 then

				paramsi = nil

			else

				referenceEmpty = false

				

				if statement.snaksi][1].datatype == 'external-id' then

					key = "external-id"

					label = self.conf:getLabel(i)



					if label ~= "" then

						label = label .. " "

					end

				else

					key = i

				end



				-- add the parameter to each matching type of citation

				for j in pairs(citeParams) do

					-- do so if there was no mismatch with a previous parameter

					if not citeMismatchj then

						-- check if this parameter is not mismatching itself

						if i18n'cite'][j][key then

							-- continue if an option is available in the corresponding cite template

							if i18n'cite'][j][key ~= "" then

                                -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter)

                                if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n'cite'][j][aliasesP.author ~= i18n'cite'][j][aliasesP.authorNameString]) then

								    citeParamsj][i18n'cite'][j][key]] = label .. paramsi][1

                                    -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained

								    for k=2, #paramsi do

                                        citeParamsj][i18n'cite'][j][key..k = label .. paramsi][k

							    	end

                                -- handle "author" and "author name string" specially if they use the same template parameter

                                elseif i == aliasesP.author or i == aliasesP.authorNameString then

                                    if paramsaliasesP.author ~= nil then

                                        numAuthorParameters = #paramsaliasesP.author

                                    else

                                        numAuthorParameters = 0

                                    end



                                    if paramsaliasesP.authorNameString ~= nil then

                                        numAuthorNameStringParameters = #paramsaliasesP.authorNameString

                                    else

                                        numAuthorNameStringParameters = 0

                                    end



                                    -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks

                                    -- reason: parameters are added to params each iteration of the loop, not before the loop

                                    if ((statement.snaksaliasesP.author == nil) == (numAuthorParameters == 0)) and ((statement.snaksaliasesP.authorNameString == nil) == (numAuthorNameStringParameters == 0)) then

                                        for k=1, numAuthorParameters + numAuthorNameStringParameters do

                                            if k <= numAuthorParameters then  -- now handling the authors from the "author" property

                                                citeParamsj][i18n'cite'][j][aliasesP.author..k = label .. paramsaliasesP.author][k

                                            else  -- now handling the authors from "author name string"

                                                citeParamsj][i18n'cite'][j][aliasesP.authorNameString..k = label .. paramsaliasesP.authorNameString][k - numAuthorParameters

                                            end

							        	end

                                    end

                                end

							end

						else

							citeMismatchj = true

						end

					end

				end

			end

		end

		

		-- use additional properties

		for i in pairs(additionalRefProperties) do

			for j in pairs(citeParams) do

				if not citeMismatchj and i18n"cite"][j][i then

					citeParamsj][i18n"cite"][j][i]] = additionalRefPropertiesi

				else

					citeMismatchj = true

				end

			end

		end



		-- get title of general template for citing web references

		citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2  -- split off namespace from front



		-- get title of template that expands stated-in references into citations

		citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2  -- split off namespace from front



		-- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present

		if citeWeb and not citeMismatch'web' and citeParams'web'][i18n'cite']['web'][aliasesP.referenceURL]] and citeParams'web'][i18n'cite']['web'][aliasesP.title]] then

			-- we need a processed "stated in" for this template

			citeParams'web'][i18n'cite']['web'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, self.linked, true)

			useCite = citeWeb

			useParams = citeParams'web'



		-- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present

		elseif citeQ and not citeMismatch'q' and citeParams'q'][i18n'cite']['q'][aliasesP.statedIn]] then

			useCite = citeQ

			useParams = citeParams'q'

		end



		if useCite and useParams then

			-- if this module is being substituted then build a regular template call, otherwise expand the template

			if mw.isSubsting() then

				for i, v in pairs(useParams) do

					value = value .. "|" .. i .. "=" .. v

				end



				value = "{{" .. useCite .. value .. "}}"

			else

				value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams}

			end



		-- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error

		elseif not referenceEmpty then

			value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>"

		end

	

		if value ~= "" then

			value = {value}  -- create one value object



			if not self.rawValue then

				-- this should become a <ref> tag, so save the reference's hash for later

				value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n'cite']['version']) + version)

			end



			ref = {value}  -- wrap the value object in an array

		end

	end



	return ref

end



-- gets a detail of one particular type for a reference

function State:getReferenceDetail(snaks, dType, raw, link, anyLang)

	local switchLang = anyLang

	local value = nil



	if not snaksdType then

		return nil

	end



	-- if anyLang, first try the local language and otherwise any language

	repeat

		for _, v in ipairs(snaksdType]) do

			value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true)  -- noSpecial = true



			if value then

				break

			end

		end



		if value or not anyLang then

			break

		end



		switchLang = not switchLang

	until anyLang and switchLang



	return value

end



-- gets the details of one particular type for a reference

function State:getReferenceDetails(snaks, dType, raw, link, anyLang)

	local values = {}



	if not snaksdType then

		return {}

	end



	for _, v in ipairs(snaksdType]) do

		-- if nil is returned then it will not be added to the table

		values#values + 1 = self.conf:getValue(v, raw, link, false, anyLang, false, true)  -- noSpecial = true

	end



	return values

end



-- level 1 hook

function State:getAlias(object)

	local value = object.value

	local title = nil



	if value and self.linked then

		if self.conf.entityID:sub(1,1) == "Q" then

			title = mw.wikibase.getSitelink(self.conf.entityID)

		elseif self.conf.entityID:sub(1,1) == "P" then

			title = "d:Property:" .. self.conf.entityID

		end



		if title then

			value = buildWikilink(title, value)

		end

	end



	value = {value}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



-- level 1 hook

function State:getBadge(value)

	value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName)



	if value == "" then

		value = nil

	end



	value = {value}  -- create one value object



	if #value > 0 then

		return {value}  -- wrap the value object in an array and return it

	else

		return {}  -- return empty array if there was no value

	end

end



function State:callHook(param, hooks, statement, result)

	local valuesArray, refHash



	-- call a parameter's hook if it has been defined and if it has not been called before

	if not resultparam and hooksparam then

		valuesArray = selfhooksparam]](self, statement, param, result, hooks)  -- array with value objects



		-- add to the result

		if #valuesArray > 0 then

			resultparam = valuesArray

			result.count = result.count + 1

		else

			resultparam = {}  -- an empty array to indicate that we've tried this hook already

			return true  -- miss == true

		end

	end



	return false

end



-- iterate through claims, claim's qualifiers or claim's references to collect values

function State:iterate(statements, hooks, matchHook)

	matchHook = matchHook or alwaysTrue



	local matches = false

	local rankPos = nil

	local result, gotRequired



	for _, v in ipairs(statements) do

		-- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.)

		matches, rankPos = matchHook(self, v)



		if matches then

			result = {count = 0}  -- collection of arrays with value objects



			local function walk(formatTable)

				local miss



				for i2, v2 in pairs(formatTable.req) do

					-- call a hook, adding its return value to the result

					miss = self:callHook(i2, hooks, v, result)



					if miss then

						-- we miss a required value for this level, so return false

						return false

					end



					if result.count == hooks.count then

						-- we're done if all hooks have been called;

						-- returning at this point breaks the loop

						return true

					end

				end



				for _, v2 in ipairs(formatTable) do

					if result.count == hooks.count then

						-- we're done if all hooks have been called;

						-- returning at this point prevents further childs from being processed

						return true

					end



					if v2.child then

						walk(v2.child)

					end

				end



				return true

			end

			gotRequired = walk(self.parsedFormat)



			-- only append the result if we got values for all required parameters on the root level

			if gotRequired then

				-- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank

				if rankPos and self.conf.foundRank > rankPos then

					self.conf.foundRank = rankPos

				end



				-- append the result

				self.results#self.results + 1 = result



				-- break if we only need a single value

				if self.singleValue then

					break

				end

			end

		end

	end



	return self:out()

end



local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId)

	local id = nil

	local prop = nil



	if arg then

		if arg:sub(1,1) == ":" then

			page = arg

			eid = nil

		elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then

			eid = arg

			page = nil

		else

			prop = arg

		end

	end



	if eid then

		if eid:sub(1,9):lower() == "property:" then

			id = replaceAlias(mw.text.trim(eid:sub(10)))



			if id:sub(1,1):upper() ~= "P" then

				id = ""

			end

		else

			id = replaceAlias(eid)

		end

	elseif page then

		if page:sub(1,1) == ":" then

			page = mw.text.trim(page:sub(2))

		end



		id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or ""

	end



	if not id then

		id = mw.wikibase.getEntityIdForCurrentPage() or ""

	end



	id = id:upper()



	if not mw.wikibase.isValidEntityId(id) then

		id = ""

	end



	return id, prop

end



local function nextArg(args)

	local arg = argsargs.pointer



	if arg then

		args.pointer = args.pointer + 1

		return mw.text.trim(arg)

	else

		return nil

	end

end



local function claimCommand(args, funcName)

	local cfg = Config:new()

	cfg:processFlagOrCommand(funcName)  -- process first command (== function name)



	local lastArg, parsedFormat, formatParams, claims, value

	local hooks = {count = 0}



	-- set the date if given;

	-- must come BEFORE processing the flags

	if argsp.args.date then

		cfg.atDate = {parseDate(argsp.args.date])}

		cfg.periods = {false, true, false}  -- change default time constraint to 'current'

	end



	-- process flags and commands

	repeat

		lastArg = nextArg(args)

	until not cfg:processFlagOrCommand(lastArg)



	-- get the entity ID from either the positional argument, the eid argument or the page argument

	cfg.entityID, cfg.propertyID = getEntityId(lastArg, argsp.args.eid], argsp.args.page], false, argsp.args.globalSiteId])



	if cfg.entityID == "" then

		return ""  -- we cannot continue without a valid entity ID

	end



	cfg.entity = mw.wikibase.getEntity(cfg.entityID)



	if not cfg.propertyID then

		cfg.propertyID = nextArg(args)

	end



	cfg.propertyID = replaceAlias(cfg.propertyID)



	if not cfg.entity or not cfg.propertyID then

		return ""  -- we cannot continue without an entity or a property ID

	end



	cfg.propertyID = cfg.propertyID:upper()



	if not cfg.entity.claims or not cfg.entity.claimscfg.propertyID then

		return ""  -- there is no use to continue without any claims

	end



	claims = cfg.entity.claimscfg.propertyID



	if cfg.states.qualifiersCount > 0 then

		-- do further processing if "qualifier(s)" command was given



		if #args - args.pointer + 1 > cfg.states.qualifiersCount then

			-- claim ID or literal value has been given



			cfg.propertyValue = nextArg(args)

		end



		for i = 1, cfg.states.qualifiersCount do

			-- check if given qualifier ID is an alias and add it

			cfg.qualifierIDsparameters.qualifier..i = replaceAlias(nextArg(args) or ""):upper()

		end

	elseif cfg.statesparameters.reference then

		-- do further processing if "reference(s)" command was given



		cfg.propertyValue = nextArg(args)

	end



	-- check for special property value 'somevalue' or 'novalue'

	if cfg.propertyValue then

		cfg.propertyValue = replaceSpecialChars(cfg.propertyValue)



		if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then

			cfg.propertyValue = " "  -- single space represents 'somevalue', whereas empty string represents 'novalue'

		else

			cfg.propertyValue = mw.text.trim(cfg.propertyValue)

		end

	end



	-- parse the desired format, or choose an appropriate format

	if args"format" then

		parsedFormat, formatParams = parseFormat(args"format"])

	elseif cfg.states.qualifiersCount > 0 then  -- "qualifier(s)" command given

		if cfg.statesparameters.property then  -- "propert(y|ies)" command given

			parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier)

		else

			parsedFormat, formatParams = parseFormat(formats.qualifier)

		end

	elseif cfg.statesparameters.property then  -- "propert(y|ies)" command given

		parsedFormat, formatParams = parseFormat(formats.property)

	else  -- "reference(s)" command given

		parsedFormat, formatParams = parseFormat(formats.reference)

	end



	-- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon

	if cfg.states.qualifiersCount > 0 and not cfg.statesparameters.property then

		cfg.separators"sep"..parameters.separator][1 = {";"}

	end



	-- if only "reference(s)" has been given, set the default separator to none (except when raw)

	if cfg.statesparameters.reference and not cfg.statesparameters.property and cfg.states.qualifiersCount == 0

	   and not cfg.statesparameters.reference].rawValue then

		cfg.separators"sep"][1 = nil

	end



	-- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent

	if cfg.states.qualifiersCount == 1 then

		cfg.separators"sep"..parameters.qualifier = cfg.separators"sep"..parameters.qualifier.."1"

	end



	-- process overridden separator values;

	-- must come AFTER tweaking the default separators

	cfg:processSeparators(args)



	-- define the hooks that should be called (getProperty, getQualifiers, getReferences);

	-- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given

	for i, v in pairs(cfg.states) do

		-- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q"

		if formatParamsi or formatParamsi:sub(1, 2)] then

			hooksi = getHookName(i, 1)

			hooks.count = hooks.count + 1

		end

	end



	-- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...);

	-- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given

	if formatParamsparameters.qualifier and cfg.states.qualifiersCount > 0 then

		hooksparameters.qualifier = getHookName(parameters.qualifier, 1)

		hooks.count = hooks.count + 1

	end



	-- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration;

	-- must come AFTER defining the hooks

	if not cfg.statesparameters.property then

		cfg.statesparameters.property = State:new(cfg, parameters.property)



		-- if the "single" flag has been given then this state should be equivalent to "property" (singular)

		if cfg.singleClaim then

			cfg.statesparameters.property].singleValue = true

		end

	end



	-- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values,

	-- which must exist in order to be able to determine if a claim has any references;

	-- must come AFTER defining the hooks

	if cfg.sourcedOnly and not cfg.statesparameters.reference then

		cfg:processFlagOrCommand(p.claimCommands.reference)  -- use singular "reference" to minimize overhead

	end



	-- set the parsed format and the separators (and optional punctuation mark);

	-- must come AFTER creating the additonal states

	cfg:setFormatAndSeparators(cfg.statesparameters.property], parsedFormat)



	-- process qualifier matching values, analogous to cfg.propertyValue

	for i, v in pairs(args) do

		i = tostring(i)



		if i:match('^[Pp]%d+$') or aliasesPi then

			v = replaceSpecialChars(v)



			-- check for special qualifier value 'somevalue'

			if v ~= "" and mw.text.trim(v) == "" then

				v = " "  -- single space represents 'somevalue'

			end



			cfg.qualifierIDsAndValuesreplaceAlias(i):upper()] = v

		end

	end



	-- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated)

	claims = sortOnRank(claims)



	-- then iterate through the claims to collect values

	value = cfg:concatValues(cfg.statesparameters.property]:iterate(claims, hooks, State.claimMatches))  -- pass property state with level 1 hooks and matchHook



	-- if desired, add a clickable icon that may be used to edit the returned values on Wikidata

	if cfg.editable and value ~= "" then

		value = value .. cfg:getEditIcon()

	end



	return value

end



local function generalCommand(args, funcName)

	local cfg = Config:new()

	cfg.curState = State:new(cfg)



	local lastArg

	local value = nil



	repeat

		lastArg = nextArg(args)

	until not cfg:processFlag(lastArg)



	-- get the entity ID from either the positional argument, the eid argument or the page argument

	cfg.entityID = getEntityId(lastArg, argsp.args.eid], argsp.args.page], true, argsp.args.globalSiteId])



	if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then

		return ""  -- we cannot continue without an entity

	end



	-- serve according to the given command

	if funcName == p.generalCommands.label then

		value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName)

	elseif funcName == p.generalCommands.title then

		cfg.inSitelinks = true



		if cfg.entityID:sub(1,1) == "Q" then

			value = mw.wikibase.getSitelink(cfg.entityID)

		end



		if cfg.curState.linked and value then

			value = buildWikilink(value)

		end

	elseif funcName == p.generalCommands.description then

		value = mw.wikibase.getDescription(cfg.entityID)

	else

		local parsedFormat, formatParams

		local hooks = {count = 0}



		cfg.entity = mw.wikibase.getEntity(cfg.entityID)



		if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then

			cfg.curState.singleValue = true

		end



		if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then

			if not cfg.entity.aliases or not cfg.entity.aliasescfg.langCode then

				return ""  -- there is no use to continue without any aliasses

			end



			local aliases = cfg.entity.aliasescfg.langCode



			-- parse the desired format, or parse the default aliases format

			if args"format" then

				parsedFormat, formatParams = parseFormat(args"format"])

			else

				parsedFormat, formatParams = parseFormat(formats.alias)

			end



			-- process overridden separator values;

			-- must come AFTER tweaking the default separators

			cfg:processSeparators(args)



			-- define the hook that should be called (getAlias);

			-- only define the hook if the parameter ("%a") has been given

			if formatParamsparameters.alias then

				hooksparameters.alias = getHookName(parameters.alias, 1)

				hooks.count = hooks.count + 1

			end



			-- set the parsed format and the separators (and optional punctuation mark)

			cfg:setFormatAndSeparators(cfg.curState, parsedFormat)



			-- iterate to collect values

			value = cfg:concatValues(cfg.curState:iterate(aliases, hooks))

		elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then

			if not cfg.entity.sitelinks or not cfg.entity.sitelinkscfg.siteID or not cfg.entity.sitelinkscfg.siteID].badges then

				return ""  -- there is no use to continue without any badges

			end



			local badges = cfg.entity.sitelinkscfg.siteID].badges



			cfg.inSitelinks = true



			-- parse the desired format, or parse the default aliases format

			if args"format" then

				parsedFormat, formatParams = parseFormat(args"format"])

			else

				parsedFormat, formatParams = parseFormat(formats.badge)

			end



			-- process overridden separator values;

			-- must come AFTER tweaking the default separators

			cfg:processSeparators(args)



			-- define the hook that should be called (getBadge);

			-- only define the hook if the parameter ("%b") has been given

			if formatParamsparameters.badge then

				hooksparameters.badge = getHookName(parameters.badge, 1)

				hooks.count = hooks.count + 1

			end



			-- set the parsed format and the separators (and optional punctuation mark)

			cfg:setFormatAndSeparators(cfg.curState, parsedFormat)



			-- iterate to collect values

			value = cfg:concatValues(cfg.curState:iterate(badges, hooks))

		end

	end



	value = value or ""



	if cfg.editable and value ~= "" then

		-- if desired, add a clickable icon that may be used to edit the returned value on Wikidata

		value = value .. cfg:getEditIcon()

	end



	return value

end



-- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args)

local function establishCommands(commandList, commandFunc)

	for _, commandName in pairs(commandList) do

		local function wikitextWrapper(frame)

			local args = copyTable(frame.args)

			args.pointer = 1

			loadI18n(aliasesP, frame)

			return commandFunc(args, commandName)

		end

		pcommandName = wikitextWrapper



		local function luaWrapper(args)

			args = copyTable(args)

			args.pointer = 1

			loadI18n(aliasesP)

			return commandFunc(args, commandName)

		end

		p"_" .. commandName = luaWrapper

	end

end



establishCommands(p.claimCommands, claimCommand)

establishCommands(p.generalCommands, generalCommand)



-- main function that is supposed to be used by wrapper templates

function p.main(frame)

	if not mw.wikibase then return nil end



	local f, args



	loadI18n(aliasesP, frame)



	-- get the parent frame to take the arguments that were passed to the wrapper template

	frame = frame:getParent() or frame



	if not frame.args1 then

		throwError("no-function-specified")

	end



	f = mw.text.trim(frame.args1])



	if f == "main" then

		throwError("main-called-twice")

	end



	assert(p"_"..f], errorText('no-such-function', f))



	-- copy arguments from immutable to mutable table

	args = copyTable(frame.args)



	-- remove the function name from the list

	table.remove(args, 1)



	return p"_"..f](args)

end



return p

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook