モジュール:サンドボックス/Was a bee/Wd

-- Original module located at :en:Module:Wd and :en:Module:Wd/i18n.
local p = {}
local arg = ...
local i18n
function loadSubmodules(frame)
	-- internationalization
	if not i18n then
		if frame then
			-- module invoked by page/template, get its title from frame
			i18n = require(frame:getTitle().."/i18n")
		else
			-- module included by other module, get its title from ...
			i18n = require(arg.."/i18n")
		end
	end
end
p.commands = {
	property   = "property",
	properties = "properties",
	qualifier  = "qualifier",
	qualifiers = "qualifiers",
	reference  = "reference",
	references = "references",
	label      = "label",
	title      = "title",
	alias      = "alias",
	aliases    = "aliases"
}
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          = "edit",
	editAtEnd     = "edit@end",
	mdy           = "mdy",
	single        = "single",
	sourced       = "sourced",
}
p.args = {
	eid = "eid"
}
p.aliasesP = {
	coord           = "P625",
	---------------
	author          = "P50",
	publisher       = "P123",
	importedFrom    = "P143",
	statedIn        = "P248",
	pages           = "P304",
	publicationDate = "P577",
	startTime       = "P580",
	endTime         = "P582",
	chapter         = "P792",
	retrieved       = "P813",
	referenceURL    = "P854",
	archiveURL      = "P1065",
	title           = "P1476",
	quote           = "P1683",
	shortName       = "P1813",
	language        = "P407",
	archiveDate     = "P2960"
}
local aliasesQ = {
	percentage              = "Q11229",
	prolepticJulianCalendar = "Q1985786",
	citeWeb                 = "Q5637226",
	citeQ                   = "Q22321052"
}
local parameters = {
	property  = "%p",
	qualifier = "%q",
	reference = "%r",
	alias     = "%a",
	separator = "%s",
	general   = "%x"
}
local formats = {
	property              = "%p%s%r",
	qualifier             = "%q%s%r",
	reference             = "%r",
	propertyWithQualifier = "%p (%q)%s%r",
	alias                 = "%a%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"}
}
-- 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 Config = {}
Config.__index = Config
-- allows for recursive calls
function Config.new()
	local cfg = {}
	setmetatable(cfg, Config)
	
	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.mdyDate = false
	cfg.singleClaim = false
	cfg.sourcedOnly = false
	cfg.editable = false
	cfg.editAtEnd = false
	
	cfg.pageTitle = false
	
	cfg.langCode = mw.language.getContentLanguage().code
	cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode)
	cfg.langObj = mw.language.new(cfg.langCode)
	
	cfg.states = {}
	cfg.states.qualifiersCount = 0
	cfg.curState = nil
	
	cfg.prefetchedRefs = nil
	
	return cfg
end
local State = {}
State.__index = State
function State.new(cfg)
	local stt = {}
	setmetatable(stt, State)
	
	stt.conf = cfg
	
	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
function replaceAlias(ID)
	if p.aliasesPID then
		ID = p.aliasesPID
	end
	
	return ID
end
function applyStringParams(str, ...)
	for i, v in ipairs(arg) do
		str = mw.ustring.gsub(str, "$"..i, v)
	end
	
	return str
end
function unknownDataTypeError(dataType)
	return applyStringParams(i18n'errorsunknown-data-type', dataType)
end
function missingRequiredParameterError()
	return i18n'errorsmissing-required-parameter'
end
function extraRequiredParameterError(param)
	return applyStringParams(i18n'errorsextra-required-parameter', param)
end
function getOrdinalSuffix(num)
	return i18n.getOrdinalSuffix(num)
end
function addDelimiters(num)
	return i18n.addDelimiters(num)
end
function replaceDecimalMark(num)
	return mw.ustring.gsub(num, ".", i18n'numericdecimal-mark', 1)
end
function replaceSpecialChar(chr)
	if chr == '_' then
		-- replace underscores with spaces
		return ' '
	else
		return chr
	end
end
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
function buildWikilink(target, label)
	if not label or target == label then
		return "" .. target .. ""
	else
		return "" .. 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
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
function mergeArrays(a1, a2)
	for i = 1, #a2 do
		a1#a1 + 1 = a2i
	end
	
	return a1
end
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
function parseWikidataURL(url)
	local ID
	
	if url:match('^https?://') then
		ID = split(url, "Q")
		
		if ID2 then
			return "Q" .. ID2
		end
	end
	
	return nil
end
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(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10)  -- remove '+' sign (explicitly give base 10 to prevent error)
		
		if partsindex == -0 then
			partsindex = tonumber("0")  -- for some reason, 'partsindex = 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
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 false
	end
	
	if aM  bM then
		return false
	end
	
	if aD  2 then
		return hookNamesparam:sub(1, 2).."\\d"index
	else
		return nil
	end
end
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"
--   }
-- 
--
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
		error(missingRequiredParameterError())
	end
	
	-- make sure that the separator parameter "%s" is not amongst the required parameters
	if root.reqparameters.separator then
		error(extraRequiredParameterError(parameters.separator))
	end
	
	return root, params
end
function convertRank(rank)
	if rank == "preferred" then
		return 1
	elseif rank == "normal" then
		return 2
	elseif rank == "deprecated" then
		return 3
	else
		return 4  -- default (in its literal sense)
	end
end
function sortOnRank(claims)
	local rankPos
	local ranks =   -- preferred, normal, deprecated, (default)
	local sorted = {}
	
	for i, v in ipairs(claims) do
		rankPos = convertRank(v.rank)
		ranksrankPos#ranksrankPos + 1 = v
	end
	
	sorted = ranks1
	sorted = mergeArrays(sorted, ranks2)
	sorted = mergeArrays(sorted, ranks3)
	
	return sorted
end
-- if ID == nil then item connected to current page is used
function getShortName(ID)
	local args = {p.aliasesP.shortName}
	
	if ID then
		argsp.args.eid = ID
	end
	
	return p._property(args)  -- "property" is single
end
-- if ID == nil then item connected to current page is used
function Config:getLabel(ID, raw, link, short)
	raw = raw or false
	link = link or false
	short = short or false
	
	local label = nil
	local title = nil
	local prefix= ""
	local lang
	
	if raw then
		if not ID then
			label = mw.wikibase.getEntityIdForCurrentPage()
		elseif mw.wikibase.getEntity(ID) then
			label = ID
			
			if ID:sub(1,1) == "P" then
				prefix = "Property:"
			end
		end
		
		prefix = "d:" .. prefix
		
		title = label  -- may be nil
	else
		-- try short name first if requested
		if short then
			label = getShortName(ID)
			
			if label == "" then
				label = nil
			end
		end
		
		-- get label
		if not label then
			label, lang = mw.wikibase.getLabelWithLang(ID)
			
			-- don't allow language fallback
			if lang ~= self.langCode then
				label = nil
			end
		end
	end
	
	if not label then
		label = ""
	elseif link then
		-- build a link if requested
		if not title then
			if not ID then
				title = mw.title.getCurrentTitle().prefixedText
			elseif ID:sub(1,1) == "Q" then
				title = mw.wikibase.sitelink(ID)
			elseif ID:sub(1,1) == "P" then
				-- properties have no sitelink, link to Wikidata instead
				title = ID
				prefix = "d:Property:"
			end
		end
		
		if title then
			label = buildWikilink(prefix .. title, label)
		end
	end
	
	return label
end
function Config:getEditIcon()
	local value = ""
	local prefix = ""
	local front = " "
	local back = ""
	
	if self.entityID:sub(1,1) == "P" then
		prefix = "Property:"
	end
	
	if self.editAtEnd then
		front = 
		back = 
	end
	
	value = "File:Blue pencil.svg|frameless|text-top|10px|alt=" .. i18n'infoedit-on-wikidata' .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode
	
	if self.propertyID then
		value = value .. "#" .. self.propertyID
	elseif self.pageTitle then
		value = value .. "#sitelinks-wikipedia"
	end
	
	value = value .. "|" .. i18n'infoedit-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  tag with the reference's hash as its name (to deduplicate references)
				outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArrayi1, {name = "wikidata-" .. valuesArrayi.refHash})
			end
		else
			outString = outString .. valuesArrayi1
		end
	end
	
	return outString
end
function Config:convertUnit(unit, raw, link, short, unitOnly)
	raw = raw or false
	link = link or false
	short = short or false
	unitOnly = unitOnly or false
	
	local space = " "
	local label = ""
	
	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 Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial)
	raw = raw or false
	link = link or false
	short = short or false
	anyLang = anyLang or false
	unitOnly = unitOnly or false
	noSpecial = noSpecial or false
	
	if snak.snaktype == 'value' then
		if snak.datavalue.type == 'string' then
			if snak.datatype == 'url' and link then
				-- create link explicitly
				if raw then
					-- will render as a linked number like 1
					return "" .. snak.datavalue.value .. ""
				else
					return "" .. snak.datavalue.value .. " " .. snak.datavalue.value .. ""
				end
			elseif snak.datatype == 'commonsMedia' then
				if link then
					return buildWikilink("c:File:" .. snak.datavalue.value, snak.datavalue.value)
				elseif not raw then
					return "File:" .. snak.datavalue.value .. ""
				else
					return snak.datavalue.value
				end
			elseif snak.datatype == 'geo-shape' and link then
				return buildWikilink("c:" .. snak.datavalue.value, snak.datavalue.value)
			elseif snak.datatype == 'math' and not raw then
				return mw.getCurrentFrame():extensionTag("math", snak.datavalue.value)
			else
				return snak.datavalue.value
			end
		elseif snak.datavalue.type == 'monolingualtext' then
			if anyLang then
				return snak.datavalue.value'text', snak.datavalue.value'language'
			elseif snak.datavalue.value'language' == self.langCode then
				return snak.datavalue.value'text'
			else
				return nil
			end
		elseif snak.datavalue.type == 'quantity' then
			local value = ""
			local unit
			
			if not unitOnly then
				-- get value and strip + signs from front
				value = mw.ustring.gsub(snak.datavalue.value'amount', "^\+(.+)$", "%1")
				
				if raw then
					return value
				end
				
				-- replace decimal mark based on locale
				value = replaceDecimalMark(value)
				
				-- add delimiters for readability
				value = addDelimiters(value)
			end
			
			unit = self:convertUnit(snak.datavalue.value'unit', raw, link, short, unitOnly)
			
			if unit then
				value = value .. unit
			end
			
			return value
		elseif snak.datavalue.type == '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 = snak.datavalue.value'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(snak.datavalue.value'time', p)
			
			if y = 6 then
					mayAddCalendar = true
					
					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'datetimesuffixesmillion-years'
						elseif precision == 0 then
							suffix = i18n'datetimesuffixesbillion-years'
						else
							yRound = yRound * yFactor
							if yRound == 1 then
								suffix = i18n'datetimesuffixesyear'
							else
								suffix = i18n'datetimesuffixesyears'
							end
						end
					else
						yRound = yRound * yFactor
					end
				end
			else
				yRound = y
				mayAddCalendar = true
			end
			
			if mayAddCalendar then
				calendarID = parseWikidataURL(snak.datavalue.value'calendarmodel')
				
				if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then
					if not raw then
						if link then
							calendar = " ("..buildWikilink(i18n'datetimejulian-calendar', i18n'datetimejulian')..")"
						else
							calendar = " ("..i18n'datetimejulian'..")"
						end
					else
						calendar = "/"..i18n'datetimejulian'
					end
				end
			end
			
			if not raw then
				local ce = nil
				
				if sign ' .. unknownDataTypeError(snak.datatype) .. 
			end
			
			label = self:getLabel(itemID, raw, link, short)
			
			if label == "" then
				label = nil
			end
			
			return label
		else
			return  .. unknownDataTypeError(snak.datavalue.type) .. 
		end
	elseif snak.snaktype == 'somevalue' and not noSpecial then
		if raw then
			return " "  -- single space represents 'somevalue'
		else
			return i18n'valuesunknown'
		end
	elseif snak.snaktype == 'novalue' and not noSpecial then
		if raw then
			return ""  -- empty string represents 'novalue'
		else
			return i18n'valuesnone'
		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:processFlag(flag)
	if not flag then
		return false
	else
		flag = mw.text.trim(flag)
	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.edit 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
	else
		flag = mw.text.trim(flag)
	end
	
	if flag == p.commands.property or flag == p.commands.properties then
		param = parameters.property
	elseif flag == p.commands.qualifier or flag == p.commands.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.commands.reference or flag == p.commands.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)
	
	-- 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:sub(-1) ~= 's' then
		self.statesparam.singleValue = true
	end
	
	self.curState = self.statesparam
	
	return true
end
function Config:qualifierMatches(claim, ID, value)
	local qualifiers
	
	if claim.qualifiers then qualifiers = claim.qualifiersID end
	if qualifiers then
		for i, 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
	
	local now = os.date('!*t')
	
	startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime)
	if startTime and startTime ~= "" and startTime ~= " " then
		startTimeY, startTimeM, startTimeD = parseDate(startTime)
	end
	
	endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime)
	if endTime and endTime ~= "" and endTime ~= " " then
		endTimeY, endTimeM, endTimeD = parseDate(endTime)
	elseif endTime == " " then
		-- end time is 'unknown', assume it is somewhere in the past;
		-- we can do this by taking the current date as a placeholder for the end time
		endTimeY = now'year'
		endTimeM = now'month'
		endTimeD = now'day'
	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(now'year', now'month', now'day', startTimeY, startTimeM, startTimeD) then
			return true
		end
	end
	
	if self.periods2 then
		-- current
		if (startTimeY == nil or not datePrecedesDate(now'year', now'month', now'day', startTimeY, startTimeM, startTimeD)) and
		   (endTimeY == nil or datePrecedesDate(now'year', now'month', now'day', endTimeY, endTimeM, endTimeD)) then
			return true
		end
	end
	
	if self.periods3 then
		-- former
		if endTimeY and not datePrecedesDate(now'year', now'month', now'day', endTimeY, endTimeM, endTimeD) then
			return true
		end
	end
	
	return false
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 = convertRank(claim.rank)
	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 resulti1 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 i, 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.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)}  -- 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.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)}  -- 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.qualifier1  -- 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..i1 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.reference2, count = 1})  -- pass reference state with level 2 hook
	else
		return {}  -- return empty array
	end
end
-- level 2 hook
-- logic determined based on https://www.wikidata.org/wiki/Help:Sources
function State:getReference(statement)
	local language, referenceURL, title, statedIn, statedInRaw, template
	local authors = {}
	local params = {}
	local citeParams = {}
	local value = ""
	local ref = {}
	
	-- number of parameters that do not go along with "stated in"-sources other than web pages as per https://www.wikidata.org/wiki/Help:Sources;
	-- these are parameters of properties other than "pages(s)" and "chapter" (for books) and "title" and "publication date" (for databases) and also "stated in"
	local hasExtraParams = false
	
	if statement.snaks then
		-- don't include "imported from", which has been added by a bot
		if statement.snaksp.aliasesP.importedFrom then
			statement.snaksp.aliasesP.importedFrom = nil
		end
		
		-- not linked yet because we need the plain value for comparison first
		language = self:getReferenceDetail(statement.snaks, p.aliasesP.language, false, false, false, true)  -- (noUnset = true)
		if language then
			-- not part of a "stated in"-source
			hasExtraParams = true
			
			-- only add language to the reference if it differs from the local one
			if self.conf.langName ~= language then
				if self.linked then
					-- retrieve language again, but this time with link
					paramsp.aliasesP.language = self:getReferenceDetail(statement.snaks, p.aliasesP.language, false, true)  -- link = true
				else
					paramsp.aliasesP.language = language
				end
			end
			
			-- we have to manually unset, since the first call to getReferenceDetail was with noUnset and the second call might not have happened
			statement.snaksp.aliasesP.language = nil
		end
		
		authors = self:getReferenceDetails(statement.snaks, p.aliasesP.author, false, self.linked)  -- link = true/false
		if #authors > 0 then
			-- not part of a "stated in"-source
			hasExtraParams = true
		end
		
		referenceURL = self:getReferenceDetail(statement.snaks, p.aliasesP.referenceURL)
		if referenceURL then
			-- not part of a "stated in"-source
			hasExtraParams = true
		end
		
		-- the next two may be part of a "stated in"-source, so retrieve them already so that they won't count as hasExtraParams
		title    = self:getReferenceDetail(statement.snaks, p.aliasesP.title, false, false, true)  -- anyLang = true
		statedIn = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, false, true, false, true)  -- link = true, (noUnset = true)
		
		-- get title of general template for citing web references
		template = mw.wikibase.sitelink(aliasesQ.citeWeb) or ""
		template = split(template, ":")2  -- split off namespace from front
		
		-- (1) if both "reference URL" and "title" are present, then use the general template for citing web references
		if referenceURL and title and template then
			citeParamsi18n'citeurl'     = referenceURL
			citeParamsi18n'citetitle'   = title
			citeParamsi18n'citewebsite' = statedIn
			
			citeParamsi18n'citelanguage' = paramsp.aliasesP.language
			
			citeParamsi18n'citedate'         = self:getReferenceDetail(statement.snaks, p.aliasesP.publicationDate)
			citeParamsi18n'citeaccess-date'  = self:getReferenceDetail(statement.snaks, p.aliasesP.retrieved)
			citeParamsi18n'citearchive-url'  = self:getReferenceDetail(statement.snaks, p.aliasesP.archiveURL)
			citeParamsi18n'citearchive-date' = self:getReferenceDetail(statement.snaks, p.aliasesP.archiveDate)
			citeParamsi18n'citepublisher'    = self:getReferenceDetail(statement.snaks, p.aliasesP.publisher, false, self.linked)  -- link = true/false
			citeParamsi18n'citequote'        = self:getReferenceDetail(statement.snaks, p.aliasesP.quote, false, false, true)  -- anyLang = true
			
			for i, v in ipairs(authors) do
				citeParamsi18n'citeauthor'..i = v
			end
			
			-- 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(citeParams) do
					value = value .. "|" .. i .. "=" .. v
				end
				
				value = ""
			else
				value = mw.getCurrentFrame():expandTemplate{title=template, args=citeParams}
			end
		else
			-- we need the raw Q-identifier for the next template
			statedInRaw = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true)  -- raw = true
			
			-- the next three may be part of a "stated in"-source, so retrieve them already so that they won't count as hasExtraParams
			paramsp.aliasesP.pages           = self:getReferenceDetail(statement.snaks, p.aliasesP.pages)
			paramsp.aliasesP.chapter         = self:getReferenceDetail(statement.snaks, p.aliasesP.chapter)
			paramsp.aliasesP.publicationDate = self:getReferenceDetail(statement.snaks, p.aliasesP.publicationDate)
			
			-- retrieve the rest of the parameters and make them count as hasExtraParams
			for i in pairs(statement.snaks) do
				paramsi = self:getReferenceDetail(statement.snaks, i, false, self.linked, true)  -- link = true/false, anyLang = true
				hasExtraParams = true
			end
			
			-- get title of template that expands a given stated-in item
			template = mw.wikibase.sitelink(aliasesQ.citeQ) or ""
			template = split(template, ":")2  -- split off namespace from front
			
			-- (2) if "stated in" is present without any parameters not belonging to a "stated in"-source, then use the template that expands the stated-in item
			if statedInRaw and not hasExtraParams and template then
				citeParamsi18n'citepages'   = paramsp.aliasesP.pages
				citeParamsi18n'citechapter' = paramsp.aliasesP.chapter
				citeParamsi18n'citedate-q'  = paramsp.aliasesP.publicationDate
				
				if mw.isSubsting() then
					for i, v in pairs(citeParams) do
						value = value .. "|" .. i .. "=" .. v
					end
					
					value = "" .. statedInRaw .. value .. ""
				else
					citeParams1 = statedInRaw
					value = mw.getCurrentFrame():expandTemplate{title=template, args=citeParams}
				end
			-- (3) else, do some default rendering of name-value pairs, but only if at least "stated in" or "reference URL" is present
			elseif statedIn or referenceURL then
				-- authors were already retrieved; start by adding them up front
				if #authors > 0 then
					citeParams#citeParams + 1 = table.concat(authors, " & ")
				end
				
				-- combine "reference URL" and "title" into one link if both are present
				if referenceURL and title then
					citeParams#citeParams + 1 = "" .. referenceURL .. " " .. title .. ""
				elseif referenceURL then
					citeParams#citeParams + 1 = referenceURL
				elseif title then
					citeParams#citeParams + 1 = title
				end
				
				if statedIn then
					citeParams#citeParams + 1 = statedIn
				end
				
				for i, v in pairs(params) do
					i = self.conf:getLabel(i)
					
					if i ~= "" then
						citeParams#citeParams + 1 = i .. ": " .. v
					end
				end
				
				value = table.concat(citeParams, "; ")
				
				if value ~= "" then
					value = value .. "."
				end
			end
		end
		
		if value ~= "" then
			value = {value}  -- create one value object
			
			if not self.rawValue then
				-- this should become a  tag, so safe the reference's hash for later
				value.refHash = statement.hash
			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, noUnset)
	raw = raw or false
	link = link or false
	anyLang = anyLang or false
	noUnset = noUnset or false
	
	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 i, 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
	
	if not noUnset then
		-- remove detail(s) to make it possible to get the rest of the details in one loop
		snaksdType = nil
	end
	
	return value
end
-- gets the details of one particular type for a reference
function State:getReferenceDetails(snaks, dType, raw, link, anyLang, noUnset)
	raw = raw or false
	link = link or false
	anyLang = anyLang or false
	noUnset = noUnset or false
	
	local values = {}
	
	if not snaksdType then
		return {}
	end
	
	for i, 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
	
	if not noUnset then
		-- remove detail(s) to make it possible to get the rest of the details in one loop
		snaksdType = nil
	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.sitelink(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
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 i, 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 i2, 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
function p.property(frame)
	loadSubmodules(frame)
	return p._property(copyTable(frame.args))
end
function p._property(args)
	loadSubmodules()
	return valueCommand(args, p.commands.property)
end
function p.properties(frame)
	loadSubmodules(frame)
	return p._properties(copyTable(frame.args))
end
function p._properties(args)
	loadSubmodules()
	return valueCommand(args, p.commands.properties)
end
function p.qualifier(frame)
	loadSubmodules(frame)
	return p._qualifier(copyTable(frame.args))
end
function p._qualifier(args)
	loadSubmodules()
	return valueCommand(args, p.commands.qualifier)
end
function p.qualifiers(frame)
	loadSubmodules(frame)
	return p._qualifiers(copyTable(frame.args))
end
function p._qualifiers(args)
	loadSubmodules()
	return valueCommand(args, p.commands.qualifiers)
end
function p.reference(frame)
	loadSubmodules(frame)
	return p._reference(copyTable(frame.args))
end
function p._reference(args)
	loadSubmodules()
	return valueCommand(args, p.commands.reference)
end
function p.references(frame)
	loadSubmodules(frame)
	return p._references(copyTable(frame.args))
end
function p._references(args)
	loadSubmodules()
	return valueCommand(args, p.commands.references)
end
function valueCommand(args, funcName)
	local _ = Config.new()
	_:processFlagOrCommand(funcName)  -- process first command (== function name)
	
	local parsedFormat, formatParams, claims, sep, value
	local hooks = {count = 0}
	
	local nextArg = args1
	local nextIndex = 2
	
	-- process flags and commands
	while _:processFlagOrCommand(nextArg) do
		nextArg = argsnextIndex
		nextIndex = nextIndex + 1
	end
	
	if nextArg then
		nextArg = mw.text.trim(nextArg)
	else
		nextArg = ""
	end
	
	-- check for optional entity ID of either item or property
	if nextArg:sub(1,1):upper() == "Q" then
		_.entityID = nextArg:upper()  -- entity ID of an item was given
		_.entity = mw.wikibase.getEntity(_.entityID)  -- get the item-entity based on the given ID
		_.propertyID = mw.text.trim(argsnextIndex or "")  -- property ID
		nextIndex = nextIndex + 1
	elseif nextArg:sub(1,9):lower() == "property:" then
		_.entityID = replaceAlias(mw.text.trim(nextArg:sub(10))):upper()  -- entity ID of a property was given
		_.entity = mw.wikibase.getEntity(_.entityID)  -- get the property-entity based on the given ID
		_.propertyID = mw.text.trim(argsnextIndex or "")  -- property ID
		nextIndex = nextIndex + 1
	else
		-- no positional entity ID was given, so get entity ID from 'eid' if it was given
		if argsp.args.eid then
			if argsp.args.eid:sub(1,1):upper() == "Q" then
				_.entityID = argsp.args.eid:upper()  
			elseif argsp.args.eid:sub(1,9):lower() == "property:" then
				_.entityID = replaceAlias(mw.text.trim(argsp.args.eid:sub(10))):upper()
			else
				-- might be entity ID of a property, or a bogus entity ID that will fail later on
				_.entityID = replaceAlias(argsp.args.eid):upper()
				
				if _.entityID == "" then
					-- explicitly return if value for 'eid' is empty
					return ""
				end
			end
		else
			_.entityID = mw.wikibase.getEntityIdForCurrentPage()  -- by default, use item-entity connected to current page
		end
		
		_.entity = mw.wikibase.getEntity(_.entityID)
		_.propertyID = nextArg  -- property ID
	end
	
	-- check if given property ID is an alias
	_.propertyID = replaceAlias(_.propertyID):upper()
	
	if _.states.qualifiersCount > 0 then
		-- do further processing if "qualifier(s)" command was given
		
		if #args - nextIndex + 1 > _.states.qualifiersCount then
			-- claim ID or literal value has been given
			
			_.propertyValue = mw.text.trim(argsnextIndex)
			nextIndex = nextIndex + 1
		end
		
		for i = 1, _.states.qualifiersCount do
			nextArg = mw.text.trim(argsnextIndex or "")  -- is a qualifierID
			nextIndex = nextIndex + 1
			
			-- check if given qualifier ID is an alias and add it
			_.qualifierIDsparameters.qualifier..i = replaceAlias(nextArg):upper()
		end
	elseif _.statesparameters.reference then
		-- do further processing if "reference(s)" command was given
		
		nextArg = argsnextIndex  -- claim ID or literal value (possibly nil)
		nextIndex = nextIndex + 1
		
		if nextArg then
			_.propertyValue = mw.text.trim(nextArg)
		end
	end
	
	-- check for special property value 'somevalue' or 'novalue'
	if _.propertyValue then
		_.propertyValue = replaceSpecialChars(_.propertyValue)
		
		if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then
			_.propertyValue = " "  -- single space represents 'somevalue', whereas empty string represents 'novalue'
		else
			_.propertyValue = mw.text.trim(_.propertyValue)
		end
	end
	
	-- parse the desired format, or choose an appropriate format
	if args"format" then
		parsedFormat, formatParams = parseFormat(args"format")
	elseif _.states.qualifiersCount > 0 then  -- "qualifier(s)" command given
		if _.statesparameters.property then  -- "propert(y|ies)" command given
			parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier)
		else
			parsedFormat, formatParams = parseFormat(formats.qualifier)
		end
	elseif _.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 _.states.qualifiersCount > 0 and not _.statesparameters.property then
		_.separators"sep"..parameters.separator1 = {";"}
	end
	
	-- if only "reference(s)" has been given, set the default separator to none (except when raw)
	if _.statesparameters.reference and not _.statesparameters.property and _.states.qualifiersCount == 0
	   and not _.statesparameters.reference.rawValue then
		_.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;
	-- must come BEFORE overriding the separator values
	if _.states.qualifiersCount == 1 then
		_.separators"sep"..parameters.qualifier = _.separators"sep"..parameters.qualifier.."1"
	end
	
	-- process overridden separator values;
	-- must come AFTER parsing the format
	for i, v in pairs(_.separators) do
		if argsi then
			sep = replaceSpecialChars(argsi)
			
			if sep ~= "" then
				_.separatorsi1 = {sep}
			else
				_.separatorsi1 = nil
			end
		end
	end
	
	-- 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(_.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 _.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 _.statesparameters.property then
		_.statesparameters.property = State.new(_)
		
		-- if the "single" flag has been given then this state should be equivalent to "property" (singular)
		if _.singleClaim then
			_.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 _.sourcedOnly and not _.statesparameters.reference then
		_:processFlagOrCommand(p.commands.reference)  -- use singular "reference" to minimize overhead
	end
	
	-- set the parsed format and the separators (and optional punctuation mark)
	_.statesparameters.property.parsedFormat = parsedFormat
	_.statesparameters.property.separator = _.separators"sep"
	_.statesparameters.property.movSeparator = _.separators"sep"..parameters.separator
	_.statesparameters.property.puncMark = _.separators"punc"
	
	-- process qualifier matching values, analogous to _.propertyValue
	for i, v in pairs(args) do
		i = tostring(i)
		
		if i:match('^Pp%d+$') or p.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
			
			_.qualifierIDsAndValuesreplaceAlias(i):upper() = v
		end
	end
	
	if _.entity and _.entity.claims then claims = _.entity.claims_.propertyID end
	if claims then
		-- 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 = _:concatValues(_.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 _.editable and value ~= "" then
			value = value .. _:getEditIcon()
		end
		
		return value
	else
		return ""
	end
end
function p.label(frame)
	loadSubmodules(frame)
	return p._label(copyTable(frame.args))
end
function p._label(args)
	loadSubmodules()
	return nameCommand(args, p.commands.label)
end
function p.title(frame)
	loadSubmodules(frame)
	return p._title(copyTable(frame.args))
end
function p._title(args)
	loadSubmodules()
	return nameCommand(args, p.commands.title)
end
function p.alias(frame)
	loadSubmodules(frame)
	return p._alias(copyTable(frame.args))
end
function p._alias(args)
	loadSubmodules()
	return nameCommand(args, p.commands.alias)
end
function p.aliases(frame)
	loadSubmodules(frame)
	return p._aliases(copyTable(frame.args))
end
function p._aliases(args)
	loadSubmodules()
	return nameCommand(args, p.commands.aliases)
end
function nameCommand(args, funcName)
	local _ = Config.new()
	_.curState = State.new(_)
	
	local ID = nil
	local value = nil
	
	local nextArg = args1
	local nextIndex = 2
	
	while _:processFlag(nextArg) do
		nextArg = argsnextIndex
		nextIndex = nextIndex + 1
	end
	
	if nextArg then
		nextArg = mw.text.trim(nextArg)
	else
		nextArg = ""
	end
	
	-- check for optional entity ID of either item or property
	if nextArg:sub(1,1):upper() == "Q" then
		ID = nextArg:upper()  -- entity ID of an item was given
	elseif nextArg:sub(1,9):lower() == "property:" then
		ID = replaceAlias(mw.text.trim(nextArg:sub(10))):upper()  -- entity ID of a property was given
	else
		-- might be entity ID of a property, or a bogus entity ID that will fail later on
		ID = replaceAlias(nextArg):upper()
		
		if ID:sub(1,1) ~= "P" then
			if ID == "" then
				-- no positional entity ID was given, so get entity ID from 'eid' if it was given
				if argsp.args.eid then
					if argsp.args.eid:sub(1,1):upper() == "Q" then
						ID = argsp.args.eid:upper()  
					elseif argsp.args.eid:sub(1,9):lower() == "property:" then
						ID = replaceAlias(mw.text.trim(argsp.args.eid:sub(10))):upper()
					else
						-- might be entity ID of a property, or a bogus entity ID that will fail later on
						ID = replaceAlias(argsp.args.eid):upper()
						
						if ID == "" then
							-- explicitly return if value for 'eid' is empty
							return ""
						end
					end
				else
					ID = nil
				end
			end
		end
	end
	
	if ID then
		_.entityID = ID
	else
		_.entityID = mw.wikibase.getEntityIdForCurrentPage()
	end
	
	-- serve according to the given command
	if funcName == p.commands.label then
		value = _:getLabel(ID, _.curState.rawValue, _.curState.linked, _.curState.shortName)
	elseif funcName == p.commands.title then
		_.pageTitle = true
		
		if not ID then
			value = mw.title.getCurrentTitle().prefixedText
		elseif ID:sub(1,1) == "Q" then
			value = mw.wikibase.sitelink(ID)
		end
		
		if _.curState.linked and value then
			value = buildWikilink(value)
		end
	elseif funcName == p.commands.alias or funcName == p.commands.aliases then
		local aliases, parsedFormat, formatParams, sep
		local hooks = {count = 0}
		
		if funcName == p.commands.alias then
			_.curState.singleValue = true
		end
		
		-- 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 parsing the format
		for i, v in pairs(_.separators) do
			if argsi then
				sep = replaceSpecialChars(argsi)
				
				if sep ~= "" then
					_.separatorsi1 = {sep}
				else
					_.separatorsi1 = nil
				end
			end
		end
		
		-- 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)
		_.curState.parsedFormat = parsedFormat
		_.curState.separator = _.separators"sep"
		_.curState.movSeparator = _.separators"sep"..parameters.separator
		_.curState.puncMark = _.separators"punc"
		
		_.entity = mw.wikibase.getEntity(ID)
		
		if _.entity and _.entity.aliases then aliases = _.entity.aliases_.langCode end
		if aliases then
			value = _:concatValues(_.curState:iterate(aliases, hooks))
		end
	end
	
	value = value or ""
	
	if _.editable and value ~= "" then
		-- if desired, add a clickable icon that may be used to edit the returned value on Wikidata
		value = value .. _:getEditIcon()
	end
	
	return value
end
-- main function that is supposed to be used by wrapper templates
function p.main(frame)
	local f, args, i, v
	
	loadSubmodules(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
		error(i18n"errors""no-function-specified")
	end
	
	f = mw.text.trim(frame.args1)
	
	if f == "main" then
		error(i18n"errors""main-called-twice")
	end
	
	assert(p"_"..f, applyStringParams(i18n'errorsno-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