Module:DescriptionFromDataItem/sandbox
Note to editors: Please don't categorize this template by editing it directly. Instead, place the category in its documentation page, in its "includeonly" section.
This module is used for {{KeyDescription}} and {{ValueDescription}} templates.
It operates as a pass-through module -- it takes whatever parameters were specified on the KeyDescription or ValueDescription templates, compares them with the values stored in the data items, modifies parameters as needed, and passes them on to the {{Description}} template. It also adds a few maintenance categories to make it easier to find some issues.
Please help translate it here.
Useful Queries |
---|
Number of key and tag descriptions per language |
8 tests failed.
Name | Expected | Actual | |
---|---|---|---|
test_english | Module:DescriptionFromDataItem/testcases:137: categories
Failed to assert that nil equals expected [[Category:Item with no description in language RU|my.dbg.key]][[Category:Item with no description in language JA|my.dbg.key]] | ||
test_french | |||
test_german | Module:DescriptionFromDataItem/testcases:177: description
Failed to assert that nil equals expected desc-en <span class=wb-edit-pencil>[[File:Arbcom ru editing.svg|12px|Datenelement (data item) anzeigen/bearbeiten|link=Item:Q42]]</span> | ||
test_no_dataitem | Module:DescriptionFromDataItem/testcases:238: categories
Failed to assert that nil equals expected [[Category:Missing data item in default namespace|theatre:type=amphi]][[Category:Missing data item|theatre:type=amphi]] | ||
test_no_dataitem_key | Module:DescriptionFromDataItem/testcases:248: categories
Failed to assert that nil equals expected [[Category:Missing data item in default namespace|theatre:type]][[Category:Missing data item|theatre:type]] | ||
test_no_dataitem_value | Module:DescriptionFromDataItem/testcases:258: categories
Failed to assert that nil equals expected [[Category:Missing data item in default namespace|theatre:type=amphi]][[Category:Missing data item|theatre:type=amphi]] | ||
test_polish | Module:DescriptionFromDataItem/testcases:192: description
Failed to assert that nil equals expected desc-en <span class=wb-edit-pencil>[[File:Arbcom ru editing.svg|12px|Show/edit corresponding data item.|link=Item:Q42]]</span> | ||
test_polish_group | Module:DescriptionFromDataItem/testcases:207: description
Failed to assert that nil equals expected desc-en <span class=wb-edit-pencil>[[File:Arbcom ru editing.svg|12px|Show/edit corresponding data item.|link=Item:Q42]]</span> | ||
test_portuguese | Module:DescriptionFromDataItem/testcases:162: description
Failed to assert that nil equals expected desc-en <span class=wb-edit-pencil>[[File:Arbcom ru editing.svg|12px|Show/edit corresponding data item.|link=Item:Q42]]</span> |
See testcases
local getArgs = require('Module:Arguments').getArgs
local titleParser = require('Module:OsmPageTitleParser/sandbox')
local data = mw.loadData('Module:DescriptionFromDataItem/data')
local i18n = data.translations
local ns = mw.title.getCurrentTitle().namespace
local p = {}
-- USEFUL DEBUGGING:
-- =p.dbg{title='Key:bridge:movable'}
-- =p.dbg{title='Tag:theatre:type=amphi'}
-- =p.dbg{title='Tag:theatre:type=amphi', key='theatre:type', value='amphi'}
-- =p.dbg{title='Key:bridge:movable', status='accepted'}
-- =p.dbg{title='Tag:noexit=yes'}
-- =p.dbg{qid='Q104'}
-- =p.dbg{qid='Q888'}
-- =p.stmt(p.GROUP, 'Q501', 'en')
-- =p.stmt(p.STATUS, 'Q5846') -- status with ref
-- =mw.text.jsonEncode(mw.wikibase.getBestStatements('Q104', p.IMAGE), mw.text.JSON_PRETTY)
-- mw.log(mw.text.jsonEncode(stmt, mw.text.JSON_PRETTY))
-- ##########################################################################
-- CONSTANTS
-- ##########################################################################
-- "fallback" - if this property is not set on the Tag item, check the corresponding Key item
-- "qid" - for item values, output item's Q ID
-- "en" - for item values, output english label
-- "map" - converts claim's value to the corresponding value in the given map
p.INSTANCE_OF = { id = 'P2', qid = true }
p.FORMATTER_URL = { id = 'P8' }
p.GROUP = { id = 'P25', fallback = true }
p.RENDER_IMAGE = { id = 'P39', fallback = true }
p.Q_EXCEPT = { id = 'P27' }
p.Q_LIMIT = { id = 'P26' }
p.KEY_ID = { id = 'P16', fallback = true }
p.PREFIX_ID = { id = 'P52' }
p.SUFFIX_ID = { id = 'P53' }
p.TAG_ID = { id = 'P19' }
p.TAG_KEY = { id = 'P10' }
p.REL_ID = { id = 'P41' }
p.REL_TAG = { id = 'P40' }
p.ROLE_REL = { id = 'P43' }
p.INCOMPATIBLE_WITH = { id = 'P44', fallback = true, multi = true, strid = true }
p.IMPLIES = { id = 'P45', multi = true, strid = true }
p.COMBINATION = { id = 'P46', multi = true, strid = true }
p.SEE_ALSO = { id = 'P18', multi = true, strid = true }
p.REQUIRES = { id = 'P22', multi = true, strid = true }
p.STATUS_REF = { id = 'P11', is_reference = true }
p.IMG_CAPTION = { id = 'P47', is_qualifier = true }
p.STATUS = { id = 'P6', en = true, extra = p.STATUS_REF }
p.IMAGE = { id = 'P28', extra = p.IMG_CAPTION }
local use_on_values = {
Q8000 = 'yes',
Q8001 = 'no',
}
local instance_types = {
Q7 = { type = 'key', templatename = 'Template:KeyDescription' },
Q22020 = { type = 'key-prefix', templatename = 'Template:KeyPrefixDescription' },
Q22021 = { type = 'key-suffix', templatename = 'Template:KeySuffixDescription' },
Q2 = { type = 'value', templatename = 'Template:ValueDescription' },
Q6 = { type = 'relation', templatename = 'Template:RelationDescription' },
}
p.USE_ON_NODES = { id = 'P33', map = use_on_values }
p.USE_ON_WAYS = { id = 'P34', map = use_on_values }
p.USE_ON_AREAS = { id = 'P35', map = use_on_values }
p.USE_ON_RELATIONS = { id = 'P36', map = use_on_values }
-- Makes it possible to override by unit tests
p.trackedLanguages = data.trackedLanguages
-- ##########################################################################
-- UTILITIES
-- ##########################################################################
local function startswith(self, str)
return self:sub(1, #str) == str
end
local formatKeyVal = function(key, value)
if value then
return key .. '=' .. value
else
return key
end
end
-- Normalizes yes/no/maybe into "yes", "no", nil
local function normalizeBoolean(val)
if val then
val = string.lower(val)
if val == 'yes' or val == 'no' then
return val
end
end
return nil
end
local function localize(key, langCode, params)
local msgTable = i18n[key]
local msg
if msgTable then
msg = msgTable[langCode] or msgTable['en']
end
if not msg then
return '<' .. key .. '>'
end
return mw.message.newRawMessage(msg, unpack(params or {})):plain()
end
local function getItemValue(self, prop)
-- Only get the first returned value, so need an extra local var step
local value = p.getClaimValue(prop, self.langCode, self.entity, self.fallbackEntity)
return value
end
-- Format as an edit link. Target is either a relative url that starts with a slash, or an item ID (e.g. Q104)
local function editLink(self, target, msgKey)
local file
if msgKey == 'desc_edit_mismatch_page' then
file = 'Red pencil.svg'
else
file = 'Arbcom ru editing.svg'
end
if not startswith(target, '/') then
target = 'Item:' .. target
end
return (' <span class=wb-edit-pencil>[[File:' .. file .. '|12px|' ..
localize(msgKey, self.langCode) .. '|link=' .. target .. ']]</span>')
end
-- Convert key:... and tag:... into {{key|...}} and {{tag|...}} in a description
-- Debug: =p.dbgFmtDesc('abc key:xyz aaa tag:ttt:bbb=yyy:123_k, bbb')
local function formatDescription(description, frame)
if startswith(description, '<span') then
-- FIXME: in case description in dataitem and wiki is different,
-- do not perform expansion. Otherwise we would break the title="..."
-- for the span element, creating invalid HTML.
return description
end
local function repl(typ, key, value)
local title = typ == 'key' and 'TagKey' or 'Tag'
return frame:expandTemplate { title = title, args = {key, value} }
end
description = string.gsub(description, '(key):([-:_a-zA-Z0-9]+)', repl)
description = string.gsub(description, '(tag):([-:_a-zA-Z0-9]+)=([-:_a-zA-Z0-9]+)', repl)
return description
end
-- ##########################################################################
-- DATA ITEM PARSING
-- ##########################################################################
-- p.Q_LIMIT "limited to region qualifier" if qualifier is present, include the statement
-- only if self.region equals any of the listed regions
-- p.Q_EXCEPT "excluding region qualifier" if qualifier is present, include the statement
-- only if self.region does not equal all of the listed regions
local regionQualifiers = { { prop = p.Q_LIMIT, include = true }, { prop = p.Q_EXCEPT, include = false } }
-- Test if qualifiers indicate that current statement should be
-- included or excluded based on the rules table
-- Returns true/false if it should be included, and true/false if it was based on qualifiers
local function allowRegion(region, statement)
if statement.rank ~= 'preferred' and statement.rank ~= 'normal' then
return false, false
end
local qualifiers = statement.qualifiers
if qualifiers then
for _, value in pairs(regionQualifiers) do
local qualifier = qualifiers[value.prop.id]
if qualifier then
local include = not value.include
for _, q in pairs(qualifier) do
if region == q.datavalue.value.id then
include = value.include
end
end
-- return after the first found rule, because multiple rules
-- do not make any sense on the same statement
return include, true
end
end
end
return true, false -- by default, the statement should be included
end
local function qidToStrid(qid)
local entity = p.wbGetEntity(qid)
if not entity then return end
local tag = p.getClaimValue(p.TAG_ID, 'en', entity)
local eKey, eValue = titleParser.splitKeyValue(tag)
if not eKey then
eKey = p.getClaimValue(p.KEY_ID, 'en', entity)
if eKey then
return { eKey }
end
else
return { eKey, eValue }
end
end
-- Convert claim value into a string
-- property object specifies what value to get:
-- 'qid' - returns data item id
-- 'strid' - return referenced item
-- 'map' - use a map to convert qid into a string
-- 'en' - only english label
-- default - first try local, then english, then qid
local function claimToValue(datavalue, prop, langCode)
local result = false
if not datavalue then
return nil
elseif datavalue.type == 'wikibase-entityid' then
local qid = datavalue.value.id
if prop.map then
result = prop.map[qid]
elseif prop.strid then
result = qidToStrid(qid)
if not result then
result = { 'Bad item: ' .. qid }
end
elseif not prop.qid then
if not prop.en then
result = p.wbGetLabelByLang(qid, langCode)
end
if not result then
result = p.wbGetLabel(qid)
end
end
if not result then
result = qid
end
elseif datavalue.type == 'string' then
result = datavalue.value
else
-- TODO: handle other property types
result = "Unknown datatype " .. datavalue.type
end
return result
end
local function getStatements(entity, prop)
if prop.multi then
return entity:getBestStatements(prop.id)
else
return entity:getAllStatements(prop.id)
end
end
-- From a monolingual property, get either the given language or English
local function getMonoString(snakList, langCode)
local enVal, val
if snakList then
for _, snak in pairs(snakList) do
local lang = snak.datavalue.value.language
val = snak.datavalue.value.text
if langCode == lang then
return val
elseif langCode == 'en' then
enVal = val
end
end
end
return enVal or val
end
-- Debug: =mw.text.jsonEncode(p.getClaimValue(p.GROUP, 'en', mw.wikibase.getEntity('Q501')),0)
function p.getClaimValue(prop, langCode, entity, fallbackEntity)
local usedFallback = false
local region = data.regions[langCode]
local statements = getStatements(entity, prop)
if fallbackEntity and prop.fallback and next(statements) == nil then
usedFallback = true
statements = getStatements(fallbackEntity, prop)
end
if prop.multi then
local result = {}
for _, stmt in pairs(statements) do
local val = claimToValue(stmt.mainsnak.datavalue, prop, langCode)
if val then
table.insert(result, val)
end
end
return result
end
local match
for _, stmt in pairs(statements) do
local include, qualified = allowRegion(region, stmt)
if include then
match = stmt
if qualified then
-- Keep non-qualified statement until we look through all claims,
-- if we see a qualified one (limited to the current region), we found the best match
break
end
end
end
local result
local extra
if match then
-- Get extra value if available (e.g. reference or image caption)
if prop.extra then
if prop.extra.is_reference and match.references then
for _, ref in pairs(match.references) do
local snak = ref.snaks[prop.extra.id]
if snak and snak[1] then
extra = snak[1].datavalue.value
break
end
end
elseif prop.extra.is_qualifier and match.qualifiers then
extra = getMonoString(match.qualifiers[prop.extra.id])
end
end
result = claimToValue(match.mainsnak.datavalue, prop, langCode)
end
return result, usedFallback, extra
end
local function validateKeyValue(self)
if self.args.type == 'relation' then
-- Ignore for relations
return
end
-- Ensure key and value are set properly in the template params
local args = self.args
local tag = getItemValue(self, p.TAG_ID)
local eKey, eValue = titleParser.splitKeyValue(tag)
if not eKey then
eKey = getItemValue(self, p.KEY_ID)
end
if not args.key then
args.key = eKey
end
if not args.value then
args.value = eValue
end
if args.key ~= eKey or args.value ~= eValue then
table.insert(self.categories, 'Mismatched Key or Value')
end
end
-- Get categories string, e.g. "[[category:xx]][[category:yy]]"
local function getCategories(self)
if next(self.categories) ~= nil then
local sortkey = formatKeyVal(self.key, self.value)
local prefix = '[[Category:'
local suffix = sortkey and '|' .. sortkey .. ']]' or ']]'
return prefix .. table.concat(self.categories, suffix .. prefix) .. suffix
else
return nil
end
end
local function formatValue(self, value, editLinkRef)
if not editLinkRef then
return value
else
return value .. editLink(self, editLinkRef, 'desc_edit')
end
end
-- Process a single property, comparing old and new values
-- add tracking categories as needed
-- if qid is set, shows a pencil icon next to this value
local function processValue(self, argname, entityVal, pageVal, qid)
local args = self.args
if pageVal == nil then
pageVal = args[argname]
end
if pageVal == '' then
pageVal = nil
end
if entityVal == '' then
entityVal = nil
end
if not pageVal then
if entityVal then
-- value is only present in the entity
if self.langCode == 'en' and ns == 0 then
if argname == 'status' or argname == 'description' or argname == 'image' then
table.insert(self.categories, 'Pages loading ' .. argname .. ' from data item')
end
if argname == 'onNode' or argname == 'onWay' or argname == 'onArea' or argname == 'onRelation' then
table.insert(self.categories, 'Pages loading applicabilities from data item')
end
end
args[argname] = formatValue(self, entityVal, qid)
else
-- value is not set in template nor in the entity
args[argname] = nil
end
elseif not entityVal then
-- value has not been copied to the entity yet
-- if page is in main namespace or DE:/ES:/FR:/IT:/JA:/NL:/RU: then add category "Not copied ..."
if ns == 0 or ns == 200 or ns == 202 or ns == 204 or ns == 206 or ns == 208 or ns == 210 or ns == 212 then
table.insert(self.categories, 'Not copied ' .. argname)
end
args[argname] = formatValue(self, pageVal, qid)
elseif entityVal == pageVal or
(argname ~= 'description' and
self.language:caseFold(entityVal) == self.language:caseFold(pageVal)) then
-- value is identical in both entity and the page
-- comparison is case-insensitive except for the description
-- For now, do not track this -- there are too many of them.
-- Once we start cleaning them up, uncomment this tracking category
-- table.insert(self.categories, 'Redundant ' .. argname)
args[argname] = formatValue(self, pageVal, qid)
elseif argname == 'image' and pageVal:gsub(" ", "_") == 'Image:' .. getItemValue(self, p.IMAGE):gsub(" ", "_") then
do return end
elseif argname == 'image' and pageVal:gsub(" ", "_") == 'image:' .. getItemValue(self, p.IMAGE):gsub(" ", "_") then
do return end
elseif argname == 'image' and pageVal:gsub(" ", "_") == 'File:' .. getItemValue(self, p.IMAGE):gsub(" ", "_") then
args[argname] = formatValue(self, pageVal, qid)
elseif argname == 'osmcarto-rendering' and pageVal:gsub(" ", "_") == 'File:' .. getItemValue(self, p.RENDER_IMAGE):gsub(" ", "_") then
-- Doesn't add "Category:Mismatched osmcarto-rendering" if only difference in filename is " " and "_".
args[argname] = formatValue(self, pageVal, qid)
elseif argname == 'statuslink' and entityVal == 'https://wiki.openstreetmap.org/wiki/' .. pageVal:gsub(" ", "_") then
-- Doesn't add "Category:Mismatched statuslink" just because the type of url notation is different but leads to the same page.
-- Notation on page: Proposed_features/foo bar
-- Notation on item: https://wiki.openstreetmap.org/wiki/Proposed_features/foo_bar
args[argname] = formatValue(self, pageVal, qid)
else
-- value in the page and in the entity do not match
-- Don't apply maintenance categories if page is in namespace "Talk:" "User:" "Template:" "File:" "Help:" etc.
if ns == 0 or ns > 15 then
if self.langCode == 'en' then
table.insert(self.categories, 'Mismatched ' .. argname .. ' in default namespace')
end
table.insert(self.categories, 'Mismatched ' .. argname)
end
if not qid then
args[argname] = pageVal
else
-- Format when value differs between Wiki and Wikibase, with a pencil
-- For now, show pageVal with two pencils: a red one to wiki page and gray one to Wikibase
local editPageLink = editLink(self, self.currentTitle:fullUrl('action=edit'), 'desc_edit_mismatch_page')
local editItemLink = editLink(self, qid, 'desc_edit_mismatch_item')
local span = mw.html.create('span')
span:attr('title', localize('desc_mismatch', self.langCode, { entityVal }))
:wikitext(pageVal)
args[argname] = tostring(span) .. editPageLink .. editItemLink
-- In the future, switch to showing mismatched old value as red, with an edit link
-- to the wiki page, plus new value with an edit link to Wikibase
-- :attr('style', 'color:red')
-- args[argname] = tostring(span) .. editPageLink .. '<br>' .. entityVal .. editItemLink
end
end
end
local function processEntity(self)
local args = self.args
local qid = self.entity:getId()
validateKeyValue(self)
-- Compare all known parameters against the data item entity
processValue(self, 'description',
self.entity:getDescription(self.langCode),
args.description, qid) -- add edit links to description
processValue(self, 'group', getItemValue(self, p.GROUP))
-- For status we must use english label (special processing inside the template)
local status, _, statuslink = p.getClaimValue(p.STATUS, self.langCode, self.entity, self.fallbackEntity)
processValue(self, 'status', status)
processValue(self, 'statuslink', statuslink)
local image, _, image_caption = p.getClaimValue(p.IMAGE, self.langCode, self.entity, self.fallbackEntity)
processValue(self, 'image', image and 'File:' .. image or nil)
processValue(self, 'image_caption', image_caption)
local render = getItemValue(self, p.RENDER_IMAGE)
processValue(self, 'osmcarto-rendering', render and 'File:' .. render or nil)
-- Handle onRelation, onArray, onWay, and onNode
if args.type ~= 'relation' then
processValue(self, 'onNode', getItemValue(self, p.USE_ON_NODES), normalizeBoolean(args.onNode))
processValue(self, 'onWay', getItemValue(self, p.USE_ON_WAYS), normalizeBoolean(args.onWay))
processValue(self, 'onArea', getItemValue(self, p.USE_ON_AREAS), normalizeBoolean(args.onArea))
processValue(self, 'onRelation', getItemValue(self, p.USE_ON_RELATIONS), normalizeBoolean(args.onRelation))
end
processValue(self, 'url_pattern', getItemValue(self, p.FORMATTER_URL), args.url_pattern)
-- Not yet possible to compare these data item values with the template params, so just use if missing
args.combination = args.combination or getItemValue(self, p.COMBINATION)
args.implies = args.implies or getItemValue(self, p.IMPLIES)
args.seeAlso = args.seeAlso or getItemValue(self, p.SEE_ALSO)
args.requires = args.requires or getItemValue(self, p.REQUIRES)
-- Values that are coming only from the data items
args.incompatibleWith = getItemValue(self, p.INCOMPATIBLE_WITH)
end
local function constructor(args)
local self = {
categories = {},
args = args,
}
if args.currentTitle then
self.currentTitle = mw.title.new(args.currentTitle)
else
self.currentTitle = mw.title.getCurrentTitle()
end
-- sets self.key, self.value, and self.language from the current title
titleParser.parseTitleToObj(self, self.currentTitle)
-- if lang parameter is set, overrides the one detected from the title
if args.lang and mw.language.isSupportedLanguage(args.lang) then
self.language = mw.getLanguage(args.lang)
end
self.langCode = self.language:getCode()
toSitelink = function(key, value)
return (value and 'Tag:' or 'Key:') .. formatKeyVal(key, value)
end
local entity
local typeGuess
if args.qid then
entity = p.wbGetEntity(args.qid)
elseif args.key then
-- template caller gave a key param (with optional value)
entity = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(args.key, args.value)))
self.key = args.key
self.value = args.value
typeGuess = self.value and 'Q2' or 'Q7'
elseif args.type then
-- template caller gave type param, guessing a relation
entity = p.wbGetEntity(p.wbGetEntityIdForTitle('Relation:' .. args.type))
args.key = 'type'
args.value = args.type
args.rtype = args.type
args.type = 'relation'
typeGuess = 'Q6'
else
if self.currentTitle then
-- template caller gave currentTitle param (probably debugging)
entity = p.wbGetEntity(p.wbGetEntityIdForTitle(self.currentTitle.text))
else
entity = p.wbGetEntity()
end
-- If there is no associated entity, try to deduce it from the title (e.g. translated pages)
-- note that we cannot guess relations the same way
if not entity and self.key then
entity = p.wbGetEntity(p.wbGetEntityIdForTitle(toSitelink(self.key, self.value)))
end
-- No data item exists
if not entity and self.key then
typeGuess = self.value and 'Q2' or 'Q7'
end
end
-- Try to get a fallback entity - key for tag, tag for relation, relation for rel role
self.entity = entity
if entity then
local _, fbStmt = next(entity:getBestStatements(p.TAG_KEY.id))
if not fbStmt then
_, fbStmt = next(entity:getBestStatements(p.REL_TAG.id))
if not fbStmt then
_, fbStmt = next(entity:getBestStatements(p.ROLE_REL.id))
end
end
if fbStmt then
self.fallbackEntity = p.wbGetEntity(fbStmt.mainsnak.datavalue.value.id)
end
else
if ns == 0 and self.langCode == 'en' then
if args.status == 'approved' or args.status == 'Approved' then
table.insert(self.categories, 'Missing data item for approved tag')
elseif args.status == 'de facto' or args.status == 'De facto' then
table.insert(self.categories, 'Missing data item for de facto tag')
elseif args.status == 'in use' or args.status == 'In use' then
table.insert(self.categories, 'Missing data item for tag in use')
else
table.insert(self.categories, 'Missing data item for unestablished tag')
end
end
if ns == 0 or ns == 200 or ns == 202 or ns == 204 or ns == 206 or ns == 208 or ns == 210 or ns == 212 then
table.insert(self.categories, 'Missing data item')
end
end
local instance_of = entity and getItemValue(self, p.INSTANCE_OF)
local types = instance_types[instance_of or typeGuess]
if types then
if not args.templatename then
args.templatename = types.templatename
end
if not args.type then
args.type = types.type
end
if instance_of == 'Q6' then
-- Relations are tricky - ther remap "temp" to "rtemp" and "value"
if not args.rtype then
args.rtype = getItemValue(self, p.REL_ID)
end
if not args.value then
args.value = args.rtype
end
end
end
-- Template:Description needs these to properly format language bar and template links
-- templatename = Template:ValueDescription | ...
-- type = key|value|relation
if not args.templatename then
local frame2 = mw.getCurrentFrame():getParent()
args.templatename = frame2 and frame2:getTitle() or 'Unknown'
end
if not args.type then
if args.templatename and string.find(args.templatename, 'KeyDescription', 1, true) then
args.type = 'key'
elseif args.templatename and string.find(args.templatename, 'ValueDescription', 1, true) then
args.type = 'value'
elseif args.templatename and string.find(args.templatename, 'KeyPrefixDescription', 1, true) then
args.type = 'key-prefix'
elseif args.templatename and string.find(args.templatename, 'KeySuffixDescription', 1, true) then
args.type = 'key-suffix'
elseif args.templatename and string.find(args.templatename, 'RelationDescription', 1, true) then
args.type = 'relation'
end
end
return self
end
-- ##########################################################################
-- ENTRY POINTS
-- ##########################################################################
-- If we found data item for this key/tag, compare template parameters
-- with what we have in the item, and add some extra formatting/links/...
-- If the values are not provided, just use the ones from the data item
function p.main(frame)
if not mw.wikibase then
return frame:expandTemplate { title = 'Warning', args = { text = "The OSM wiki is experiencing technical difficulties. Infoboxes will be restored soon." } }
end
local args = getArgs(frame)
-- initialize self - parse title, language, and get relevant entities
local self = constructor(args)
if self.entity then
processEntity(self)
end
-- if page is in main namespace or DE:/ES:/FR:/IT:/JA:/NL:/RU: then add category if no description is provided in a tracked language
if ns == 0 or ns == 200 or ns == 202 or ns == 204 or ns == 206 or ns == 208 or ns == 210 or ns == 212 then
if self.entity then
-- If this module is included from [[Template:Deprecated]],
-- omit to check if description is set or not
if args.status ~= 'deprecated' and args.status ~= 'obsolete' then
-- If this is an English item, check if description is set for
-- the tracked languages, and if not, add a tracking category
if self.langCode == 'en' then
for _, lng in ipairs(p.trackedLanguages) do
if not self.entity:getDescription(lng) then
table.insert(self.categories,
'Item with no description in language ' .. mw.ustring.upper(lng))
end
end
elseif not self.entity:getDescription('en') then
table.insert(self.categories, 'Item with no description in language EN')
end
end
end
end
-- Create a group category. Use language-prefixed name if category page exists
if self.args.group and not args.debug then
local group = self.language:ucfirst(self.args.group)
local prefix = titleParser.langPrefix(self.langCode)
local title = mw.title.new('Category:' .. prefix .. group)
if title and title.exists then
table.insert(self.categories, title.text)
else
table.insert(self.categories, group)
end
end
local categories = getCategories(self)
local baseTemplate = args.basetemplate or 'Template:Description'
if args.debuglua then
-- debug and unit test support
return {
template = baseTemplate,
args = args,
categories = categories,
}
end
for _, arg in pairs({ 'combination', 'implies', 'seeAlso', 'requires', 'incompatibleWith' }) do
if type(args[arg]) == 'table' then
local result = ''
for _, val in pairs(args[arg]) do
result = result .. '* ' .. frame:expandTemplate { title = 'Tag', args = val } .. '\n'
end
if result ~= '' then
args[arg] = result
else
args[arg] = nil
end
end
end
if args.description then
args.description = formatDescription(args.description, frame)
end
local result = frame:expandTemplate { title = baseTemplate, args = args }
if categories then
result = result .. categories
end
if args.debugargs then
result = result ..
'<br><pre>' ..
mw.text.nowiki(mw.text.jsonEncode(args, mw.text.JSON_PRETTY)) ..
'</pre><br>'
end
return result
end
-- Create a table row to describe a specific value
-- Usually rendered as key | value | element | comment | rendering | photo
function p.row(frame)
local args = getArgs(frame)
if args[1] and not args.key then
args.key = args[1]
end
if args[2] and not args.value then
args.value = args[2]
end
-- Unlike sidecard, table could be used in different types of pages, and should not rely on auto-guessing
assert(args.key, 'Missing key=... parameter')
assert(args.value, 'Missing value=... parameter')
-- initialize self - parse title, language, and get relevant entities
local self = constructor(args)
if self.entity then
local qid = self.entity:getId()
validateKeyValue(self)
-- Compare all known parameters against the data item entity
processValue(self, 'description',
self.entity:getDescription(self.langCode),
args.description, qid) -- add edit links to description
value, usedFb, ref = p.getClaimValue(p.IMAGE, self.langCode, self.entity, self.fallbackEntity)
processValue(self, 'photo', value and 'File:' .. value or nil)
local render = getItemValue(self, p.RENDER_IMAGE)
processValue(self, 'osmcarto-rendering', render and 'File:' .. render or nil)
-- Handle onRelation, onArray, onWay, and onNode
processValue(self, 'onNode', getItemValue(self, p.USE_ON_NODES), normalizeBoolean(args.onNode))
processValue(self, 'onWay', getItemValue(self, p.USE_ON_WAYS), normalizeBoolean(args.onWay))
processValue(self, 'onArea', getItemValue(self, p.USE_ON_AREAS), normalizeBoolean(args.onArea))
processValue(self, 'onRelation', getItemValue(self, p.USE_ON_RELATIONS), normalizeBoolean(args.onRelation))
end
local elems = {}
if 'yes' == args.onNode then
table.insert(elems, 'iconNode')
end
if 'yes' == args.onWay then
table.insert(elems, 'iconWay')
end
if 'yes' == args.onArea then
table.insert(elems, 'iconArea')
end
if 'yes' == args.onRelation then
table.insert(elems, 'iconRelation')
end
args.render = args.render and '[[' .. args.render .. '|100px]]'
args.photo = args.photo and '[[' .. args.photo .. '|100px]]'
if args.description2 then
args.description = args.description .. '<br>' .. args.description2
end
-- key | value | element | comment | render | photo
local resultTbl = {
args.key,
args.value,
elems,
args.description or '',
args.render or '',
args.photo or '',
}
local categories = getCategories(self)
if args.debuglua then
-- debug and unit test support
return { args = args, categories = categories, row = resultTbl }
end
-- expand templates
local lang = self.langCode
for i, v in ipairs(elems) do
elems[i] = frame:expandTemplate { title = v }
end
local result = '|-\n| ' .. table.concat({
frame:expandTemplate { title = 'TagKey/exists', args = { resultTbl[1], lang = lang } },
frame:expandTemplate { title = 'TagValue/exists', args = { resultTbl[1], resultTbl[2], lang = lang } },
table.concat(elems, ''),
resultTbl[4],
resultTbl[5],
resultTbl[6],
}, '\n| ')
if categories then
result = result .. categories
end
if args.debugargs then
result = result ..
'<br><pre>' ..
mw.text.nowiki(mw.text.jsonEncode(resultTbl, mw.text.JSON_PRETTY)) ..
'</pre><br><pre>' ..
mw.text.nowiki(result) ..
'</pre><br>'
end
return result
end
-- ##########################################################################
-- DEBUGGING AND TESTING SUPPORT
-- ##########################################################################
-- From the debug console, use =p.dbg{title='Key:bridge'}
function p.dbg(args)
args.currentTitle = args.title or 'Key:bridge:movable'
args.debuglua = args.debuglua == nil and true or args.debuglua
local frame = mw.getCurrentFrame():newChild { title = 'Module:DescriptionFromDataItem', args = args }
return mw.text.jsonEncode(p.main(frame), mw.text.JSON_PRETTY)
end
function p.dbgFmtDesc(desc)
return formatDescription(desc, mw.getCurrentFrame():newChild { title = 'Module:DescriptionFromDataItem' })
end
-- From the debug console, use =p.dbgrow{key='bridge', value='movable'}
function p.dbgrow(args)
local frame = mw.getCurrentFrame():newChild { title = 'Module:DescriptionFromDataItem', args = args }
return p.row(frame)
end
-- Debug helper for statements
-- =p.stmt(p.GROUP, 'Q501', 'en')
function p.stmt(prop, id, lang)
return mw.text.jsonEncode(
{ p.getClaimValue(prop, lang, mw.wikibase.getEntity(id)) },
mw.text.JSON_PRETTY)
end
-- These methods could be overwritten by unit tests
function p.wbGetEntity(entity)
return mw.wikibase.getEntity(entity)
end
function p.wbGetEntityIdForTitle(title)
return mw.wikibase.getEntityIdForTitle(title)
end
function p.wbGetLabel(qid)
return mw.wikibase.getLabel(qid)
end
function p.wbGetLabelByLang(qid, langCode)
return mw.wikibase.getLabelByLang(qid, langCode)
end
return p