Відкрити головне меню
{{i}} Документація модуля[перегляд] [редагувати] [історія] [очистити кеш]

This модуль is intended to fetch data from Wikidata with or without a link to the connected Wikipedia article.

The модуль is under development and is not yet complete. Of the available datatypes, it currently supports strings, quantities, monolingual text, time and globe coordinates.

UsageРедагувати

{{Шаблон:Hashinvoke:wd|function|flag1|flag2|flag3|arg1|arg2|arg3}}

FunctionsРедагувати

  • property
    Returns the requested property or list of properties from the given item.
  • qualifier
    Returns the requested qualifier or list of qualifiers from the given property of the given item.
  • propertyWithQualifier
    Returns the requested property and its requested qualifier or a list thereof from the given item, with the qualifier formatted between parentheses behind the property and with units of measurement if applicable.
  • label
    Returns the label of the given item or property.
  • main
    Intended for use by wrapper templates (around this module) to invoke one of the above functions, returning their respective output.

ParametersРедагувати

FlagsРедагувати

  • linked
    Creates a link to the Wikipedia article that is connected to the property or qualifier if it exists.
    If this parameter is omitted, then the plain property or qualifier value will be returned.
  • unit
    Appends the unit of measurement to the value if applicable.
    For units, the linked flag behaves slightly different in that it links the unit of measurement where it would normally link the property or qualifier value.
    If this flag is used with time datatypes, then it makes the returned dates more verbose (e.g. 11 February 1731). Either way, dates in the Julian calendar stored with a precision of days through millenniums will have "Julian" attached to the output, where without the unit flag it will look like 1731-2-11/Julian (which may be split off using the {{#titleparts}} template function).
    If this flag is used with globe coordinate datatypes, then it adds the various symbols to the returned value (e.g. 52°5'3"N, 4°19'3"E). Without the unit flag, globe coordinates will look like 52/5/3/N/4/19/3/E (which may be split into parts using the {{#titleparts}} template function).
  • short (EXPENSIVE when used)
    Gets the short name (P1813) of any item returned if they have one attached. If that is not the case, then the default behaviour of returning the item label will occur.
  • single
    Returns only a single value instead of multiple (if multiple claims match). The returned value is the first match found from the best-ranked claims.
  • One of:
    best (default)
    preferred
    normal
    deprecated
    Sets a rank constraint for the selected claims.
    The latter three can be followed by a + or a -, e.g.normal+ or preferred-, where the first selects claims with a 'normal' rank or higher and the second selects claims with a 'preferred' rank or lower. To get claims of all ranks, use preferred- or deprecated+.
    Output is always sorted from highest rank to lowest (regardless of this flag being set).
  • One of:
    future
    current
    former
    Sets a time constraint for the selected claims. Uses the claims' qualifiers of start time (P580) and end time (P582) to determine if the claim is valid for the selected time period.

ArgumentsРедагувати

  • property | [<flags>] | [<item_id>] | <property_id>
    • <flags> (optional)
      A list of flags (see above).
    • <item_id> (optional, EXPENSIVE when used)
      Q-identifier of the item to be accessed (e.g. Q55).
      If this parameter is omitted, then the item connected to the current page will be used.
    • <property_id>
      P-identifier of the property to be accessed (e.g. P38).


  • qualifier | [<flags>] | [<item_id>] | <property_id> | [<claim_id_or_value>] | <qualifier_id>
    • <flags> (optional)
      A list of flags (see above).
    • <item_id> (optional, EXPENSIVE when used)
      Q-identifier of the item to be accessed (e.g. Q55).
      If this parameter is omitted, then the item connected to the current page will be used.
    • <property_id>
      P-identifier of the property to be accessed (e.g. P38).
    • <claim_id_or_value> (optional)
      Either the Q-identifier of the particular claim to be accessed (e.g. Q6655) or a literal value (i.e. string or quantity etc., no item label) equal to the claim to be accessed.
      Dates as literal values must be formatted year-month-day (e.g. 1731-2-11) for dates with a precision of days, year-month (e.g. 1731-2) for dates with a precision of months, and year (e.g. 1731) for dates of lesser precision without any spaces or leading zeros. Dates BCE require a minus sign in front of the year (e.g. -2950-1-31). Dates stored in the Julian calendar must have /Julian attached to the end (e.g. 1731-2-11/Julian). Decades like the 2010s must be given as 2010 (but the 2010s BCE as -2019), centuries like the 20th century as 1901 (but the 20th century BCE as -2000), and millenniums like the 3rd millennium as 2001 (but the 3rd millennium BCE as -3000).
      Globe coordinates as literal values must be formatted with forward slashes (i.e. /) between the parts and no symbols (e.g. 52/5/3/N/4/19/3/E) without any spaces or leading zeros.
      The special type 'no value' can be given by entering the empty string (i.e. ||) and the special type 'unknown value' can be given by entering a space (i.e. ||).
      If this parameter is omitted, then all claims (matching any other constraints) within the property will be accessed.
    • <qualifier_id>
      P-identifier of the qualifier to be accessed (e.g. P518).


  • propertyWithQualifier | [<flags>] | [<item_id>] | <property_id> | [<claim_id_or_value>] | <qualifier_id>
    This function is equivalent to qualifier, except that the property is also returned with the qualifier between parentheses behind it. It also enables the unit flag (see above).


  • label | [<flags>] | [<item_or_property_id>]
    • <flags> (optional)
      A list of flags (see above).
    • <item_or_property_id> (optional)
      Q-identifier of the item (e.g. Q55) or P-identifier of the property (e.g. P38) to be accessed.
      If this parameter is omitted, then the item connected to the current page will be used.


  • main | <function> | [<parameters>]
    • <function>
      The name of one of the above functions.
    • <parameters> (optional)
      A list of flags and arguments applicable to the chosen function.

ExamplesРедагувати

propertyРедагувати

  • Q55 (Нідерланди), P395 (код на номерних знаках): [string]
{{Шаблон:Hashinvoke:wd|property|Q55|P395}} → NL


  • Q55 (Нідерланди), P1549 (катойконім): [monolingual text]
{{Шаблон:Hashinvoke:wd|property|Q55|P1549}} → голандець, нідерландець, голандка, нідерландка, голандці, нідерландці


property with rankРедагувати

  • Q55 (Нідерланди), P38 (валюта): [item label]
best rank (default)
{{Шаблон:Hashinvoke:wd|property|Q55|P38}} → євро
{{Шаблон:Hashinvoke:wd|property|best|Q55|P38}} → євро
{{Шаблон:Hashinvoke:wd|property|best|linked|Q55|P38}}євро
other ranks
{{Шаблон:Hashinvoke:wd|property|preferred|Q55|P38}} → євро
{{Шаблон:Hashinvoke:wd|property|preferred-|linked|Q55|P38}}євро; Нідерландський гульден, долар США
{{Шаблон:Hashinvoke:wd|property|normal+|Q55|P38}} → євро; Нідерландський гульден, долар США
{{Шаблон:Hashinvoke:wd|property|normal|Q55|P38}} → Нідерландський гульден, долар США


property with time constraintРедагувати

  • Q55 (Нідерланди), P38 (валюта): [item label]
{{Шаблон:Hashinvoke:wd|property|normal+|current|Q55|P38}} → євро; долар США
{{Шаблон:Hashinvoke:wd|property|normal+|former|Q55|P38}} → Нідерландський гульден


property with short nameРедагувати

  • Q55 (Нідерланди), P38 (валюта): [item label]
{{Шаблон:Hashinvoke:wd|property|normal|current|short|Q55|P38}} → долар США
{{Шаблон:Hashinvoke:wd|property|normal|current|short|linked|Q55|P38}}долар США


property with unitРедагувати

  • Q55 (Нідерланди), P2884 (мережева напруга): [quantity]
{{Шаблон:Hashinvoke:wd|property|unit|Q55|P2884}} → 230 вольт
{{Шаблон:Hashinvoke:wd|property|unit|linked|Q55|P2884}} → 230 вольт


property with return of globe coordinateРедагувати

  • Q55 (Нідерланди), P625 (географічні координати): [globe coordinate]
{{Шаблон:Hashinvoke:wd|property|Q55|P625}} → 52/19/N/5/33/E
{{Шаблон:Hashinvoke:wd|property|unit|Q55|P625}} → 52°19'N, 5°33'E
{{Шаблон:Hashinvoke:wd|property|unit|linked|Q55|P625}}52°19'N, 5°33'E


property with return of single valueРедагувати

  • Q55 (Нідерланди), P150 (адміністративно-територіально поділяється на): [item label]
{{Шаблон:Hashinvoke:wd|property|Q55|P150}} → Гронінген, Фрисландія, Гелдерланд, Південна Голландія, Північна Голландія, Оверейсел, Північний Брабант, Лімбург, Флеволанд, Зеландія, Дренте, Утрехт, Бонайре, Сінт-Естатіус, Саба
{{Шаблон:Hashinvoke:wd|property|single|Q55|P150}} → Гронінген


qualifierРедагувати

qualifier with rankРедагувати

  • Q55 (Нідерланди), P38 (валюта), P518 (стосується частини): [item label]
best rank (default)
{{Шаблон:Hashinvoke:wd|qualifier|Q55|P38|P518}} → European Netherlands
{{Шаблон:Hashinvoke:wd|qualifier|best|Q55|P38|P518}} → European Netherlands
{{Шаблон:Hashinvoke:wd|qualifier|best|linked|Q55|P38|P518}} → European Netherlands
other ranks
{{Шаблон:Hashinvoke:wd|qualifier|preferred|Q55|P38|P518}} → European Netherlands
{{Шаблон:Hashinvoke:wd|qualifier|preferred-|linked|Q55|P38|P518}} → European Netherlands; Нідерландські Карибські острови
{{Шаблон:Hashinvoke:wd|qualifier|normal+|Q55|P38|P518}} → European Netherlands; Нідерландські Карибські острови
{{Шаблон:Hashinvoke:wd|qualifier|normal|Q55|P38|P518}} → Нідерландські Карибські острови


qualifier with time constraintРедагувати

  • Q55 (Нідерланди), P38 (валюта), P518 (стосується частини): [item label]
{{Шаблон:Hashinvoke:wd|qualifier|normal+|current|Q55|P38|P518}} → European Netherlands; Нідерландські Карибські острови
{{Шаблон:Hashinvoke:wd|qualifier|normal+|former|Q55|P38|P518}}


qualifier with claim IDРедагувати

  • Q55 (Нідерланди), P38 (валюта), Q4917 (долар США), P518 (стосується частини): [item label]
{{Шаблон:Hashinvoke:wd|qualifier|Q55|P38|Q4917|P518}} → Нідерландські Карибські острови
{{Шаблон:Hashinvoke:wd|qualifier|linked|Q55|P38|Q4917|P518}}Нідерландські Карибські острови


qualifier with literal valueРедагувати

  • Q55 (Нідерланди), P2884 (мережева напруга), 230 volt, P2144 (частота): [quantity]
{{Шаблон:Hashinvoke:wd|qualifier|Q55|P2884|230|P2144}} → 50


qualifier with unitРедагувати

  • Q55 (Нідерланди), P2884 (мережева напруга), 230 volt, P2144 (частота): [quantity]
{{Шаблон:Hashinvoke:wd|qualifier|unit|Q55|P2884|230|P2144}} → 50 герц
{{Шаблон:Hashinvoke:wd|qualifier|unit|linked|Q55|P2884|230|P2144}} → 50 герц


qualifier with return of timeРедагувати

  • Q55 (Нідерланди), P38 (валюта), Q788472 (Нідерландський гульден), P582 (час/дата закінчення): [time]
{{Шаблон:Hashinvoke:wd|qualifier|Q55|P38|Q788472|P582}} → 2002-1-1
{{Шаблон:Hashinvoke:wd|qualifier|unit|Q55|P38|Q788472|P582}} → 1 січень 2002


propertyWithQualifierРедагувати

  • Q55 (Нідерланди), P2884 (мережева напруга), P2144 (частота): [quantity]
{{Шаблон:Hashinvoke:wd|propertyWithQualifier|Q55|P2884|P2144}} → 230 вольт (50 герц)
{{Шаблон:Hashinvoke:wd|propertyWithQualifier|linked|Q55|P2884|P2144}} → 230 вольт (50 герц)


  • Q55 (Нідерланди), P38 (валюта), P518 (стосується частини): [item label]
{{Шаблон:Hashinvoke:wd|propertyWithQualifier|normal+|current|Q55|P38|P518}} → євро (European Netherlands); долар США (Нідерландські Карибські острови)
{{Шаблон:Hashinvoke:wd|propertyWithQualifier|normal+|current|short|linked|Q55|P38|P518}}євро (European Netherlands); долар США (Нідерландські Карибські острови)


If the module is transcluded on the Netherlands page (which is linked to Q55), then the Q55 can be omitted:

  • P38 (валюта), P518 (стосується частини): [item label]
{{Шаблон:Hashinvoke:wd|propertyWithQualifier|normal+|current|P38|P518}} → євро (European Netherlands); долар США (Нідерландські Карибські острови)


labelРедагувати

See alsoРедагувати

  • {{Wikidata}}, a user-friendly wrapper template for this module.
local p = {}

local State = {}
State.__index = State

-- allows for recursive calls
function State.new()
	local stt = {}
	setmetatable(stt, State)
	
	stt.outPreferred = {}
	stt.outNormal = {}
	stt.outDeprecated = {}
	
	stt.bestRank = true
	stt.foundRank = 3
	stt.maxRank = nil
	stt.minRank = nil
	
	stt.period = 0
	
	stt.linked = false
	stt.propertyWithQualifier = false
	
	stt.withUnit = false
	stt.shortName = false
	stt.singleValue = false
	
	stt.langCode = mw.language.getContentLanguage().code
	stt.langObj = mw.language.new(stt.langCode)
	
	stt:setRankBoundaries("best")
	
	return stt
end

function State:unknownDatatypeError(type)
	return "<strong class=\"error\">Unknown or unsupported datatype '" .. type .. "'</strong>"
end

function State:parseWikidataURL(url)
	local i, j
	
	if url:match('^http[s]?://') then
		i, j = url:find("Q")
		
		if i then
			return url:sub(i)
		end
	end
	
	return nil
end

function State:parseDate(dateStr, precision)
	precision = precision or "d"
	local i, j, index, ptr
	local parts = {nil, nil, nil}
	
	if dateStr == nil then
		return parts[1], parts[2], parts[3]  -- 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
		parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10)  -- remove + sign
		
		if parts[index] == -0 then
			parts[index] = 0
		end
		
		if precision == "y" then
			-- we're done
			return parts[1], parts[2], parts[3]  -- year, month, day
		end
		
		index = index + 1
		ptr = i + 1
		
		i, j = dateStr:find("-", ptr)
		
		if i then
			-- month
			parts[index] = tonumber(dateStr:sub(ptr, i-1), 10)
			
			if precision == "m" then
				-- we're done
				return parts[1], parts[2], parts[3]  -- 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
		parts[index] = tonumber(dateStr:sub(ptr), 10)
	end
	
	return parts[1], parts[2], parts[3]  -- year, month, day
end

function State:convertUnit(unit, addLink)
	addLink = addLink or false
	local itemID, label, target
	
	if unit == "" or unit == "1" then
		return nil
	end
	
	itemID = self:parseWikidataURL(unit)
	
	if itemID then
		if itemID == "Q11229" then  -- 'percentage'
			return "%"
		else
			label = mw.wikibase.label(itemID)
			target = nil
			
			if addLink or label == nil then
				target = mw.wikibase.sitelink(itemID)
			end
			
			if addLink then
				if target then
					return " " .. "[[" .. target .. "|" .. (label or target) .. "]]"
				end
				
				if not label then
					return " " .. "[[:d:" .. itemID .. "|" .. itemID .. "]]"
				end
			end
			
			return " " .. (label or target or itemID)
		end
	end
	
	return " " .. unit
end

function State:getShortName(itemID)
	return p._property({"single", itemID, "P1813"})  -- 'short name'
end

function State:getOrdinalSuffix(num)
	if tostring(num):sub(-2,-2) == '1' then
		return "th"  -- 10th, 11th, 12th, 13th, ... 19th
	end
	
	num = tostring(num):sub(-1)
	
	if num == '1' then
		return "st"
	elseif num == '2' then
		return "nd"
	elseif num == '3' then
		return "rd"
	else
		return "th"
	end
end

function State:getValue(snak, addUnit, addLink)
	addUnit = addUnit or false
	addLink = addLink or false
	
	if snak.snaktype == 'value' then
		if snak.datavalue.type == 'string' then
			return snak.datavalue.value
		elseif snak.datavalue.type == 'monolingualtext' then
			if snak.datavalue.value['language'] == self.langCode then
				return snak.datavalue.value['text']
			else
				return nil
			end
		elseif snak.datavalue.type == 'quantity' then
			-- strip + signs from front
			local value = mw.ustring.gsub(snak.datavalue.value['amount'], "^\+(.+)$", "%1")
			
			if addUnit then
				local unit = self:convertUnit(snak.datavalue.value['unit'], addLink)
				if unit then
					value = value .. unit
				end
			end
			
			return value
		elseif snak.datavalue.type == 'time' then
			local y, m, d, p, yDiv, yRound, yFull, value, calendarID
			local yFactor = 1
			local sign = 1
			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 = self:parseDate(snak.datavalue.value['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 addUnit then
							if precision == 6 then
								suffix = " millennium"
							else
								suffix = " century"
							end
							
							suffix = self:getOrdinalSuffix(yRound) .. suffix
						else
							-- if no unit added, 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 addUnit then
							suffix = "s"
						end
					end
					
					if not addUnit 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 addUnit then
						if precision == 3 then
							suffix = " million years"
						elseif precision == 0 then
							suffix = " billion years"
						else
							yRound = yRound * yFactor
							if yRound == 1 then
								suffix = " year"
							else
								suffix = " years"
							end
						end
					else
						yRound = yRound * yFactor
					end
				end
			else
				yRound = y
				mayAddCalendar = true
			end
			
			if mayAddCalendar then
				calendarID = self:parseWikidataURL(snak.datavalue.value['calendarmodel'])
				
				if calendarID and calendarID == "Q1985786" then  -- 'Proleptic Julian calendar'
					if addUnit then
						if addLink then
							calendar = " ([[Julian calendar|Julian]])"
						else
							calendar = " (Julian)"
						end
					else
						calendar = "/Julian"
					end
				end
			end
			
			if addUnit then
				local ce = nil
				
				if sign < 0 then
					ce = "BCE"
				elseif precision <= 5 then
					ce = "CE"
				end
				
				if ce then
					if addLink then
						ce = "[[Common Era|" .. ce .. "]]"
					end
					suffix = suffix .. " " .. ce
				end
				
				value = tostring(yRound)
				
				if m then
					value = self.langObj:formatDate("F", "1-"..m.."-1") .. " " .. value
					
					if d then
						value = d .. " " .. value
					end
				end
				
				value = value .. suffix .. calendar
			else
				value = tostring(yRound * sign)
				
				if m then
					value = value .. "-" .. m
					
					if d then
						value = value .. "-" .. d
					end
				end
				
				value = value .. calendar
			end
			
			return value
		elseif snak.datavalue.type == 'globecoordinate' then
			local precision, numDigits, strFormat, value, globe
			local latValue, latitude, latDegrees, latMinutes, latSeconds
			local latDirection = "N"
			local lonValue, longitude, lonDegrees, lonMinutes, lonSeconds
			local lonDirection = "E"
			
			local degSymbol = "°"
			local minSymbol = "'"
			local secSymbol = '"'
			local partsGlue = ", "
			
			if not addUnit then
				degSymbol = "/"
				minSymbol = "/"
				secSymbol = "/"
				partsGlue = "/"
			end
			
			latitude = snak.datavalue.value['latitude']
			longitude = snak.datavalue.value['longitude']
			
			if latitude < 0 then
				latDirection = "S"
				latitude = math.abs(latitude)
			end
			if longitude < 0 then
				lonDirection = "W"
				longitude = math.abs(longitude)
			end
			
			precision = snak.datavalue.value['precision']
			
			latitude = math.floor(latitude / precision + 0.5) * precision
			longitude = math.floor(longitude / precision + 0.5) * precision
			
			latDegrees = math.floor(latitude)
			lonDegrees = math.floor(longitude)
			
			latMinutes = math.floor((latitude - latDegrees) * 60)
			lonMinutes = math.floor((longitude - lonDegrees) * 60)
			
			latSeconds = (latitude - (latDegrees + latMinutes / 60)) * 3600
			lonSeconds = (longitude - (lonDegrees + lonMinutes / 60)) * 3600
			
			latValue = latDegrees .. degSymbol
			lonValue = lonDegrees .. degSymbol
			
			if precision < 1 then
				latValue = latValue .. latMinutes .. minSymbol
				lonValue = lonValue .. lonMinutes .. minSymbol
			end
			
			if precision < (1 / 60) then
				numDigits = math.ceil(-math.log10(3600 * precision))
				
				if numDigits < 0 or numDigits == -0 then
					numDigits = 0
				end
				
				strFormat = "%." .. numDigits .. "f"
				
				latSeconds = string.format(strFormat, latSeconds)
				lonSeconds = string.format(strFormat, lonSeconds)
				
				latValue = latValue .. latSeconds .. secSymbol
				lonValue = lonValue .. lonSeconds .. secSymbol
			end
			
			latValue = latValue .. latDirection
			lonValue = lonValue .. lonDirection
			
			value = latValue .. partsGlue .. lonValue
			
			if addLink then
				globe = self:parseWikidataURL(snak.datavalue.value['globe'])
				
				if globe then
					globe = mw.wikibase.label(globe):lower()
				else
					globe = "earth"
				end
				
				value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latitude.."_"..latDirection.."_"..longitude.."_"..lonDirection.."_globe:"..globe.." "..value.."]"
			end
			
			return value
		elseif snak.datavalue.type == 'wikibase-entityid' then
			local value = ""
			local target = nil
			local itemID = "Q" .. snak.datavalue.value['numeric-id']
			
			if self.shortName then
				value = self:getShortName(itemID)
			end
			
			if value == "" then
				value = mw.wikibase.label(itemID)
			end
			
			if addLink or value == nil then
				target = mw.wikibase.sitelink(itemID)
			end
			
			if addLink then
				if target then
					value = "[[" .. target .. "|" .. (value or target) .. "]]"
				elseif not value then
					value = "[[:d:" .. itemID .. "|" .. itemID .. "]]"
				end
			elseif not value then
				value = (target or itemID)
			end
			
			return value
		else
			return self:unknownDatatypeError(snak.datavalue.type)
		end
	elseif snak.snaktype == 'somevalue' then
		return "unknown"
	elseif snak.snaktype == 'novalue' then
		return "none"
	else
		return nil
	end
end

function State:getRawValue(snak)
	if snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then
		return "Q" .. snak.datavalue.value['numeric-id']
	elseif snak.snaktype == 'somevalue' then
		return " "  -- single space represents 'somevalue'
	elseif snak.snaktype == 'novalue' then
		return ""  -- empty value represents 'novalue'
	else
		return self:getValue(snak, false, false)
	end
end

function State:getSingleRawQualifier(claim, qualifierID)
	local qualifiers
	
	if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end
	
	if qualifiers and qualifiers[1] then
		return self:getRawValue(qualifiers[1])
	else
		return nil
	end
end

function State:snakEqualsValue(snak, value)
	local snakValue = self:getRawValue(snak)
	
	if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end
	
	return snakValue == value
end

function State:setRankBoundaries(rank)
	local rankPos
	
	if (rank == "best") then
		self.bestRank = true
		self.foundRank = 3
		return
	else
		self.bestRank = false
	end
	
	if (rank:sub(1,9) == "preferred") then
		rankPos = 1
	elseif (rank:sub(1,6) == "normal") then
		rankPos = 2
	elseif (rank:sub(1,10) == "deprecated") then
		rankPos = 3
	end
	
	if (rank:sub(-1) == "+") then
		self.maxRank = 1
		self.minRank = rankPos
	elseif (rank:sub(-1) == "-") then
		self.maxRank = rankPos
		self.minRank = 3
	else
		self.maxRank = rankPos
		self.minRank = rankPos
	end
end

function State: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 State:rankMatches(rankPos)
	if self.bestRank then
		if self.foundRank > rankPos then
			self.foundRank = rankPos
			
			-- found a better rank, reset worse rank outputs
			if self.foundRank == 1 then
				self.outNormal = {}
				self.outDeprecated = {}
			elseif self.foundRank == 2 then
				self.outDeprecated = {}
			end
		end
		
		return self.foundRank >= rankPos  -- == would also work here
	else
		return (self.maxRank <= rankPos and rankPos <= self.minRank)
	end
end

function State: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

function State: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.period == 0 then
		-- any time
		return true
	end
	
	local now = os.date('!*t')
	
	startTime = self:getSingleRawQualifier(claim, "P580")  -- 'start time'
	if startTime ~= "" and startTime ~= " " then
		startTimeY, startTimeM, startTimeD = self:parseDate(startTime)
	end
	
	endTime = self:getSingleRawQualifier(claim, "P582")  -- 'end time'
	if endTime ~= "" and endTime ~= " " then
		endTimeY, endTimeM, endTimeD = self: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 self: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.period == 1 then
		-- future
		if startTimeY == nil or not self:datePrecedesDate(now['year'], now['month'], now['day'], startTimeY, startTimeM, startTimeD) then
			return false
		else
			return true
		end
	elseif self.period == 2 then
		-- current
		if (startTimeY ~= nil and self:datePrecedesDate(now['year'], now['month'], now['day'], startTimeY, startTimeM, startTimeD)) or
		   (endTimeY ~= nil and not self:datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD)) then
		    return false
		else
		   	return true
		end
	elseif self.period == 3 then
		-- former
		if endTimeY == nil or self:datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD) then
			return false
		else
			return true
		end
	end
end

function State:appendOutput(value, rankPos)
	local done = false
	
	if rankPos == 1 then
		self.outPreferred[#self.outPreferred + 1] = value
		
		if self.singleValue then
			done = true
		end
	elseif rankPos == 2 then
		self.outNormal[#self.outNormal + 1] = value
		
		if self.singleValue and not self.bestRank and self.maxRank == 2 then
			done = true
		end
	elseif rankPos == 3 then
		self.outDeprecated[#self.outDeprecated + 1] = value
		
		if self.singleValue and not self.bestRank and self.maxRank == 3 then
			done = true
		end
	end
	
	return done
end

function State:out()
	local out = ""
	
	if self.outDeprecated[1] then
		if self.singleValue then
			out = self.outDeprecated[1]
		else
			out = table.concat(self.outDeprecated, ", ")
		end
	end
	
	if self.outNormal[1] then
		if self.singleValue then
			out = self.outNormal[1]
		else
			if out ~= "" then
				out = "; " .. out
			end
			
			out = table.concat(self.outNormal, ", ") .. out
		end
	end
	
	if self.outPreferred[1] then
		if self.singleValue then
			out = self.outPreferred[1]
		else
			if out ~= "" then
				out = "; " .. out
			end
			
			out = table.concat(self.outPreferred, ", ") .. out
		end
	end
	
	return out
end

function State:processFlag(flag)
	if flag == "linked" then
		self.linked = true
		return true
	elseif flag == "unit" then
		self.withUnit = true
		return true
	elseif flag == "short" then
		self.shortName = true
		return true
	elseif flag == "single" then
		self.singleValue = true
		return true
	elseif flag == "best" or flag:match('^preferred[+-]?$') or flag:match('^normal[+-]?$') or flag:match('^deprecated[+-]?$') then
		self:setRankBoundaries(flag)
		return true
	elseif flag == "future" then
		self.period = 1
		return true
	elseif flag == "current" then
		self.period = 2
		return true
	elseif flag == "former" then
		self.period = 3
		return true
	else
		return false
	end
end

function p.property(frame)
	return p._property(frame.args)
end

function p._property(args)
	local _ = State.new()
	
	local entity, propertyID, claims, rankPos, value, done
	local nextArg = mw.text.trim(args[1] or "")
	local nextIndex = 2
	
	while _:processFlag(nextArg) do
		nextArg = mw.text.trim(args[nextIndex] or "")
		nextIndex = nextIndex + 1
	end
	
	if nextArg:sub(1,1):upper() == "Q" then
		entity = mw.wikibase.getEntity(nextArg)
		propertyID = mw.text.trim(args[nextIndex] or ""):upper()
	else
		entity = mw.wikibase.getEntity()
		propertyID = nextArg:upper()
	end
	
	if entity and entity.claims then claims = entity.claims[propertyID] end
	if claims then
		for i, v in ipairs(claims) do
			rankPos = _:convertRank(v.rank)
			if _:rankMatches(rankPos) and _:timeMatches(v) then
				value = _:getValue(v.mainsnak, _.withUnit, _.linked)
				if value then
					done = _:appendOutput(value, rankPos)
					if done then
						break
					end
				end
			end
		end
		return _:out()
	else
		return ""
	end
end

function p.qualifier(frame)
	return p._qualifier(frame.args)
end

function p._qualifier(args, _)
	_ = _ or State.new()
	
	local entity, propertyID, propertyValue, qualifierID, claims, qualifiers, rankPos, outValue, outInter, outQualifiers
	local done = false
	
	local nextArg = mw.text.trim(args[1] or "")
	local nextIndex = 2
	
	while _:processFlag(nextArg) do
		nextArg = mw.text.trim(args[nextIndex] or "")
		nextIndex = nextIndex + 1
	end
	
	if nextArg:sub(1,1):upper() == "Q" then
		entity = mw.wikibase.getEntity(nextArg)
		propertyID = mw.text.trim(args[nextIndex] or ""):upper()
		nextIndex = nextIndex + 1
	else
		entity = mw.wikibase.getEntity()
		propertyID = nextArg:upper()
	end
	
	nextArg = args[nextIndex]
	nextIndex = nextIndex + 1
	
	qualifierID = nextArg
	
	nextArg = mw.text.trim(args[nextIndex] or "")
	nextIndex = nextIndex + 1
	
	if nextArg == "" then
		-- claim ID or literal value has NOT been given
		propertyValue = nil
		qualifierID = mw.text.trim(qualifierID or ""):upper()
	else
		-- claim ID or literal value has been given
		propertyValue = qualifierID  -- cannot be nil when reached; empty value represents 'novalue'
		if propertyValue ~= "" and mw.text.trim(propertyValue) == "" then
			propertyValue = " "  -- single space represents 'somevalue'
		else
			propertyValue = mw.text.trim(propertyValue)
		end
		qualifierID = nextArg:upper()
	end
	
	if entity and entity.claims then claims = entity.claims[propertyID] end
	if claims then
		for i, v in ipairs(claims) do
			rankPos = _:convertRank(v.rank)
			if propertyValue == nil or _:snakEqualsValue(v.mainsnak, propertyValue) then
				if _:rankMatches(rankPos) and _:timeMatches(v) then
					outValue = nil
					outInter = nil
					outQualifiers = {}
					
					if _.propertyWithQualifier then
						-- get the property value first
						outValue = _:getValue(v.mainsnak, _.withUnit, _.linked)
					end
					
					if v.qualifiers then qualifiers = v.qualifiers[qualifierID] end
					if (not _.propertyWithQualifier or outValue) and qualifiers then
						-- get a bare qualifier, or the qualifiers connected to the property if it had a value
						for i2, v2 in ipairs(qualifiers) do
							outInter = _:getValue(v2, _.withUnit, _.linked)
							if outInter then
								if not _.propertyWithQualifier then
									done = _:appendOutput(outInter, rankPos)
									if done then
										break
									end
								else
									outQualifiers[#outQualifiers + 1] = outInter
								end
							end
						end
					end
					
					if _.propertyWithQualifier and outValue then
						outQualifiers = table.concat(outQualifiers, ", ")
						
						if outQualifiers ~= "" then
							outQualifiers = " <span style=\"font-size:smaller\">(" .. outQualifiers .. ")</span>"
							outValue = outValue .. outQualifiers
						end
						
						done = _:appendOutput(outValue, rankPos)
					end
					
					if done then
						break
					end
				end
			end
		end
		return _:out()
	else
		return ""
	end
end

function p.propertyWithQualifier(frame)
	return p._propertyWithQualifier(frame.args)
end

function p._propertyWithQualifier(args)
	local _ = State.new()
	_.propertyWithQualifier = true
	_.withUnit = true
	return p._qualifier(args, _)
end

function p.label(frame)
	return p._label(frame.args)
end

function p._label(args)
	local _ = State.new()
	
	local label = ""
	local target = ""
	local nextArg = mw.text.trim(args[1] or "")
	local nextIndex = 2
	
	while _:processFlag(nextArg) do
		nextArg = mw.text.trim(args[nextIndex] or "")
		nextIndex = nextIndex + 1
	end
	
	if nextArg then
		if nextArg:sub(1,1):upper() == "Q" then
			if _.shortName then
				label = _:getShortName(nextArg)
			end
			
			if label == "" then
				label = mw.wikibase.label(nextArg)
			end
			
			if _.linked or label == nil then
				target = mw.wikibase.sitelink(nextArg)
			end
			
			if _.linked and target then
				label = "[[" .. target .. "|" .. (label or target) .. "]]"
			end
		else
			label = mw.wikibase.label(nextArg)
		end
		
		return (label or target)
	else
		return mw.wikibase.label()
	end
end

-- main function that may be used by wrapper templates
function p.main(frame)
	local f, args, i, v
	
	frame = frame:getParent() or frame
	f = mw.text.trim(frame.args[1] or "")
	assert(p[f], 'The function "' .. f .. '" does not exist')
	
    args = {}
    for i, v in ipairs(frame.args) do
    	if i > 1 then
        	args[i-1] = v
        end
    end
	frame.args = args
	
	return p[f](frame)
end

return p