Module:DescriptionFromDataItem
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 |
☒N 8 tests failed.
| Name | Expected | Actual | |
|---|---|---|---|
| ☒N | 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]] | |
| ☑Y | test_french | ||
| ☒N | 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> | |
| ☒N | 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]] | |
| ☒N | 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]] | |
| ☒N | 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]] | |
| ☒N | 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> | |
| ☒N | 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> | |
| ☒N | 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
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit) pages.
Add categories to the /doc subpage. Subpages of this module.
local getArgs = require('Module:Arguments').getArgs local titleParser = require('Module:OsmPageTitleParser') 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.KEY_PREFIX_ID = { id = 'P52', fallback = true } p.KEY_SUFFIX_ID = { id = 'P53', fallback = true } 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 } p.USE_ON_CHANGESETS = { id = 'P37', 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 eKey = p.getClaimValue(p.KEY_PREFIX_ID, 'en', entity) if eKey then return { eKey .. ':' } end eKey = p.getClaimValue(p.KEY_SUFFIX_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, langCode) if prop.multi then return entity:getBestStatements(prop.id) elseif prop.id == 'P28' and langCode == 'en' then -- For multiple images, get the best (the image with the highest rank, e.g. "preferred") in case of English description page. 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, langCode) if fallbackEntity and prop.fallback and next(statements) == nil then usedFallback = true statements = getStatements(fallbackEntity, prop, langCode) 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 == 'key-prefix' or self.args.type == 'key-suffix' or self.args.type == 'relation' then -- Ignore for key-prefixes, key-suffixes and 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 -- Doesn't add "Category:Mismatched image" if "|image=" on wiki page is set with "Image:" instead of "File:" elseif argname == 'image' and pageVal:gsub(" ", "_") == 'image:' .. getItemValue(self, p.IMAGE):gsub(" ", "_") then do return end -- Doesn't add "Category:Mismatched image" if "|image=" on wiki page is set with "image:" instead of "File:" 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 if argname == 'description' then table.insert(self.categories, 'Mismatched description|' .. mw.title.getCurrentTitle().prefixedText) else table.insert(self.categories, 'Mismatched ' .. argname) end 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) processValue(self, 'project') -- 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, onArea, onWay, onNode, and onChangeset 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)) processValue(self, 'onChangeset', getItemValue(self, p.USE_ON_CHANGESETS), normalizeBoolean(args.onChangeset)) 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' and not args.debug 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') elseif args.status ~= 'deprecated' then 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, '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 local title = 'Tag' if val[1]:sub(-1) == ':' then title = 'Prefix' val[1] = val[1]:sub(1, #val[1] - 1) elseif val[1]:sub(1, 1) == ':' then title = 'Suffix' val[1] = val[1]:sub(2) end result = result .. '* ' .. frame:expandTemplate { title = title, 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, onArea, onWay, onNode, and onChangeset 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)) processValue(self, 'onChangeset', getItemValue(self, p.USE_ON_CHANGESETS), normalizeBoolean(args.onChangeset)) 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