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:
value
Returns a human-readable reference to a tag value.
Parameters:
key-value
.tag
Returns a human-readable reference to a key or key-value pair using key=value syntax.
Parameters:
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:
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("*:%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