Module:Tag

From OpenStreetMap Wiki
Jump to navigation Jump to search
[Edit] [Purge] Documentation

Usage

Utilities related to OSM tags.

A number of testcases verify the correctness of these functions. Run the unit tests.

key

Returns a human-readable reference to a key.

Parameters:

|1 =
The name of a key.
|subkey = (|subkey1 =), |subkey2 =, |subkey3 =, etc.
The name of each successive subkey. A subkey is separated by a semicolon and links to the key description page of the base key.
|: =, |:: =, |::: =, etc.
The name of each successive key component. A key component is separated by a semicolon and links to a key description page specifically about the key component.
|kl =, |kl: =, |kl:: =, etc.
The language code of the page to link each successive key component. |kl = determines the link for |1 =, |kl: = for |: =, and so on. If the language is unspecified, the key component is linked to the page in the same language as the page that transcludes this template, falling back to English.
|lang =
The language code of all the link targets. If the language is unspecified, each link points to the page in the same language as the page that transcludes this template, falling back to English.

value

Returns a human-readable reference to a tag value.

Parameters:

|key = or |1 =
The name of a key.
|value =
A tag value, which will be linked or unlinked depending on the value of |link =.
|link =
Determines the link target of the link that this template outputs:
|link = key
Links to the key's description page.
|link = key#
Links to a named anchor on the key's description page in the form key-value.
|link = value
Links to the value's description page.
|link = none
No link.
|2 =
A tag value to be linked to a value description page. Use this parameter or the combination of |link = value and |value = for any value of an enumeration-typed key.
|3 =
A tag value to be linked to the key's description page. Use this parameter or the combination of |link = key# and |value = for any value of an enumeration-typed key that is specifically documented on the key's description page.
|4 =
An unlinked tag value. Use this parameter and leave |2 = and |3 = blank, or combine |link = key with |value =, for any value of a key that is not specifically documented anywhere. Alternatively, this parameter and leave |2 = and |3 = blank, or combine |link = none with |value =, for any value of a key that holds freeform text, a URL, or more complex syntax.
|lang = or |kl =
The language code of the link target. If the language is unspecified, the key component is linked to the page in the same language as the page that transcludes this template, falling back to English.

tag

Returns a human-readable reference to a key or key-value pair using key=value syntax.

Parameters:

|1 =
The name of a key.
|subkey = (|subkey1 =), |subkey2 =, |subkey3 =, etc.
The name of each successive subkey. A subkey is separated by a semicolon and links to the key description page of the base key.
|: =, |:: =, |::: =, etc.
The name of each successive key component. A key component is separated by a semicolon and links to a key description page specifically about the key component.
|kl =, |kl: =, |kl:: =, etc.
The language code of the page to link each successive key component. |kl = determines the link for |1 =, |kl: = for |: =, and so on. If the language is unspecified, the key component is linked to the page in the same language as the page that transcludes this template, falling back to English.
|2 =, |; = (|subval =), |;; = (|subval2 =), etc.
Tag values separated by semicolons. Each value is linked to a value description page. Use these parameters for any value of an enumeration-typed key.
|vl =, |vl2 =, |vl3 =, etc.
The language code of the page to link each successive value. |vl = determines the link for |2 =, |vl2 = for |; = or |subval =, |vl3 = for |;; = or |subval2 =, and so on. If the language is unspecified, the value is linked to the page in the same language as the page that transcludes this template, falling back to English.
|3 =
A tag value. The value is unlinked. Use this parameter and leave |2 = blank for any value of a key that holds freeform text, a URL, or more complex syntax, or for explanatory placeholder text. A value of *:wikipedia=*, *:wikidata=*, *:wikimedia_commons=*, *:url=*, or *:website=* is linked to the referenced URL.
|lang =
The language code of all the link targets. If the language is unspecified, each link points to the page in the same language as the page that transcludes this template, falling back to English.

keyComponentList

Returns an unordered list of key components that form the given key. Each key component is annotated with its description from the associated data item, if available.

Parameters:

|1 =
The key to split into components.
|intro =
Introductory text to insert before the list.

See also

local p = {}
local getArgs = require('Module:Arguments').getArgs
local languages = require("Module:Languages")
local currentTitle = mw.title.getCurrentTitle()
local defaultLangCode = languages.languageFromTitle(currentTitle)

local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame, {
			trim = true,
			removeBlanks = false,
		})
		return p[funcName](args)
	end
end

function pageLink(pageName, label, langCode)
	local validPageName
	if langCode and #langCode > 0 then
		validPageName = languages.translationPageName(langCode, mw.title.new(pageName))
	elseif defaultLangCode ~= "en" and pageName ~= currentTitle.fullText then
		local translatedPageName = languages.translationPageName(defaultLangCode, mw.title.new(pageName))
		if mw.title.new(translatedPageName).exists then
			validPageName = translatedPageName
		else
			validPageName = pageName
		end
	elseif mw.title.new(pageName) then
		validPageName = pageName
	end
	
	if validPageName then
		return "[[" .. validPageName .. "|" .. label .. "]]"
	else
		return label
	end
end

function valueLink(key, value, langCode)
	local pageName = "Tag:" .. key .. "=" .. value
	return pageLink(pageName, tostring(mw.html.create("bdi"):wikitext(value)), langCode)
end

p.value = makeInvokeFunc("_value")
function p._value(args)
	local langCode = args.kl or args.lang
	local key = args.key or args[1]
	
	local pageName
	if args.link == "value" or args.link == "tag" or (args[2] and #args[2] > 0) then
		if key == "type" then
			pageName = "Relation:" .. (args.value or args[2])
		else
			pageName = mw.ustring.format("Tag:%s=%s", key, args.value or args[2])
		end
	elseif args.link == "key#" or (args[3] and #args[3] > 0) then
		pageName = mw.ustring.format("Key:%s#%s-%s", key, key, args.value or args[3])
	elseif args.link ~= "none" and key then
		pageName = "Key:" .. key
	end
	
	local label = args.value or args[4] or args[3] or args[2] or mw.text.nowiki("*")
	label = mw.html.create("bdi"):wikitext(label)
	
	if pageName then
		return tostring(pageLink(pageName, tostring(label), langCode))
	else
		return tostring(label)
	end
end

function tag(args, includesValue)
	local components = {}
	local keyComponents = {}
	local keyComponentsToLink = {}
	
	-- Key
	local key = args[1]
	table.insert(keyComponents, key)
	table.insert(keyComponentsToLink, key)
	
	-- Give |subkey= precedence over |subkey1=.
	if args.subkey then
		table.insert(keyComponents, args.subkey)
	end
	
	-- Collect subkeys.
	local subkeyIndex = 2
	while args["subkey" .. subkeyIndex] do
		local subkey = args["subkey" .. subkeyIndex]
		table.insert(keyComponents, subkey)
		subkeyIndex = subkeyIndex + 1
	end
	
	-- Combine |subkey*= with |key=, but not for linking purposes.
	keyComponents = {
		table.concat(keyComponents, mw.text.nowiki(":")),
	}
	
	-- Collect subkeys to be linked separately.
	local subkeyIndex = 1
	while args[string.rep(":", subkeyIndex)] do
		local subkey = args[string.rep(":", subkeyIndex)]
		table.insert(keyComponents, subkey)
		table.insert(keyComponentsToLink, subkey)
		subkeyIndex = subkeyIndex + 1
	end
	
	-- Link the key and any subkeys.
	local linkedKeyComponents = {}
	for i, key in ipairs(keyComponentsToLink) do
		local langCode = args["kl" .. string.rep(":", i - 1)] or args.lang
		table.insert(linkedKeyComponents, pageLink("Key:" .. key, keyComponents[i], langCode))
	end
	table.insert(components, table.concat(linkedKeyComponents, ":"))
	
	components = {
		tostring(mw.html.create("bdi")
			:css("white-space", "nowrap")
			:wikitext(table.concat(components)))
	}
	if not includesValue then
		return table.concat(components)
	end
	
	table.insert(components, "=")
	
	-- Values
	local lastKeyComponent = keyComponents[#keyComponents]
	if args[2] and #args[2] > 0 then
		local values = {}
		if args[2] then
			table.insert(values, args[2])
		end
		if args[";"] or args.subval then
			table.insert(values, args[";"] or args.subval)
		end
		local subvalueIndex = 2
		while args[string.rep(";", subvalueIndex)] or args["subval" .. subvalueIndex] do
			local otherValue = args[string.rep(";", subvalueIndex)] or args["subval" .. subvalueIndex]
			if #otherValue > 0 then
				table.insert(values, otherValue)
			end
			subvalueIndex = subvalueIndex + 1
		end
		local linkedValues = {}
		for i, value in ipairs(values) do
			local langCode = args[i > 1 and ("vl" .. i) or "vl"] or args.lang
			table.insert(linkedValues, valueLink(lastKeyComponent, value, langCode))
		end
		table.insert(components, table.concat(linkedValues, ";"))
	elseif args[3] and #args[3] > 0 then
		local lastBaseKeyComponent = keyComponentsToLink[#keyComponentsToLink]
		local lastSubkey = lastKeyComponent:match("%w+$")
		local value = args[3]
		
		-- A wiki page title cannot contain a square bracket, so this is likely already a wikilink or external link.
		local isLiteralLink = (value:sub(1, 1)) == "["
		
		local pageName
		local url
		if not isLiteralLink then
			if lastSubkey == "wikipedia" or lastBaseKeyComponent == "wikipedia" then
				if lastSubkey ~= "wikipedia" then
					value = lastSubkey .. ":" .. value
				end
				pageName = "w:" .. value
			elseif lastSubkey == "wikidata" or lastBaseKeyComponent == "wikidata" then
				pageName = "d:" .. value
			elseif lastSubkey == "wikimedia_commons" or lastBaseKeyComponent == "wikimedia_commons" then
				pageName = "Commons:" .. value
			end
			
			if lastSubkey == "url" or lastBaseKeyComponent == "url" or lastSubkey == "website" or lastBaseKeyComponent == "website" then
				url = value
			end
		end
		
		if url then
			local label = mw.html.create("bdi")
				:css("white-space", "normal")
				:wikitext(mw.text.nowiki(url))
			table.insert(components, "[" .. url .. " " .. tostring(label) .. "]")
		elseif pageName then
			local label = mw.html.create("bdi"):wikitext(args[3])
			table.insert(components, pageLink(pageName, tostring(label), ""))
		else
			local label = mw.html.create("bdi"):wikitext(args[3])
			table.insert(components, tostring(label))
		end
	else
		table.insert(components, mw.text.nowiki("*"))
	end
	
	return table.concat(components)
end

p.key = makeInvokeFunc("_key")
function p._key(args)
	return tag(args, false)
end

p.tag = makeInvokeFunc("_tag")
function p._tag(args)
	return tag(args, true)
end

function p.keyComponents(key)
	if #key == 0 then
		return {}
	end
	
	local rawComponents = mw.text.split(key, ":", true)
	local resolvedComponents = {}
	local mostSpecificTitle
	local mostSpecificEntityId
	local mostSpecificDescription
	local seenCoreComponent = false
	for i, component in ipairs(rawComponents) do
		local base = table.concat(resolvedComponents, ":")
		if #base > 0 then
			base = base .. ":"
		end
		local key = mw.ustring.format("%s%s", base, component)
		local title
		local entityId
		
		-- First check if this component is a prefix.
		-- TODO: Require the prefix to precede any non-prefixes.
		local pageName = mw.ustring.format("Key:%s:*", key)
		title = mw.title.new(pageName)
		entityId = mw.wikibase.getEntityIdForTitle(pageName)
		
		-- How about a suffix?
		-- TODO: Require the suffix to follow any non-suffixes.
		if not entityId and not (title and title.exists) then
			local pageName = mw.ustring.format("Key:*:%s", key)
			title = mw.title.new(pageName)
			entityId = mw.wikibase.getEntityIdForTitle(pageName)
		end
		
		if not entityId and not (title and title.exists) then
			local pageName = mw.ustring.format("Key:%s", key)
			title = mw.title.new(pageName)
			entityId = mw.wikibase.getEntityIdForTitle(pageName)
		end
		
		if entityId or title.exists then
			local description = mw.wikibase.getDescription(entityId)
			if (description or not mw.ustring.find(key, ":")) and
					-- Avoid deprecated keys, which are less likely to be key components.
					(not entityId or #mw.wikibase.getBestStatements(entityId, "P17") == 0) then
				table.insert(resolvedComponents, component)
				mostSpecificTitle = title
				mostSpecificEntityId = entityId
				mostSpecificDescription = description
			else
				break
			end
		else
			break
		end
	end
	
	if #resolvedComponents == 0 then
		local component = rawComponents[1]
		if mw.language.isKnownLanguageTag(component) then
			table.insert(resolvedComponents, component)
			mostSpecificDescription = mw.ustring.format("[[w:ISO 639:%s|%s]]",
				component,
				mw.language.fetchLanguageName(component, defaultLangCode))
		end
	end
	
	local subkey = table.concat(rawComponents, ":", #resolvedComponents + 1)
	if #resolvedComponents == 0 then
		local component = {
			name = subkey,
		}
		return { component }
	end
	
	local superkey = {
		name = table.concat(resolvedComponents, ":"),
		title = mostSpecificTitle,
		entityId = mostSpecificEntityId,
		description = mostSpecificDescription,
	}
	resolvedComponents = p.keyComponents(subkey)
	table.insert(resolvedComponents, 1, superkey)
	return resolvedComponents
end

function p.keyComponentList(frame)
	-- Get arguments from the calling frame, falling back to its calling frame
	local args = frame.args[1] and frame.args or frame:getParent().args
	
	local key = args[1]
	local components = p.keyComponents(key)
	if #components < 2 then
		return ""
	end
	
	local listItems = {}
	for i, component in ipairs(components) do
		local tag
		if i == 1 then
			tag = frame:expandTemplate {
				title = "Tag",
				args = { component["name"] },
			}
		elseif component["title"] or component["entityId"] then
			tag = frame:expandTemplate {
				title = "Tag",
				args = {
					"*",
					[":"] = component["name"]
				},
			}
		else
			tag = frame:expandTemplate {
				title = "Value",
				args = {
					mw.ustring.format("&#x2a;&#x3a;%s=*", component["name"]),
				},
			}
		end
		local description = component.description
		local edit = component.entityId and frame:expandTemplate {
			title = "Edit",
			args = {
				"Item:" .. component.entityId,
			},
		} or ""
		if description then
			table.insert(listItems, mw.ustring.format("* %s: %s %s", tag, description, edit))
		else
			table.insert(listItems, mw.ustring.format("* %s %s", tag, edit))
		end
	end
	
	if args.intro and #args.intro > 0 then
		table.insert(listItems, 1, args.intro)
	end
	
	return table.concat(listItems, "\n")
end

return p