require('strict')-- local genitive = require('Module:Genitive')._genitivelocal contLangCode = mw.language.getContentLanguage:getCode
local cmodule = local conf = require 'Module:External links/conf'(contLangCode)local hasdatafromwikidata = falselocal hasdatafromlocal = falselocal haswikidatalink = true -- whether or not this page has a wikidata entity(?); we assume there is one at first
local p =
local function getLabel(entity, use_genitive, pagetitle) local label = (pagetitle ~= ) and pagetitle or nil if not label and not entity then label = mw.title.getCurrentTitle.text elseif not label then label = mw.wikibase.getLabel(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
-- 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 regexConverterTest(regex, str) regex = regex:gsub("\\d", function(num) return string.rep("%d", num) end) return string.find(str, '^' .. regex .. '$')end
local function getFormatterUrl(prop, value) local fUrl = "" local statements = mw.wikibase.getBestStatements(prop, "P1630") -- to avoid deep tests if #statements
'value' and regexConverterTest(qualsnak.datavalue.value, value) then -- it matched, this is correct and overrides any other. fUrl = mainsnak.datavalue.value break end end elseif fUrl
local function getLanguageData(prop, qid, separator) -- Formerly outputted a table, but this function was always run through table.concat when invoked, so it has been simplified to yield a string separator = separator or local output = if not mw.wikibase.entityExists(qid) then -- yield error, which would originally happen because table.concat(nil) errors error("getLanguageData was given a nonexistent entity") end -- get claims local statements = mw.wikibase.getBestStatements(qid, prop) -- to avoid deep tests if #statements
'value' then -- if this is the correct P-value, dive into it and get P218 (ISO 639-1) if prop
'P218' or prop
local langqvalorder = -- check `language of work or name` first, `original language of film or TV show` secondlocal otherqvalorder =
local function getValuesFromWikidata(linkTemplate) local output = -- mw.log("getValuesFromWikidata, linkTemplate="..dump(linkTemplate)) -- get statements local entity = mw.wikibase.getEntity -- check if the entity exists -- TODO: check if we can skip distinguishing between no entity vs. no statements if not entity then -- check if the entity exists return nil end local statements = entity:getBestStatements(linkTemplate.prop) -- to avoid deep tests if #statements
'value' then -- now get the actual data langcode = getLanguageData('P305', qualsnak.datavalue.value.id) end end end -- mw.log("langcode is now="..dump(langcode)) end if string.find(langcode, "[pP]%d+") then -- we still don't have any langcode, so we default to "en" langcode = nil end end -- if langcode and langcode ~= then output[#output].langcode = langcode end end -- mw.log("getValuesFromWikidata returning head="..dump(head).." tail="..dump(tail)) return outputend
local function findMainLinksOnWikidata(linkTemplate, pagetitle, short_links) local output = -- get the entity we are checking local entity = mw.wikibase.getEntity -- to avoid deep tests if not entity then return nil end local values = getValuesFromWikidata(linkTemplate) for _, value in ipairs(values) do local verified_value = value.value if not (linkTemplate.regex and regexConverterTest(linkTemplate.regex, value.value)) then output[#output+1] = -- Search for a url formatter local url = if linkTemplate.url_f then -- we have a locally defined url-formatter function from the config, use it as first priority url = linkTemplate.url_f(verified_value) if linkTemplate.track and not string.find(linkTemplate.langcode, "[pP]%d+") then output[#output].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-local-wd'), linkTemplate.prop):plain elseif linkTemplate.track then output[#output].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-wd-wd'), linkTemplate.prop):plain end elseif linkTemplate.url then -- we have a locally defined url-formatter string from the config, use it as second priority url = mw.message.newRawMessage(linkTemplate.url, verified_value):plain if linkTemplate.track and not string.find(linkTemplate.langcode, "[pP]%d+") then output[#output].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-local-wd'), linkTemplate.prop):plain elseif linkTemplate.track then output[#output].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-wd-wd'), linkTemplate.prop):plain end else -- config has no url formatter; check if Wikidata has one on the property local formatterUrl = getFormatterUrl(linkTemplate.prop, verified_value) if formatterUrl ~= then url = mw.message.newRawMessage(formatterUrl, verified_value):plain if linkTemplate.track then output[#output].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-wd-wd'), linkTemplate.prop):plain end end end if url ~= then local langlink = (value.langcode and value.langcode ~= and value.langcode ~= contLangCode) and mw.message.newRawMessage(conf:g('msg-langcode'), value.langcode, mw.language.fetchLanguageName(value.langcode, contLangCode)) or "" output[#output].text = mw.message.newRawMessage(short_links and linkTemplate.short or linkTemplate.message, getLabel(entity, linkTemplate.genitive, pagetitle), url, langlink, verified_value, mw.uri.encode(verified_value, 'PATH')) :plain end end end --mw.log("findMainLinksOnWikidata returning="..dump(output)) return outputend
local function getSitelinkFromWikidata(linkTemplate, entity) -- to avoid deep tests if not entity then entity = mw.wikibase.getEntity if not entity then --mw.log("getSitelinkFromWikidata no entity") return nil end end local requested_sitelink = string.match(linkTemplate.prop, "SL(%l+)") -- a specific wiki to be linked to can be specified by config; otherwise, default to this wiki local sitelink = entity:getSitelink(requested_sitelink) return sitelink or nilend
-- This function has a bug: :getSitelink does not return an object - only a string; yet this function tries to access sitelink.langcodelocal function findSiteLinksOnWikidata(linkTemplate, pagetitle, short_links) local output = local sitelink = getSitelinkFromWikidata(linkTemplate) -- verify existence of sitelink if not sitelink then return nil end if not (linkTemplate.regex and not regexConverterTest(linkTemplate.regex, sitelink)) then output[1] = -- Search for a url-formatter local url = if linkTemplate.url_f then -- we have a locally defined url-formatter function from the config, use it as first priority url = linkTemplate.url_f(sitelink) elseif linkTemplate.url then -- we have a locally defined url-formatter string from the config, use it as second priority url = mw.message.newRawMessage(linkTemplate.url, sitelink):plain else url = sitelink:gsub(' ','_') end if linkTemplate.track and not string.find(linkTemplate.langcode, "SL%l+") and (linkTemplate.url_f or linkTemplate.url) then output[1].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-local-wd'), linkTemplate.prop):plain elseif linkTemplate.track then output[1].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-wd-wd'), linkTemplate.prop):plain end if url ~= then local langlink = (sitelink.langcode and sitelink.langcode ~= and sitelink.langcode ~= contLangCode) and mw.message.newRawMessage(conf:g('msg-langcode'), sitelink.langcode, mw.language.fetchLanguageName(sitelink.langcode, contLangCode)) or "" output[1].text = mw.message.newRawMessage(short_links and linkTemplate.short or linkTemplate.message, getLabel(entity, linkTemplate.genitive, pagetitle), url, langlink, sitelink, mw.uri.encode(sitelink, 'PATH')) :plain end end --mw.log("findSiteLinksOnWikidata returning="..dump(output)) return outputend
local function findMainLinksLocal(linkTemplate, pagetitle, short_links, local_value) local output = -- to avoid deep tests if not local_value or local_value
'table', "Something went wrong: wikidata_values is neither a table nor nil") if linkTemplate.track and wikidata_values then local local_value_in_wikidata = false for _,value in ipairs(wikidata_values) do if value.value
local function findSiteLinksLocal(linkTemplate, pagetitle, short_links, local_value) local output = -- to avoid deep tests if not local_value or local_value
local_value) output[1].category[1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, (local_value_in_wikidata and 'track-cat-local-wd-equal' or 'track-cat-local-wd-unequal')), linkTemplate.prop):plain end -- Search for a url formatter local url = if linkTemplate.url_f then -- we have a locally defined url-formatter function from the config, use it as first priority url = linkTemplate.url_f(local_value) if linkTemplate.track then output[1].category[#output[1].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-local-local'), linkTemplate.prop):plain end elseif linkTemplate.url then -- we have a locally defined url-formatter string from the config, use it as second priority url = mw.message.newRawMessage(linkTemplate.url, local_value):plain if linkTemplate.track then output[1].category[#output[1].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-local-local'), linkTemplate.prop):plain end else -- we know wikidata_property exists url = local_value:gsub(' ','_') if linkTemplate.track then output[1].category[#output[1].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLangCode, 'track-cat-wd-local'), linkTemplate.prop):plain end end local langlink = (output[1].langcode and output[1].langcode ~= and output[1].langcode ~= contLangCode) and mw.message.newRawMessage(conf:g('msg-langcode'), linkTemplate.langcode, mw.language.fetchLanguageName(linkTemplate.langcode, contLangCode)) or "" output[1].text = mw.message.newRawMessage(short_links and linkTemplate.short or linkTemplate.message, getLabel(nil, linkTemplate.genitive, pagetitle), url, langlink, local_value, mw.uri.encode(local_value, 'PATH')) :plain end --mw.log("findSiteLinksLocal returning="..dump(output)) return outputend
local function addLinkback(str, property) local id = mw.wikibase.getEntityIdForCurrentPage if not id then return str end local class = local url = if property then class = 'wd_' .. string.lower(property) url = mw.uri.fullUrl('d:' .. id .. '#' .. property) url.fragment = property else url = mw.uri.fullUrl('d:' .. id) end local title = conf:g('wikidata-linkback-edit') local icon = '[%s [[File:Blue pencil.svg|%s|10px|text-top|link=]] ]' url = tostring(url) local v = mw.html.create('span') :addClass(class) :wikitext(str) :tag('span') :addClass('noprint plainlinks wikidata-linkback') :css('padding-left', '.3em') :wikitext(icon:format(url, title)) :allDone return tostring(v)end
local function getArgument(frame, argument) local args = frame.args if args[1]
local function removeEntry(conf_claims, identifier, property) for i, linkTemplate in ipairs(conf_claims) do if linkTemplate[identifier]
function p.getLinks(frame, customClaims) --customClaims is a backdoor for testcases 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 conf_claims = customClaims or cmodule:getConfiguredClaims(contLangCode) local limits = cmodule:getLimits assert(limits, mw.message.newRawMessage(conf:g('missing-limits'), configured_conf):plain) local links_shown = tonumber(getArgument(frame, conf:a('arg-maxlink'))) or limits['links-shown'] or 10 -- maximum links to display 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] = true -- cheat to make #req_lang indicate populated table req_lang[1] = true end end end local short_links = getArgument(frame, conf:a('arg-short')) short_links = (short_links and short_links ~= or false) local showinline = getArgument(frame, conf:a('arg-inline')) showinline = (showinline and showinline ~= or false) local somedataonwikidata = not short_links --mw.log("conf_claims="..dump(conf_claims)) --mw.log("req_prop="..dump(req_prop)) --mw.log("req_lang="..dump(req_lang)) --mw.log("short_links="..dump(short_links)) local output = local category = for _, linkTemplate in ipairs(conf_claims) do -- if we're called with a list of approved properties or languages, check if this one is "approved" if (#req_prop
0 or req_lang[linkTemplate.langcode] or string.find(linkTemplate.langcode, "[pP]%d+")) then -- Error if linkTemplate.prop is nonexistent, as it is required assert(linkTemplate.prop, "malformed linkTemplate from config (no .prop given): " .. dump(linkTemplate)) local links = local checkedonwikidata = false -- get the any local overriding value from the call local wikivalue = getArgument(frame, linkTemplate.prop) if (not wikivalue or wikivalue
nil then -- a nil-value indicated no wikidata-link haswikidatalink = false links = else checkedonwikidata = true end elseif (not wikivalue or wikivalue
nil then -- a nil-value indicated no wikidata-link haswikidatalink = false links = else checkedonwikidata = true end elseif string.find(linkTemplate.prop, "SL%l+") then -- we know that wikivalue is set if this is true -- this is a sitelink-type (SLspecieswiki) links = findSiteLinksLocal(linkTemplate, pagetitle, short_links, wikivalue) elseif wikivalue and wikivalue ~= then -- the property is of another annotation, and therefore a local construct links = findMainLinksLocal(linkTemplate, pagetitle, short_links, wikivalue) end --mw.log("links="..dump(links)) for _,v in ipairs(links) do -- we'll have to check langcodes again as they may have come from wikidata if (#req_lang
0 then output[1] = v.text else output[#output] = output[#output] .. cmodule:getMessage(contLangCode,'short-list-separator') .. v.text end somedataonwikidata = true elseif not short_links and not showinline and v.text and v.text ~= then -- only if short links were not requested output[#output+1] = (#output ~= 0 and conf:g('msg-ul-prepend') or ) -- if this is the first link, we won't output a list-element (msg-ul-prepend) .. (checkedonwikidata and addLinkback(v.text, linkTemplate.prop) or v.text) -- if the link comes from wikidata, also output a linkback. elseif not short_links and v.text and v.text ~= then -- and showinline -- only if short links were not requested output[#output+1] = v.text end if linkTemplate.track then -- add category if tracking is on for this property and a category exists in the link-result. for _,cats in ipairs(v.category) do category[#category+1] = cats end end if links_shown
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(contLangCode,'short-list-separator')), nil) or table.concat(output,cmodule:getMessage(contLangCode,'short-list-separator'))) elseif not showinline and #output>0 then -- and not shortlinks outtext = table.concat(output,"\n") elseif #output>0 then -- and not short_links and showinline outtext = table.concat(output,conf:g('msg-inline-separator')) end if not hasdatafromwikidata then category[#category+1] = cmodule:getMessage(contLangCode, 'no-data-cat') if not hasdatafromlocal and not short_links then outtext = cmodule:getMessage(contLangCode, 'no-data-text') end end if not haswikidatalink then category[#category+1] = cmodule:getMessage(contLangCode, 'no-wikilink-cat') if not hasdatafromlocal and not short_links then outtext = cmodule:getMessage(contLangCode, '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')) return getLanguageData(prop, nil, conf:a(mod-filter-separator))end
return p