Документация Документация

Сборщик данных из Викиданных — ссылок на энциклопедии и братские википроекты по теме исходной страницы. Используется в Модуль:Источники по теме.

local RU = 'ru'
local RUWIKISOURCE = 'ruwikisource'

local wd = require("Module:WD")
local util = require("Module:Util")
local encyclopediasData = util.get_json("MediaWiki:Encyclopedias settings.json") -- настройки заголовков энциклопедий и их id в ВД
local encyclopedias_sites_cfg = util.get_json("MediaWiki:Encyclopedias sites.json") -- настройки энциклопедий на внеших сайтах
local p = {}

local page


function p.getQualifierValues(statement, qualifierName)
	local result = {}
	if statement then
		local q = statement.qualifiers
		if q and q[qualifierName] ~= nil then
			for _, qualifier in pairs(q[qualifierName]) do
				local d = qualifier.datavalue
				if d and d.type ~= nil and d.value ~= nil then
					if d.type == "wikibase-entityid" then
						result[#result + 1] = d.value["id"]
					end
				end
			end
		end
	end
	return result
end


-- deprecated, не используется
local function check_backlink(entity)
	local has_backlink
	if entity and entity.claims and entity.claims.P1343 then
		for _, refs in pairs(entity.claims.P1343) do
			if refs.qualifiers and refs.qualifiers.P805 then
				for _, ref in pairs(refs.qualifiers.P805) do
					if ref.datavalue and ref.datavalue.value and ref.datavalue.value.id then
						local ref_id = ref.datavalue.value.id
						if ref_id == page.entity.id then has_backlink = true break end
					end
				end
				if has_backlink then break end
			end
		end
	end
	return has_backlink
end


function p.get_sitelinks_from_entity(entity, result)
	-- populate from entity interwiki
	local result = result or {}
	local sitelinks = entity.sitelinks
	if sitelinks ~= nil then
		for sitecode, sitelink in pairs(sitelinks) do
			-- mw.log("  "..sitecode.." = "..sitelink.title)
			if result[sitecode] == nil then
				result[sitecode] = sitelink.title
			end
		end
	end

	if result['commons'] == nil then
		local commonsCategories = wd.getClaimValues_ranked(entity, 'P373')
		for _, commonsCategory in pairs(commonsCategories) do
			result['commons'] = 'Category:' .. commonsCategory
			break
		end
		-- если commons нет в интервиках и в P373, то использовать изображение из P18
		if result['commons'] == nil then
			local commonsCategories = wd.getClaimValues_ranked(entity, 'P18')
			for _, commonsCategory in pairs(commonsCategories) do
				result['commons'] = 'File:' .. commonsCategory
				break
			end
		end
	end
	return result
end


function p.get_ext_enc_sites(entity, result)
	local result = page.external_sites
	for _, site in pairs(encyclopedias_sites_cfg) do
		local enc_name, property_id, url_pattern = site.argument, site.property, site.url_pattern
		local id
		local ids = wd.getClaimValues_ranked(entity, property_id); if ids then id = ids[1] end
		-- local ids_sts = entity:getBestStatements(site.property); if ids_sts then id = wd.get_statement_value(ids_sts, wd.DT_EXT_ID, wd.VT_STRING)[1] end
		if id and not result[enc_name] then
			site.wikilink = util.make_ext_link(url_pattern:gsub("$1", id), enc_name)
			site.id = id
			result[enc_name] = site
		end
	end
	return result
end


-- загрузка ссылок из элементов в списке указанного свойства
local function get_links_from_items_of_property(entity, property_id)
	local targets = wd.getClaimValues_ranked(entity, property_id)
	-- mw.logObject(targets, "targets")
	if #targets > 0 then
		for key, entity_id in pairs(targets) do
			if property_id == 'P921' then --  P921 "основная тема произведения"
				page.has_topic_property = true
			end
			
			local entity_target = wd.get_entity_by_id(entity_id)
			if entity_target then
				p.populate_data(entity_target)
			end
		end
	end
end

-- загрузка данных из элемента страницы
local is_topic_entity_reached = false
local is_edition_as_topic_reached = false
function p.populate_data(entity)
	if not entity or not entity.claims then return end
	
	if wd.has_valid_item_value(entity, "P31", 'Q3331189') and not is_topic_entity_reached and not is_edition_as_topic_reached then -- Q3331189 "версия или издание"
		-- Переменные `is_topic_entity_reached` и `is_edition_as_topic_reached` ограничивают глубину подгрузки элементов.
		-- Проверка: 
		-- На странице [[Ветхий Завет (Макарий)]] [[d:Q4350033]] должна показываться ссылка на [[w:Перевод Библии архимандрита Макария]]. При этом элемент является Q3331189 версией Библии.
		-- На странице [[ЕЭБЕ/Пешитта]] [[d:Q24922704]] должна показываться ссылка на [[w:Пешитта]]. При этом темой указана "Пешита", которая является Q3331189 версией Библии.
		-- Не должно быть перехода на глубокий элемент [[w:Библия]].
		local sitelinks_ = p.get_sitelinks_from_entity(entity)
		sitelinks_['ruwikisource'] = nil
		if next( sitelinks_ ) then
			is_edition_as_topic_reached = true
		end
	end
	
	if wd.has_valid_item_value(entity, "P31", 'Q3331189') and not is_topic_entity_reached and not is_edition_as_topic_reached then -- Q3331189 "версия или издание"
		-- загрузка из элементов в списке P629 "является изданием или переводом"
		get_links_from_items_of_property(entity, "P629") 
		
	elseif wd.is_encyclopedic_article(entity) and entity.claims['P921'] and not is_topic_entity_reached then
		-- для энциклопедических, словарных статей и перенаправлений (P31 in [Q10389811, Q13433827, Q1580166, Q1302249])
		-- загрузка из элементов в списке P921 "основная тема произведения"
		if wd.has_valid_item_value(entity, "P31", 'Q1302249') then -- Q1302249 "перекрёстная ссылка"
			page.has_xref_property = true
		end
		is_topic_entity_reached = true
		get_links_from_items_of_property(entity, "P921") 
		
	else
		-- загрузка из текущего элемента
		local label = entity.labels and ((entity.labels.ru and entity.labels.ru.value) or (entity.labels.en and entity.labels.en.value)) or entity.id
		table.insert( page.sitelinks, { label = label, id = entity.id, data = p.get_sitelinks_from_entity(entity) } )
		page.sitelinks[#page.sitelinks]['data']['wikidata'] = entity.id
		
		page.encyclopedias_ids = p.get_ids_of_described_encs(entity)
		
		p.get_ext_enc_sites(entity)
	end

	return page
end


-- проход P1343 - списка «описывается в источниках»
function p.get_ids_of_described_encs(entity)
	local result = page.encyclopedias_ids
	-- local describedByClaim = wd.getClaimValues_ranked( entity, 'P1343' )
	local describedByClaim = entity:getBestStatements('P1343') -- P1343 «описывается в источниках»
    for _, statement in pairs( describedByClaim ) do
    	local snak = statement["mainsnak"]
	    if snak.datavalue.type == wd.VT_ENTITY_ID  then
	        local dictId = snak.datavalue.value["id"]
    		local qualifiers = p.getQualifierValues(statement, 'P805') -- P805 тема утверждения
			if qualifiers == nil or #qualifiers == 0 then
				qualifiers = p.getQualifierValues(statement, 'P248') -- P248 утверждается в (deprecated)
			end
			for _, articleId in pairs(qualifiers) do
				for _, enc in pairs(encyclopediasData) do
					local dictionaryShortTitle = enc.argument
					if enc.id == dictId then
						-- todo: enc.id == dictId не работает для элементов отдельных томов, напр. [[d:Q23892934]], ибо их нет в encyclopediasData. Надо см. наличие P361 "часть от" и сверять с ним, если эти лишние запросы вообще нужны.
						local dictLinks = result[dictionaryShortTitle]
						if dictLinks == nil then
							dictLinks = {}
							result[dictionaryShortTitle] = dictLinks
						end
						dictLinks[articleId] = articleId
						break
					end
				end
				if page.entity and articleId == page.entity.id then page.has_backlink = true end  -- проверка обратной ссылки, используется для категоризации
			end
		end
    end
	return result
end


--[[
is_match_pagenames("ЭСБЕ/Пупкин, Иван Васильевич/ДО", "ЭСБЕ/$1/ДО") → "Пупкин, Иван Васильевич"
is_match_pagenames("ЭСБЕ/Пупкин, Иван Васильевич",    "ЭСБЕ/$1/ДО") → nil
]]
function p.is_match_pagenames(s_sitelink, s_pattern)
	local s_regexp = string.gsub(s_pattern, "%(", "%%(")
	s_regexp = string.gsub(s_regexp, "%)", "%%)")
	s_regexp = "^" .. string.gsub(s_regexp, "$1", "(.+)") .. "$"
	--	mw.log("regexp: "..s_regexp)
	return string.match(s_sitelink, s_regexp)
end

--[[
{{#invoke:Другие источники|test_is_match_pagenames|ЭСБЕ/Пупкин, Иван Васильевич/ДО|ЭСБЕ/$1/ДО}} → "Пупкин, Иван Васильевич"
{{#invoke:Другие источники|test_is_match_pagenames|МЭСБЕ/Аконит|МЭСБЕ/$1}} → "Аконит"
{{#invoke:Другие источники|test_is_match_pagenames|ЭСБЕ/Аконит|МЭСБЕ/$1}} → nil
]]
local function test_is_match_pagenames(frame)
	local s_sitelink = tostring(frame.args[1])
	local s_pattern = tostring(frame.args[2])
	local s_title = p.is_match_pagenames(s_sitelink, s_pattern)
	if s_title == nil then
		return "nil"
	else
		return '"' .. s_title .. '"'
	end
end


local function getDefaultTitle()
	for _, enc in pairs(encyclopediasData) do
		if enc.project == RUWIKISOURCE and enc.titleDO then
			local DO = (enc.default == 'DO')
			local titleVT, titleDO = enc.titleVT, enc.titleDO
			local nameVT, nameDO = p.is_match_pagenames(page.title, titleVT), p.is_match_pagenames(page.title, titleDO)
			if nameDO then
				if DO then return titleDO:gsub('$1', nameDO) else return titleVT:gsub('$1', nameDO) end
			elseif nameVT then
				if DO then return titleDO:gsub('$1', nameVT) else return titleVT:gsub('$1', nameVT) end
			end
		end
	end
	return page.title
end


function p.getLink(enc, entityId, isPRS)
	if enc.project ~= RUWIKISOURCE then
		local entity = wd.get_entity_by_id(entityId)
		if not entity then
			mw.log("Невозможно загрузить " .. tostring(entityId))
			return nil
		end
		if (entity.sitelinks == nil) or (entity.sitelinks[enc.project] == nil) then
			mw.log("В " .. tostring(entityId) .. " нет ссылки на " .. enc.project .. " для " .. enc.argument)
			return nil
		end
		return ':' .. enc.projectCode .. entity.sitelinks[enc.project].title
	end
	local s_sitelink = mw.wikibase.sitelink(entityId)
	if s_sitelink == nil then
		return nil
	end
	local s_primary_pattern = nil
	local s_secondary_pattern = nil
	if isPRS then
		s_primary_pattern = enc.titleDO
		s_secondary_pattern = enc.titleVT
	else
		s_primary_pattern = enc.titleVT
		s_secondary_pattern = enc.titleDO
	end
	-- mw.log(isPRS, s_primary_pattern, s_secondary_pattern, s_sitelink)
	if p.is_match_pagenames(s_sitelink, s_primary_pattern) ~= nil then -- ссылка из Викиданных соответствует признаку isPRS
		return s_sitelink -- т.к. ссылка  получена из Викиданных, поверять её существование не надо, просто её возвращаем
	end
	local s_article_title
	if enc.titleDO then
		s_article_title = p.is_match_pagenames(s_sitelink, s_secondary_pattern)
	end
	if not s_article_title then
		-- ссылка не соответствует ни titleDO, ни titleVT
		mw.log("ссылка на " .. enc.title .. " нарушает правила именования: " .. s_sitelink)
		return s_sitelink
	end
	local s_link
	if enc.id == "Q1970746" then -- для ТСД
		s_link = "ТСД/" .. s_article_title
		if isPRS then
			s_link = s_link .. "/ДО"
		end
	else
		s_link = string.gsub(s_primary_pattern, "$1", s_article_title)
	end
	if mw.title.new(s_link, 0).exists then
		return s_link
	else
		return s_sitelink
	end
end


-- загружает элемент текущей страницы
function p.get_page_entity()
	entity = mw.wikibase.getEntity()
	if not entity then
		local default_title = getDefaultTitle()
			entity = mw.wikibase.getEntity(mw.wikibase.getEntityIdForTitle(default_title))
	end
	return entity
end

-- загружает элемент темы если даны параметры шаблона ВИКИДАННЫЕ или ВИКИПЕДИЯ
function p.get_page_entity_topic_by_template_arg(args)
	local topic_id = is(args['ВИКИДАННЫЕ'])
	local wp_pagename = is(args['ВИКИПЕДИЯ'])
	if topic_id or wp_pagename then
		if not topic_id then
			local lang, title = string.match(wp_pagename, '^:?([a-z]+):(.+)') -- ссылка формата ':код_языка:Название' в параметрах
			if not lang then
				lang, title = RU, wp_pagename
			end
			topic_id = mw.wikibase.getEntityIdForTitle(title, lang .. 'wiki')
		end
		return wd.get_entity_by_id(topic_id)
	end
end


function p.get_links(_page)
	page = _page
	page.entity = p.get_page_entity()
	page.entity_topic_by_template_arg = p.get_page_entity_topic_by_template_arg(page.args)  -- элемент темы, указанный в параметрах ВИКИПЕДИЯ/ВИКИДАННЫЕ или в элементе Викиданных 
	page.has_encyclopedic_article_status_in_wd = wd.is_encyclopedic_article(page.entity)
	
	local entity = page.entity_topic_by_template_arg or page.entity  -- загружать ссылки из элемента темы если задан, иначе - из элемента страницы
	p.populate_data(entity)
	
	-- mw.logObject(page, "page")
	return page
end


-- проверка переменной, возврат её или nil если пустая
function is(var) if (var == '' or var == nil) then return nil else return var end end

return p