Модуль:Другие источники

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

Подгружает данные из Викиданных и интерпретирует их, показывая в шапках статей. Используется в Модуль:Отексте, Шаблон:Отексте, Модуль:Обавторе, Шаблон:Обавторе.

local currentProject = 'ruwikisource'
local wd = require("Module:WD")
local is_author_page
local encyclopediasData = mw.text.jsonDecode(
	mw.title.new( "MediaWiki:Encyclopedias settings.json" ):getContent()
)  -- настройки заголовков энциклопедий и их id в ВД

local function getClaimValues( entity, propertyId )
    local result = {}
    local claim = entity.claims[ propertyId ]
    if ( claim == nil ) then
        return result
    end

    for _, statement in pairs( claim ) do
        local mainsnak = statement.mainsnak
        if ( mainsnak ~= nil and mainsnak.datavalue ~= nil ) then
            if ( mainsnak.datavalue.type == "string" ) then
                result[#result+1] = mainsnak.datavalue.value
            elseif ( mainsnak.datavalue.type == "wikibase-entityid" ) then
                result[#result+1] = 'Q' .. mainsnak.datavalue.value["numeric-id"]
            else 
                result[#result+1] = mainsnak.datavalue.value
            end
        end
    end

    return result
end

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

local p = {}
local is_topic_property = false  -- для установки категории при наличии P921

function p.populateEncyclopediasDataByEntityId( entityId, result )

    if ( result == nil ) then
        result = {}
    end

    if ( entityId == nil ) then
        return result
    end

    local entity = mw.wikibase.getEntity( entityId )
    if ( entity == nil ) then
        return result
    end

    return p.populateEncyclopediasDataByEntity( entity, result )
end

function p.populateEncyclopediasDataByEntity( entity, result )

    if ( result == nil ) then
        result = {}
    end

    if ( entity == nil or entity.claims == nil ) then
        return result
    end

    -- first check current item references
    local describedByClaim = entity.claims[ 'P1343' ]
    if ( describedByClaim ~= nil ) then
        for _, statement in pairs( describedByClaim ) do
            if ( statement.mainsnak ~= nil
            		and statement.rank ~= 'deprecated'
                    and statement.mainsnak.datavalue.type == "wikibase-entityid"
                    and statement.mainsnak.datavalue.value["numeric-id"] ~= nil ) then
                local dictId = 'Q' .. statement.mainsnak.datavalue.value["numeric-id"]

                for _, enc in pairs( encyclopediasData ) do
                    local dictionaryShortTitle = enc.argument
                    local dictinaryEntityId = enc.id

                    if ( dictinaryEntityId == dictId ) then
                    	local qualifiers = getQualifierValues(statement, 'P805')  -- P805 тема утверждения
                    	if (qualifiers == nil or #qualifiers == 0) then
                    		qualifiers = getQualifierValues(statement, 'P248')    -- P248 утверждается в (deprecated)
                    	end
                        for _, qualifierValue in pairs( qualifiers ) do
                            local dictLinks = result[ dictionaryShortTitle ]
                            if ( dictLinks == nil ) then
                                dictLinks = {}
                                result[ dictionaryShortTitle ] = dictLinks
                            end
                            dictLinks[ qualifierValue ] = qualifierValue
                        end
                    end
                end
            end
        end
    end

    -- check if entity have main topic item
		-- для энциклопедических и словарных статей (P31 in [Q10389811, Q13433827, Q1580166]) просматриваем P921 ("основная тема произведения")
    if ( entity ) then
    	if wd.has_valid_item_value (entity, "P31", 10389811) 
    	or wd.has_valid_item_value (entity, "P31", 13433827) 
    	or wd.has_valid_item_value (entity, "P31", 1580166) then
            local parentEntityIds = getClaimValues( entity, 'P921' )
            --mw.logObject(parentEntityIds,"parentEntityIds")
            for _, parentEntityId in pairs( parentEntityIds ) do
            	is_topic_property = true
                p.populateEncyclopediasDataByEntityId( parentEntityId, result )
            end
        end
    end

    -- check if entity have edition of item
    if ( entity ) then
        local parentEntityIds = getClaimValues( entity, 'P629' )
        for _, parentEntityId in pairs( parentEntityIds ) do
            p.populateEncyclopediasDataByEntityId( parentEntityId, result )
        end
    end
    return result
end
--[[
parseLink("ЭСБЕ/Пупкин, Иван Васильевич/ДО", "ЭСБЕ/$1/ДО") → "Пупкин, Иван Васильевич"
parseLink("ЭСБЕ/Пупкин, Иван Васильевич",    "ЭСБЕ/$1/ДО") → nil
]]
local function parseLink(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)
--	for s_catch in string.match(s_sitelink, s_regexp) do
--		return s_catch
--	end
--	return nil
end

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

local function getLink( enc, entityId, isPRS )
	if enc.project ~= currentProject then
	    local entity = mw.wikibase.getEntity( 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 parseLink(s_sitelink, s_primary_pattern) ~= nil then  -- ссылка из Викиданных соответствует признаку isPRS
		return s_sitelink -- т.к. ссылка  получена из Викиданных, поверять её существование не надо, просто её возвращаем
	end
	-- Т.к. ссылка из Викиданных не соответсвует признаку isPRS,
	-- попробуем возвратить "правильную" ссылку (если она существует, конечно)
	local s_article_title
	if enc.titleDO then 
		s_article_title = parseLink(s_sitelink, s_secondary_pattern)
	end
	if s_article_title == nil then
		-- ссылка не соответсвует ни titleDO, ни titleVT
		-- это какая-то ошибка, выводим сообщение на консоль и возвращаем её
		mw.log("ссылка на "..enc.title.." нарушает правила именования: "..s_sitelink)
        return s_sitelink
	end
	local s_link
	if enc.id == "Q1970746" then -- workaround для ТСД
		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

local function isNotBlank( str )
    return str ~= nil and mw.ustring.len( str ) > 0
end

local function getPageTitleFromArgument( enc, title, isPRS )
	if ( enc.project ~= currentProject ) then
		return ':' .. enc.projectCode .. enc.prefix .. title .. enc.suffix
	end
	local linkVT = nil
	local linkDO = nil
	if enc.id == "Q1970746" then -- workaround для ручного параметра ТСД=
      linkVT = "ТСД/" .. title
      linkDO = linkVT .. "/ДО"
    else  
      linkVT = string.gsub(enc.titleVT, "$1", title)
      if enc.titleDO then 
    	linkDO = string.gsub(enc.titleDO, "$1", title)
      end
	end	
    if ( isPRS ) then
        if ( mw.title.new( linkDO , 0 ).exists ) then
            return linkDO
        elseif ( mw.title.new( linkVT , 0 ).exists ) then
            return linkVT
        else
        	return linkDO
        end
    else
    	if ( mw.title.new( linkVT , 0 ).exists ) then
            return linkVT
    	elseif ( mw.title.new( linkDO , 0 ).exists ) then
        	return linkDO
        else
            return linkVT
        end
    end
end

local function getDefaultTitle( title )
	for _, enc in pairs( encyclopediasData ) do
		if (enc.project == currentProject) and enc.titleDO then
			local DO = (enc.default == 'DO')
			local titleVT, titleDO = enc.titleVT, enc.titleDO
			local nameVT, nameDO = parseLink( title, titleVT ), parseLink( 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 title
end

local function fetchEncyclopediasLinks( frame )
    local data = { wlinks={}, categories={}, categories_raw={}, isPRS }
    function data:add(s) table.insert(self.wlinks, s) end
    function data:add_category(s) 
    	table.insert(self.categories, '[[Категория:'..s..']]')
    	table.insert(self.categories_raw, s)
    end
    data.isPRS = frame.isPRS
    local args = frame:getParent().args
    local page_title = mw.title.getCurrentTitle().text
    local wd_entity = mw.wikibase.getEntity()
    if not wd_entity then
    	local default_title = getDefaultTitle( page_title )
    	wd_entity = mw.wikibase.getEntity(mw.wikibase.getEntityIdForTitle(default_title))
    end
    local encyclopediasIds = p.populateEncyclopediasDataByEntity( wd_entity, nil )
    -- проверка обратной ссылки («описывается в источниках»)
    if wd_entity 
	and (wd.has_valid_item_value (wd_entity, "P31", 10389811) 
	or wd.has_valid_item_value (wd_entity, "P31", 13433827) 
	or wd.has_valid_item_value (wd_entity, "P31", 1580166))
	and wd_entity.claims.P921 then
    	--mw.logObject(wd_entity, "wd_entity")
   		--local has_backlink
   		for _, sub in pairs(wd_entity.claims.P921) do
   			local dv = sub.mainsnak.datavalue
   			if type(dv) == "table" and type(dv.value) == "table" and dv.value.id then
		   		local has_backlink
   				local sub_entity = mw.wikibase.getEntity (dv.value.id)
   				if sub_entity and sub_entity.claims and sub_entity.claims.P1343 then
   					--mw.logObject(sub_entity.claims.P1343, "sub_entity.claims.P1343")
   					for _, refs in pairs(sub_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 == wd_entity.id then has_backlink = true break end
   								end
   							end
     						if has_backlink then break end
 						end
   					end
   				end
   				if not has_backlink then data:add_category('Викиданные:Страницы с указанным элементом темы без обратной ссылки') end
   			end
   		end
    	--[[for i, v in pairs(encyclopediasIds) do 
    		if v[wd_entity.id] then has_backlink = true break end
    	end
    	if not has_backlink then data:add_category('Викиданные:Страницы с указанным элементом темы без обратной ссылки') end]]
    end
    
    for _, enc in pairs( encyclopediasData ) do
        local dictArgName = enc.argument
        local id = enc.id
        if not ((enc.project == currentProject) and (parseLink(page_title, enc.titleVT) ~= nil or (enc.titleDO and parseLink(page_title, enc.titleDO) ~= nil))) then
            local s_param_value = args[dictArgName]
            local ids = encyclopediasIds[dictArgName]
            local manual_link, wd_link
            local mlinks, wdlinks = {}, {}
            if isNotBlank(s_param_value) then
            	-- local keyword to suppress WD output
            	if s_param_value then
            		for elem in mw.text.gsplit( s_param_value, '%s*~%s*' ) do -- несколько значений в одном параметре
            			manual_link = getPageTitleFromArgument( enc, elem,  data.isPRS )
            			table.insert(mlinks, manual_link)
            		end
                	data:add_category('Ручная ссылка:' .. dictArgName)
            	end
            end
            if ids ~= nil then
                for _, id in pairs(ids) do
                	wd_link = getLink(enc, id, data.isPRS)
                	if wd_link == nil then
                		-- элемент без страницы на искомом языке, пропускаем
                	else
                		table.insert(wdlinks, wd_link)
						data:add_category('Ссылка из Викиданных:' .. dictArgName)
                	end
                end
            end
            
            -- добавление ссылок
        	for _, manual_link in pairs(mlinks) do 
        		data:add( '[[' .. manual_link .. '|' .. enc.title .. ']]')
        		if not enc.projectCode and not mw.title.new( manual_link ).exists then
        			data:add_category('Ручная ссылка на несуществующую словарную статью:' .. dictArgName)
        		end
        	end
        	-- добавлять ссылки из ВД которые не дублируют ручные
        	for _, wd_link in pairs(wdlinks) do 
        		if #mlinks>0 then
	        		for _, manual_link in pairs(mlinks) do 
	        			if wd_link ~= manual_link then
	        				local link
	        				if mw.ustring.match( manual_link, '^(.+)#') then 
	        					data:add_category('Якорь в ручной ссылке')
	        				else 
								data:add( '[[' .. wd_link .. '|' .. enc.title .. ']]')
	        				end
	        			end
	        		end
	        	else 
	        		data:add( '[[' .. wd_link .. '|' .. enc.title .. ']]')
        		end
        	end
            
            for _, manual_link in pairs(mlinks) do 
            	for _, wd_link in pairs(wdlinks) do 
            		if wd_link == manual_link then
            			data:add_category('Ручная ссылка совпадает со ссылкой из Викиданных:' .. dictArgName)
            		end
            	end
            end
        end
    end
    if not is_author_page then
	    if wd_entity then 
	    	data:add_category('Викиданные:Страницы с элементами|'..page_title) 
	    	if is_topic_property then data:add_category('Викиданные:Страницы с указанным элементом темы|'..page_title) 
	    	else data:add_category('Викиданные:Страницы с элементами без указания элемента темы|'..page_title) end
	    end
    end
    -- mw.logObject(data, "data")
    return data
end
function p.fetchEncyclopediasLinks( frame, _is_author_page ) 
	is_author_page = _is_author_page
	return fetchEncyclopediasLinks(frame ) 
end

-- оформление ссылок для шапки {{отексте}}, вызывается из Модуль:Отексте
function p.renderOtherSources_aboutText( frame )
    local data = fetchEncyclopediasLinks(frame)
    local result = ''
    if #data.wlinks >0 then
		local desc; if frame.isPRS then desc = 'Другіе источники' else desc = 'Другие источники' end
		result = " •  '''"..desc.."''': " .. table.concat(data.wlinks, ' : ')
	end
    -- mw.logObject(result, "result")
    return result .. table.concat(data.categories)
end

return p