Mô đun:External links

require('strict')
-- local genitive = require('Module:Genitive')._genitive
local contLang = mw.language.getContentLanguage()
local cmodule = {}
local conf = require 'Module:External links/conf'(contLang:getCode())
local hasdatafromwikidata = false
local hasdatafromlocal = false
local 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 label
end
-- @todo cleanup, this is in production, use the console
local function dump(obj)
	return "" .. mw.dumpObject(obj) .. ""
end
local function stringFormatter( datavalue )
	if datavalue == nil or datavalue'type' ~= 'string' then
		return nil
	end
	return datavalue.value
end
local pval = {}
pval.P1793 = { -- format as a regular expression
	types = {
		snaktype = 'value',
		datatype = 'string',
	},
}
pval.P407 = { -- language of work or name
	types = {
		snaktype = 'value',
		datatype = 'wikibase-item',
		datavalue = {
			type = 'wikibase-entityid', 
		}
	},
}
pval.P364 = { -- original language of work 
	types = {
		snaktype = 'value',
		datatype = 'wikibase-item',
		datavalue = {
			type = 'wikibase-entityid', 
		}
	},
}
pval.P218 = { -- ISO 639-1 language 
	types = {
		snaktype = 'value',
		datatype = 'external-id',
		datavalue = {
			type = 'string', 
		}
	},
}
pval.P305 = { -- IETF language tag
	types = {
		snaktype = 'value',
		datatype = 'external-id',
		datavalue = {
			type = 'string', 
		}
	},
}
pval.P582 = { -- end time
	types = {
		snaktype = 'value',
		datatype = 'time',
		datavalue = {
			type = 'string', 
		}
	},
}
-- 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{2}", "%%d%%d")
	output = string.gsub(output, "\\d{3}", "%%d%%d%%d")
	output = string.gsub(output, "\\d{4}", "%%d%%d%%d%%d")
	output = string.gsub(output, "\\d{5}", "%%d%%d%%d%%d%%d")
	output = string.gsub(output, "\\d{6}", "%%d%%d%%d%%d%%d%%d")
	output = string.gsub(output, "\\d{7}", "%%d%%d%%d%%d%%d%%d%%d")
	output = string.gsub(output, "\\d{8}", "%%d%%d%%d%%d%%d%%d%%d%%d")
	output = string.gsub(output, "\\d", "%%d")
	
	return output
end
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' == 'statement'
			and claim'rank' ~= 'deprecated'
		if valid then
			local mainsnak = claim.mainsnak or {}
			local preferred = claim'rank' == '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 qualifiersqualid then
				-- it's here, let's check it out!
				local items = {}
				-- traverse all snaks in this qualifier
				for _, qualsnak in ipairs( qualifiersqualid ) do
					if qualsnak and pvalqualid then
						--mw.log("qualsnak = " .. dump(qualsnak))
						-- check if the snak is of the correct snaktype and datatype
						local valid = qualsnak.snaktype == pvalqualid.types.snaktype
							and qualsnak.datatype == pvalqualid.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 preferred) or (tail ==  and not preferred) then
					-- if we don't have any other, use this one
					if preferred and head ==  then
						head = mainsnak.datavalue.value
					elseif not preferred and tail ==  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.claimsprop
	-- 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' == 'statement'
			and claim'rank' ~= 'deprecated'
		if valid then
			local mainsnak = claim.mainsnak or {}
			local preferred = claim'rank' == 'preferred'
			-- verify the item is what we expect
			local valid = mainsnak.snaktype == pvalprop.types.snaktype
				and mainsnak.datatype == pvalprop.types.datatype
				and mainsnak.datavalue.type == pvalprop.types.datavalue.type
			if valid then
				-- mw.log("getLanguageData claim is valid="..dump(claim))
				-- if this is the correct P-value, dive into it and get P218 (ISO 639-1)
				if mainsnak.property == '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 == 'P218' or 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 tail
end
local langqvalorder = {'P407','P364'}
local otherqvalorder = {'P582'}
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 ==  then
		--mw.log("getValuesFromWikidata no claims or no props")
		return {}
	end
	-- get the claims for this entity
	local statements = entity.claimsprops.prop
	-- to avoid deep tests
	if not statements then
		return {}
	end
	-- 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' == 'statement'
			and claim'rank' ~= 'deprecated'
		if valid then
			-- mw.log("getValuesFromWikidata valid claim="..dump(claim))
			local mainsnak = claim.mainsnak or {}
			local preferred = claim'rank' == 'preferred'
			-- get the content of the claim (the identifier)
			local langcode = props.langcode
			local checklangcode = nil
			if props.langcode and props.langcode ~=  then
				checklangcode = string.find(langcode, "(pP%d+)")
			end
			if checklangcode and checklangcode ~= "" then
				-- this is a P-value for language-code, so we'll check qualifiers for languagedata
				-- first get any qualifiers
				local qualifiers = claim.qualifiers or {}
				for _, qualid in ipairs( langqvalorder ) do
					-- if the claim has this qualifier
					if qualifiersqualid then
						-- it's here, let's check it out!
						local items = {}
						-- traverse all snaks in this qualifier
						for _, qualsnak in ipairs( qualifiersqualid ) do
							if qualsnak and pvalqualid then
								-- mw.log("qualsnak = " .. dump(qualsnak))
								-- check if the snak is of the correct snaktype and datatype
								local valid = qualsnak.snaktype == pvalqualid.types.snaktype
									and qualsnak.datatype == pvalqualid.types.datatype
								if valid then
									-- now get the actual data
									langcode = table.concat(getLanguageData('P305', 'Q'..qualsnak.datavalue.value'numeric-id'), )
								end
							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
			local stillvalid = true
			-- we should check a couple of other qualifiers as well
			-- first get any qualifiers
			local qualifiers = claim.qualifiers or {}
			for _, qualid in ipairs( otherqvalorder ) do
				-- if the claim has this qualifier
				if qualifiersqualid then
					-- it's here, let's check it out!
					local items = {}
					-- traverse all snaks in this qualifier
					for _, qualsnak in ipairs( qualifiersqualid ) do
						if qualsnak and pvalqualid then
							-- mw.log("qualsnak = " .. dump(qualsnak))
							-- check if the snak is of the correct snaktype and datatype
							local valid = qualsnak.snaktype == pvalqualid.types.snaktype
								and qualsnak.datatype == pvalqualid.types.datatype
							if not valid then
								-- sorry, this is not correct
								mw.log("qualsnak = INCORRECT")
								stillvalid = false
							end
						end
					end
				end
				-- mw.log("langcode is now="..dump(langcode))
			end
			if stillvalid then
				if preferred then
					head#head+1 = { value=stringFormatter(mainsnak.datavalue) }
					if langcode and langcode ~=  then
						head#head'langcode' = langcode
					end
				else
					tail#tail+1 = { value=stringFormatter(mainsnak.datavalue) }
					if langcode and langcode ~=  then
						tail#tail'langcode' = langcode
					end
				end
			end
		end
	end
	-- mw.log("getValuesFromWikidata returning head="..dump(head).." tail="..dump(tail))
	return #head>0 and head or tail
end
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 output
end
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 = { value = sitelinks }
	end
	--mw.log("getSitelinksFromWikidata returning output="..dump(output))
	return output
end
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 output
end
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 ==  then
		-- bail out if no value is present
		return output
	end
	-- get the formatvalue from the property
	local verified_value = local_value
	if props.regex and props.regex ~=  then
		-- let's verify the id
		-- maybe we'll have to convert the regex to Lua-style
		local regex = regexConverter(props.regex)
		local test = string.match( local_value, '^'..regex..'$' )
		if test then
			-- it matched, this is correct
			verified_value = local_value
		else
			verified_value = nil
		end
		
	end
	if not verified_value then
		return output
	end
	local wikidata_property = string.find(props.prop, "(pP%d+)")
	local wikidata_values = {}
	if wikidata_property then
		-- get any wikidata values to see if they are equal to local values
		wikidata_values = getValuesFromWikidata(props)
	end
	if wikidata_property or (props.url and props.url ~= ) or (props.url_f) then
		output#output+1 = {}
		output#output.langcode = string.find(props.langcode, "(pP%d+)") and "" or props.langcode
		--mw.log("findMainLinksLocal - props="..dump(props).." langcode="..output#output.langcode)
		output#output.category = {}
		local url = 
		if props.track and wikidata_property and wikidata_values and #wikidata_values then
			local local_value_in_wikidata = false
			for _,value in ipairs( wikidata_values ) do
				if value.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 output
end
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 ==  then
		-- bail out if no value is present
		return output
	end
	-- get the formatvalue from the property
	local verified_value = local_value
	if props.regex and props.regex ~=  then
		-- let's verify the id
		-- maybe we'll have to convert the regex to Lua-style
		local regex = regexConverter(props.regex)
		local test = string.match( local_value, '^'..regex..'$' )
		if test then
			-- it matched, this is correct
			verified_value = local_value
		else
			verified_value = nil
		end
		
	end
	if not verified_value then
		return output
	end
	local wikidata_property = string.find(props.prop, "(SL.+)")
	local wikidata_values = {}
	if wikidata_property then
		-- get any wikidata values to see if they are equal to local values
		wikidata_values = getSitelinksFromWikidata(props)
	end
	if wikidata_property or (props.url and props.url ~= ) or (props.url_f) then
		output#output+1 = {}
		output#output.langcode = string.find(props.langcode, "(SL.+)") and "" or props.langcode
		--mw.log("findSiteLinksLocal - props="..dump(props).." langcode="..output#output.langcode .." wikidata_values="..dump(wikidata_values))
		output#output.category = {}
		local url = 
		if props.track and wikidata_property and wikidata_values and #wikidata_values then
			local local_value_in_wikidata = false
			for _,value in ipairs( wikidata_values ) do
				if value.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 output
end
local function addLinkback(str, property)
	local id = mw.wikibase.getEntityObject()
	if not id then
		return str
	end
	if type(id) == 'table' then
		id = id.id
	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 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 args1 == nil then
		local pFrame = frame:getParent();
		args = pFrame.args;
		for k,v in pairs( frame.args ) do
			argsk = v;
		end
	end
	if argsargument then
		return argsargument
	end
	return nil
end
local function removeEntry(conf_claims, identifier, property)
	for i, props in ipairs(conf_claims) do
		if propsidentifier == property then
			table.remove(conf_claims, i)
		end
	end
	return conf_claims
end
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 == claim.prop or 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_claimsi'track' = false
				elseif track_prop == claim.prop or track_prop == conf:a('mod-filter-all') then
					conf_claimsi'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_prop = {}
				no_req_prop = true
			end
			local remove_prop = string.match(i, "^-(.*)")
			if remove_prop then
				-- if a property starts with "-", then we'll simply remove that 
				-- property from the conf_claims
				conf_claims = removeEntry(conf_claims, 'prop', remove_prop)
			elseif not no_req_prop then -- only if we are allowing properties to be filtered 
				req_propi = 1
				-- cheat to make #req_prop indicate populated table
				req_prop1 = 1
			end
		end
	end
	local requested_langs = getArgument(frame, conf:a('arg-languages'))
	--mw.log("requested_langs="..dump(requested_langs))
	-- assume all languages are allowed
	local req_lang = {}
	local no_req_lang = false  -- we'll allow languages to be filtered for now
	if requested_langs and requested_langs ~=  then
		-- the languages should be written as langcodes as used in the conf_claims
		for i in string.gmatch(requested_langs,"(^ ,;:+)") do
			-- get the requested languages and be able to access them
			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_langi = 1
				-- cheat to make #req_lang indicate populated table
				req_lang1 = 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 ==  then
		links_shown = limits'links-shown' and limits'links-shown' or 10
	else
		links_shown = tonumber(links_shown)
	end
	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))
	for _, props 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_propprops.prop) and (#req_lang==0 or req_langprops.langcode or string.find(props.langcode, "(pP%d+)")) then
			--mw.log("checking claim="..dump(props))
			local links = {}
			local checkedonwikidata = false
			-- get the any local overriding value from the call
			local wikivalue = getArgument(frame, props.prop)
			--mw.log("wikivalue="..dump(wikivalue))
			if (not wikivalue or wikivalue == "") 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 == nil then
					-- a nil-value indicated no wikidata-link
					haswikidatalink = false
					links = {}
				else
					checkedonwikidata = true
				end
			elseif (not wikivalue or wikivalue == "") 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 == nil then
					-- a nil-value indicated no wikidata-link
					haswikidatalink = false
					links = {}
				else
					checkedonwikidata = true
				end
			elseif (wikivalue and wikivalue ~= "") and string.find(props.prop, "(SL%l+)") then
				-- this is a sitelink-type (SLspecieswiki)
				links = findSiteLinksLocal(props, pagetitle, short_links, wikivalue)
			elseif wikivalue and wikivalue ~=  then
				-- the property is of another annotation, and therefore a local construct
				links = findMainLinksLocal(props, 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 or req_langv.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
							output#output+1 = v.text
						else
							output#output = output#output .. cmodule:getMessage(contLang:getCode(),'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>=1 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, props.prop) or v.text)	-- if the link comes from wikidata, also output a linkback.
					elseif not short_links and showinline and v.text and v.text ~=  then
						-- only if short links were not requested
						output#output+1 = v.text
					end
					if props.track and v.category and #v.category 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
						links_shown = links_shown - 1
					else
						break
					end
				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(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 outtext
end
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