require('strict')-- local genitive = require('Module:Genitive')._genitivelocal contLang = mw.language.getContentLanguage
local cmodule = local conf = require 'Module:External links/conf'(contLang:getCode)local hasdatafromwikidata = falselocal hasdatafromlocal = falselocal haswikidatalink = true -- we assume it's connected
local p =
local function getLabel(entity, use_genitive, pagetitle) local label = (pagetitle and pagetitle ~= ) and pagetitle or nil if not label and not entity then label = mw.title.getCurrentTitle.text elseif not label then label = mw.wikibase.label(entity.id) or mw.title.getCurrentTitle.text end-- return use_genitive and genitive(label, 'sitt') or label return use_genitive and label .. "'s" or labelend
-- @todo cleanup, this is in production, use the consolelocal function dump(obj) return "
" .. mw.dumpObject(obj) .. ""end
local function stringFormatter(datavalue) if datavalue
local pval = pval.P1793 =
pval.P407 =
pval.P364 =
pval.P218 =
pval.P305 =
pval.P582 =
-- This is a really makeshift crappy converter, but it'll do some basic-- conversion from PCRE to Lua-style patterns (note that this only work-- in very few cases)local function regexConverter(regex) local output = regex output = string.gsub(output, "\\d", "%%d%%d") output = string.gsub(output, "\\d", "%%d%%d%%d") output = string.gsub(output, "\\d", "%%d%%d%%d%%d") output = string.gsub(output, "\\d", "%%d%%d%%d%%d%%d") output = string.gsub(output, "\\d", "%%d%%d%%d%%d%%d%%d") output = string.gsub(output, "\\d", "%%d%%d%%d%%d%%d%%d%%d") output = string.gsub(output, "\\d", "%%d%%d%%d%%d%%d%%d%%d%%d") output = string.gsub(output, "\\d", "%%d") return outputend
local function getFormatterUrl(prop, value) local head = "" local tail = "" local entity = mw.wikibase.getEntity(prop) -- to avoid deep tests if not entity or not entity.claims then return head end -- get the claims for this entity local statements = entity.claims['P1630'] -- formatter URL -- to avoid deep tests if not statements then return head end local formatters = -- let's go through the claims for _, claim in ipairs(statements) do -- to avoid deep tests if not claim then claim = end local valid = claim['type']
'preferred' -- get any qualifiers for this claim (we are interested in P1793 for -- indication of which claim is correct) local qualifiers = claim.qualifiers or -- now let's check the qualifier we are interested in local qualid = 'P1793' -- format as a regular expression -- if the claim has this qualifier if qualifiers[qualid] then -- it's here, let's check it out! local items = -- traverse all snaks in this qualifier for _, qualsnak in ipairs(qualifiers[qualid]) do if qualsnak and pval[qualid] then --mw.log("qualsnak = " .. dump(qualsnak)) -- check if the snak is of the correct snaktype and datatype local valid = qualsnak.snaktype
pval[qualid].types.datatype if valid then -- we'll have to convert the regex to Lua-style local regex = regexConverter(qualsnak.datavalue.value) local test = string.match(value, '^'..regex..'$') if test then -- it matched, this is correct and overrides any other. if preferred then head = mainsnak.datavalue.value else tail = mainsnak.datavalue.value end end end end end else -- we don't have any qualifier, is it preferred? if (head
and not preferred) then -- if we don't have any other, use this one if preferred and head
then tail = mainsnak.datavalue.value end end end end end return head ~= and head or tail
end
local function getLanguageData(prop, qid) local head = local tail = -- mw.log("getLanguageData, prop="..dump(prop).." qid="..dump(qid)) -- get the entity we are checking local entity = mw.wikibase.getEntityObject(qid) -- to avoid deep tests if not entity then return nil end if not entity.claims then return end -- get the claims for this entity local statements = entity.claims[prop] -- to avoid deep tests if not statements then return end -- mw.log("getLanguageData going through claims="..dump(statements)) -- let's go through the claims for _, claim in ipairs(statements) do -- to avoid deep tests if not claim then claim = end local valid = claim['type']
'preferred' -- verify the item is what we expect local valid = mainsnak.snaktype
pval[prop].types.datatype and mainsnak.datavalue.type
'P364' then -- original language of work if preferred then head[#head+1] = table.concat(getLanguageData('P218', 'Q'..mainsnak.datavalue.value['numeric-id']), conf:a('mod-filter-separator')) else tail[#tail+1] = table.concat(getLanguageData('P218', 'Q'..mainsnak.datavalue.value['numeric-id']), conf:a('mod-filter-separator')) end elseif mainsnak.property
'P305' then -- ISO 639-1 code or IETF language tag if preferred then head[#head+1] = stringFormatter(mainsnak.datavalue) else tail[#tail+1] = stringFormatter(mainsnak.datavalue) end end end end end -- mw.log("getLanguageData returning head="..dump(head).." tail="..dump(tail)) return #head>0 and head or tailend
local langqvalorder = local otherqvalorder =
local function getValuesFromWikidata(props) local head = local tail = -- mw.log("getValuesFromWikidata, props="..dump(props)) -- get the entity we are checking local entity = mw.wikibase.getEntityObject -- to avoid deep tests if not entity then --mw.log("getValuesFromWikidata no entity") return nil end if not entity.claims or not props or not props.prop or props.prop
'statement' and claim['rank'] ~= 'deprecated' if valid then -- mw.log("getValuesFromWikidata valid claim="..dump(claim)) local mainsnak = claim.mainsnak or local preferred = claim['rank']
pval[qualid].types.snaktype and qualsnak.datatype
pval[qualid].types.snaktype and qualsnak.datatype
local function findMainLinksOnWikidata(props, pagetitle, short_links) local output = local pid = nil -- get the entity we are checking local entity = mw.wikibase.getEntityObject -- to avoid deep tests if not entity then return nil end local values = getValuesFromWikidata(props) for _, value in ipairs(values) do local verified_value = nil if props.regex then -- we have a local defined regex, so this will have to pass first -- maybe we'll have to convert the regex to Lua-style local regex = regexConverter(props.regex) local test = string.match(value.value, '^'..regex..'$') --mw.log("testing with "..regex.. " and test="..dump(test).." and value="..id) if test then -- it matched, this is correct and overrides any other. verified_value = value.value end else verified_value = value.value end if verified_value then local url = output[#output+1] = output[#output].langcode = value.langcode output[#output].category = if props.url_f then -- we have a local defined url-formatter function, use it as first priority url = props.url_f(verified_value) if props.track and not string.find(props.langcode, "([pP]%d+)") then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-wd'), props.prop):plain elseif props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-wd'), props.prop):plain end elseif props.url then -- we have a local defined url-formatter string, use it as second priority url = mw.message.newRawMessage(props.url, verified_value):plain if props.track and not string.find(props.langcode, "([pP]%d+)") then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-wd'), props.prop):plain elseif props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-wd'), props.prop):plain end else -- get the formatvalue from the property, if it exists local formatterUrl = getFormatterUrl(props.prop, verified_value) if formatterUrl ~= then url = mw.message.newRawMessage(formatterUrl, verified_value):plain if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-wd'), props.prop):plain end end end if url ~= then local this_wiki = mw.getContentLanguage local this_wiki_code = this_wiki:getCode local langlink = (value.langcode and value.langcode ~= and value.langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), value.langcode, mw.language.fetchLanguageName(value.langcode, this_wiki_code)) or "" if short_links and props.short then output[#output].text = mw.message.newRawMessage(props.short, getLabel(entity, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain else output[#output].text = mw.message.newRawMessage(props.message, getLabel(entity, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain end end end end --mw.log("findMainLinksOnWikidata returning="..dump(output)) return outputend
local function getSitelinksFromWikidata(props, entity) local output = --mw.log("getSitelinksFromWikidata, props="..dump(props)) -- to avoid deep tests if not entity then entity = mw.wikibase.getEntityObject if not entity then --mw.log("getSitelinksFromWikidata no entity") return nil end end local requested_sitelink = string.match(props.prop, "SL(%l+)") local sitelinks = entity:getSitelink(requested_sitelink) if sitelinks and sitelinks ~= then output[#output+1] = end --mw.log("getSitelinksFromWikidata returning output="..dump(output)) return outputend
local function findSiteLinksOnWikidata(props, pagetitle, short_links) local output = local pid = nil -- get the entity we are checking local entity = mw.wikibase.getEntityObject -- to avoid deep tests if not entity then return nil end local values = getSitelinksFromWikidata(props) for _, value in ipairs(values) do local verified_value = nil if props.regex then -- we have a local defined regex, so this will have to pass first -- maybe we'll have to convert the regex to Lua-style local regex = regexConverter(props.regex) local test = string.match(value.value, '^'..regex..'$') --mw.log("testing with "..regex.. " and test="..dump(test).." and value="..id) if test then -- it matched, this is correct and overrides any other. verified_value = value.value end else verified_value = value.value end if verified_value then --mw.log("it's verified..") local url = output[#output+1] = output[#output].langcode = value.langcode output[#output].category = if props.url_f then -- we have a local defined url-formatter function, use it as first priority url = props.url_f(verified_value) if props.track and not string.find(props.langcode, "(SL%l+)") then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-wd'), props.prop):plain elseif props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-wd'), props.prop):plain end elseif props.url then -- we have a local defined url-formatter string, use it as second priority url = mw.message.newRawMessage(props.url, verified_value):plain if props.track and not string.find(props.langcode, "(SL%l+)") then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-wd'), props.prop):plain elseif props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-wd'), props.prop):plain end else url = verified_value:gsub(' ','_') if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-wd'), props.prop):plain end end if url ~= then local this_wiki = mw.getContentLanguage local this_wiki_code = this_wiki:getCode local langlink = (value.langcode and value.langcode ~= and value.langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), value.langcode, mw.language.fetchLanguageName(value.langcode, this_wiki_code)) or "" if short_links and props.short then output[#output].text = mw.message.newRawMessage(props.short, getLabel(entity, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain else output[#output].text = mw.message.newRawMessage(props.message, getLabel(entity, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain end end end end --mw.log("findSiteLinksOnWikidata returning="..dump(output)) return outputend
local function findMainLinksLocal(props, pagetitle, short_links, local_value) local output = -- to avoid deep tests if not props.prop then return nil end if not local_value or local_value
verified_value then local_value_in_wikidata = true end end output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, (local_value_in_wikidata and 'track-cat-local-wd-equal' or 'track-cat-local-wd-unequal')), props.prop):plain end if wikidata_property and wikidata_values and #wikidata_values then hasdatafromwikidata = true -- signal up the chain this article has a wikidata claim end if props.url_f then -- we have a local defined url-formatter function, use it as first priority url = props.url_f(verified_value) if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-local'), props.prop):plain end elseif props.url then -- we have a local defined url-formatter string, use it as second priority url = mw.message.newRawMessage(props.url, verified_value):plain if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-local'), props.prop):plain end elseif wikidata_property then -- get the formatvalue from the property, if it exists local formatterUrl = getFormatterUrl(props.prop, verified_value) if formatterUrl ~= then url = mw.message.newRawMessage(formatterUrl, verified_value):plain if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-local'), props.prop):plain end end else -- no other choice, bail out return end local this_wiki = mw.getContentLanguage local this_wiki_code = this_wiki:getCode local langlink = (output[#output].langcode and output[#output].langcode ~= and output[#output].langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), props.langcode, mw.language.fetchLanguageName(props.langcode, this_wiki_code)) or "" if short_links and props.short then output[#output].text = mw.message.newRawMessage(props.short, getLabel(nil, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain else output[#output].text = mw.message.newRawMessage(props.message, getLabel(nil, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain end end --mw.log("findMainLinksLocal returning="..dump(output)) return outputend
local function findSiteLinksLocal(props, pagetitle, short_links, local_value) local output = -- to avoid deep tests if not props.prop then return nil end if not local_value or local_value
verified_value then local_value_in_wikidata = true end end output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, (local_value_in_wikidata and 'track-cat-local-wd-equal' or 'track-cat-local-wd-unequal')), props.prop):plain end if wikidata_property and wikidata_values and #wikidata_values then hasdatafromwikidata = true -- signal up the chain this article has a wikidata claim end if props.url_f then -- we have a local defined url-formatter function, use it as first priority url = props.url_f(verified_value) if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-local'), props.prop):plain end elseif props.url then -- we have a local defined url-formatter string, use it as second priority url = mw.message.newRawMessage(props.url, verified_value):plain if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-local-local'), props.prop):plain end elseif wikidata_property then url = verified_value:gsub(' ','_') if props.track then output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode, 'track-cat-wd-local'), props.prop):plain end else -- no other choice, bail out return end local this_wiki = mw.getContentLanguage local this_wiki_code = this_wiki:getCode local langlink = (output[#output].langcode and output[#output].langcode ~= and output[#output].langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), props.langcode, mw.language.fetchLanguageName(props.langcode, this_wiki_code)) or "" if short_links and props.short then output[#output].text = mw.message.newRawMessage(props.short, getLabel(nil, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain else output[#output].text = mw.message.newRawMessage(props.message, getLabel(nil, props.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain end end --mw.log("findSiteLinksLocal returning="..dump(output)) return outputend
local function addLinkback(str, property) local id = mw.wikibase.getEntityObject if not id then return str end if type(id)
local function getArgument(frame, argument) local args = frame.args if args[1]
local function removeEntry(conf_claims, identifier, property) for i, props in ipairs(conf_claims) do if props[identifier]
function p.getLinks(frame) local configured_conf = getArgument(frame, conf:a('arg-conf')) if configured_conf then cmodule = require ('Module:External_links/conf/'..configured_conf) else error(mw.message.newRawMessage(conf:g('missing-conf'), configured_conf):plain) end local output = local category = local conf_claims = cmodule:getConfiguredClaims(contLang:getCode) local limits = cmodule:getLimits assert(limits, mw.message.newRawMessage(conf:g('missing-limits'), configured_conf):plain) local links_shown = getArgument(frame, conf:a('arg-maxlink')) local pagetitle = getArgument(frame, conf:a('arg-title')) -- get a list of tracked properties from the article itself local requested_tracking = getArgument(frame, conf:a('arg-track')) if requested_tracking and requested_tracking ~= then -- the properties should be written as P1234, P2345 and other -- version corresponding to the applicable property-identifiers in the config for track_prop in string.gmatch(requested_tracking,"([^,;:]+)") do -- get the requested properties and be able to access them -- like req_prop['P345'] to verify if it was requested local remove_track = string.match(track_prop, "^\-(.*)") for i,claim in ipairs (conf_claims) do if remove_track
conf:a('mod-filter-all') then -- if a property starts with "-", then we'll simply remove that -- property from the conf_claims conf_claims[i]['track'] = false elseif track_prop
conf:a('mod-filter-all') then conf_claims[i]['track'] = true end end end end -- get a list of "approved" properties from the article itself local requested_properties = getArgument(frame, conf:a('arg-properties')) --mw.log("requested_properties="..dump(requested_properties)) -- assume all properties are allowed local req_prop = local no_req_prop = false -- we'll allow properties to be filtered for now if requested_properties and requested_properties ~= then -- the properties should be written as P1234, P2345 and other -- version corresponding to the applicable property-identifiers in the config for i in string.gmatch(requested_properties,"([^,;:]+)") do -- get the requested properties and be able to access them -- like req_prop['P345'] to verify if it was requested if i
conf:a('mod-filter-all') then -- this is a special modifier, saying we should ignore -- all previous and future positive filters and remove the -- filter (with exception of negative filters) req_lang = no_req_lang = true end -- like req_lang['en'] to verify if it was requested local remove_lang = string.match(i, "^\-(.*)") if remove_lang then -- if a language starts with "-", then we'll simply remove that -- language from the conf_claims conf_claims = removeEntry(conf_claims, 'langcode', remove_lang) elseif not no_req_lang then -- only if we are allowing languages to be filtered req_lang[i] = 1 -- cheat to make #req_lang indicate populated table req_lang[1] = 1 end end end local short_links = getArgument(frame, conf:a('arg-short')) if short_links and short_links ~= then short_links = true else short_links = false end local showinline = getArgument(frame, conf:a('arg-inline')) if showinline and showinline ~= then showinline = true else showinline = false end if not links_shown or links_shown
0 or req_prop[props.prop]) and (#req_lang
"") and string.find(props.prop, "([pP]%d+)") then -- the property is a Pnnn type, and therefore on Wikidata links = findMainLinksOnWikidata(props, pagetitle, short_links) if links
"") and string.find(props.prop, "(SL%l+)") then -- this is a sitelink-type (SLspecieswiki) --mw.log("finding sitelinks..") links = findSiteLinksOnWikidata(props, pagetitle, short_links) if links
0 or req_lang[v.langcode]) then if checkedonwikidata and not hasdatafromwikidata then -- add a general tracking category for articles with data from wikidata hasdatafromwikidata = true category[#category+1] = cmodule:getMessage(contLang:getCode, 'with-data-cat') elseif not checkedonwikidata and not hasdatafromlocal then -- add a general tracking category for articles with data from template-calls in local articles hasdatafromlocal = true category[#category+1] = cmodule:getMessage(contLang:getCode, 'with-local-cat') end if short_links and props.short and v.text and v.text ~= then -- if short links were requested, and a short definition exists for this property, let's use it if #output
0 then break end end end local outtext = "" if short_links and #output>0 then -- if these are short links, output the whole thing with linkback to wikidata --mw.log("somedataonwikidata="..dump(somedataonwikidata).." and output="..dump(output).." and #output="..dump(#output)) outtext = (somedataonwikidata and addLinkback(table.concat(output,cmodule:getMessage(contLang:getCode,'short-list-separator')), nil) or table.concat(output,cmodule:getMessage(contLang:getCode,'short-list-separator'))) elseif not short_links and not showinline and #output>0 then outtext = table.concat(output,"\n") elseif not short_links and showinline and #output>0 then outtext = table.concat(output,conf:g('msg-inline-separator')) end if not hasdatafromwikidata then category[#category+1] = cmodule:getMessage(contLang:getCode, 'no-data-cat') if not hasdatafromlocal and not short_links then outtext = cmodule:getMessage(contLang:getCode, 'no-data-text') end end if not haswikidatalink then category[#category+1] = cmodule:getMessage(contLang:getCode, 'no-wikilink-cat') if not hasdatafromlocal and not short_links then outtext = cmodule:getMessage(contLang:getCode, 'no-wikilink') end end local nocategory = getArgument(frame, conf:a('arg-no-categories')) category = #category>0 and "\n" .. table.concat(category,"\n") or "" --mw.log("nocategory="..dump(nocategory).." and outtext="..dump(outtext).." and category="..dump(category)) outtext = outtext .. (nocategory and or category) return outtextend
function p.getLanguageCode(frame) local prop = getArgument(frame, conf:a('arg-properties')) local output = getLanguageData(prop) return table.concat(output, conf:a('mod-filter-separator'))end
return p