Модуль:Навигация-мини

Материал из Викитеки — свободной библиотеки
Перейти к навигации Перейти к поиску
Документация Документация

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

local p = {}
local wd = require("Module:WD")
local is_author_page

local RU = 'ru'
local WIKISOURCE = 'wikisource'
local RUWIKISOURCE = 'ruwikisource'
local argSearch = 'ПОИСК'

-- Приоритет выбора ссылок на интервики. Расставлены примерно по величине размера проекта и числу админов, с приоритетом для европ., и en-de/pl-uk языков. '*' — любая другая другая интервика
local preferredLanguages = { RU, 'en', 'de', 'pl', 'uk', 'fr', 'it', 'es', 'cs', 'pt', 'da', 'zh', 'he', 'ar', '*' }

-- Настройки наименований википроектов
local projects = mw.text.jsonDecode( mw.title.new( 'MediaWiki:Wikiprojects settings.json' ):getContent())

local parentProperties = {
--	'P921', -- main topic link
	-- 'P910', -- main category. Отключено. В ссылках на интервики проектов (ВП, ВТ итп) выводит интервики категорий. 
	           -- Напр., [[ЕЭБЕ/Ассирия]] → item → item main topic → item main category → get interwiki category
	-- 'P629', -- edition or translation of
	-- 'P361', -- part of
}

-- local currentTitleFull = mw.title.getCurrentTitle().fullText

--[[
Функция возвращает массив значений свойства propertyId из элемента entity
Возвращаются значения "preferred", если они есть, иначе значения "normal".
]]
local function getClaimValues( entity, propertyId )
    local result = {}
    local claim = nil
    if entity ~= nil and entity.claims ~= nil then
		claim = entity.claims[ propertyId ]
    end
    if claim ~= nil then
		local b_prefered_mode = false
		for key, value in pairs(claim) do
			local snak = nil
	        if value.rank == "preferred" then	-- предпочтительный ранг
				if not b_prefered_mode then
	            	result = {}
	            	b_prefered_mode = true
				end
				snak = value.mainsnak
	        elseif value.rank == "normal" and not b_prefered_mode then	-- обычный ранг
				snak = value.mainsnak
	        end
	        if snak ~= nil and snak.snaktype == "value" then
				if snak.datavalue.type == "wikibase-entityid" then
	            	result[#result+1] = "Q" .. snak.datavalue.value["numeric-id"]
				else
	            	result[#result+1] = snak.datavalue.value
				end
	        end
		end
    end
    return result
end

--[[
-- функция для тестирования
-- {{#invoke:Навигация-мини|test_getClaimValues|Q20646238|P921}}
function p.test_getClaimValues (frame)
    local s_item = frame.args[1]
    local s_property = frame.args[2]
    local entity = mw.wikibase.getEntity(s_item)
    local data = getClaimValues(entity, s_property)
    local s_result = ""
    for key, value in pairs(data) do
      s_result = s_result .. "[" .. key .. "]: " .. tostring(value) .. "<br/>"
    end
    return s_result
end
]]
--[[
function p.populateInterwikiByEntityId( 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.populateInterwikiByEntity( entity, result )
end
]]
function p.populateInterwikiByEntity(entity, result)
	-- populate from entity interwiki
	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
    -- check if entity have main topic item
    -- ToDo: блок не используется, 'P921' (main topic link) просматривается в populate_interwikis_from_item()
    for _, propertyCode in pairs( parentProperties ) do
	    local parentEntityIds = getClaimValues( entity, propertyCode )
	    for _, parentEntityId in pairs( parentEntityIds ) do
			local entity2 = mw.wikibase.getEntity(parentEntityId)
--	        if propertyCode == 'P921' and result['wikidata'] == nil then
--	            result['wikidata'] = entity2.id  -- entity itself is a wikidata link
--	        end
	        p.populateInterwikiByEntity(entity2, result)
	    end
	end
	if result['commons'] == nil then
	    local commonsCategories = getClaimValues( entity, 'P373' )
	    for _, commonsCategory in pairs( commonsCategories ) do
	    	 result[ 'commons' ] = 'Category:' .. commonsCategory
	    	 break
	    end
	    -- если commons нет в интервиках и в P373, то использовать изображение из P18
	    if result[ 'commons' ] == nil then
			local commonsCategories = getClaimValues( entity, 'P18' )
		    for _, commonsCategory in pairs( commonsCategories ) do
		    	 result[ 'commons' ] = 'File:' .. commonsCategory
		    	 break
		    end
		end
	end
    return result
end

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", "(.+)").."$";
	return string.match(s_sitelink, s_regexp);
end

local function getDefaultTitle()
	local otherSources = mw.text.jsonDecode(mw.title.new( "MediaWiki:Encyclopedias settings.json" ):getContent())
	local title = mw.title.getCurrentTitle().text;
	for _, sourceDescription in pairs( otherSources ) do
		if (sourceDescription.project == 'ruwikisource') and sourceDescription.titleDO then
			local DO = (sourceDescription.default == 'DO')
			local titleVT, titleDO = sourceDescription.titleVT, sourceDescription.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

-- загрузка списка интервик из элемента текущей страницы
function populate_interwikis_from_item( manual_topic_id )
	if manual_topic_id == '' then manual_topic_id = nil end
	local entity = mw.wikibase.getEntity()
	if not entity then
    	local default_title = getDefaultTitle()
    	entity = mw.wikibase.getEntity(mw.wikibase.getEntityIdForTitle(default_title))
    end
	local wikidataInterwiki = {}
	if entity ~= nil then
		-- для энциклопедических и словарных статей (P31 in [Q10389811, Q13433827, Q1580166]) просматриваем P921 ("основная тема произведения")
    	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 topics = getClaimValues(entity, "P921")
    		for key, item_id in pairs(topics) do
    			-- mw.log("просматриваем: "..item_id)
    			if wikidataInterwiki['wikidata'] == nil then
   					wikidataInterwiki['wikidata'] = item_id
   				end
   				local topic_item = mw.wikibase.getEntity(item_id)
	    		wikidataInterwiki = p.populateInterwikiByEntity(topic_item, wikidataInterwiki)
			end
			if manual_topic_id and #topics == 0 then 
		   		local topic_item = mw.wikibase.getEntity(manual_topic_id)
				wikidataInterwiki = p.populateInterwikiByEntity(topic_item, wikidataInterwiki)
			end
		else
   			-- mw.log("это не энциклопедическая статья")
   			wikidataInterwiki['wikidata'] = entity.id
			wikidataInterwiki = p.populateInterwikiByEntity(entity, wikidataInterwiki)
			if wikidataInterwiki[RUWIKISOURCE] == mw.title.getCurrentTitle().fullText then
				wikidataInterwiki[RUWIKISOURCE] = nil
			end
		end
	elseif manual_topic_id then 
   		local topic_item = mw.wikibase.getEntity(manual_topic_id)
		wikidataInterwiki = p.populateInterwikiByEntity(topic_item, wikidataInterwiki)
	end
	return wikidataInterwiki
end

-- сортировка интервик в формате [проект.язык.страница]. Без monolanguage-проектов.
function reshape_interwiki_by_projects( wikidataInterwiki )
	local interwiki_by_projects = {}
	for _, project in pairs( projects ) do
		local project_code = project.project
		for langprojectcode, title in pairs( wikidataInterwiki ) do
			if title and langprojectcode ~= 'commonswiki' then
				local lang = string.match( langprojectcode, '^(.*)'..project_code..'$' )
				if lang and lang ~= '' then 
					if not interwiki_by_projects[project_code] then interwiki_by_projects[project_code] = {} end
					interwiki_by_projects[project_code][lang] = title 
				end
			end
		end
	end
	return interwiki_by_projects
end

-- сюда собираются данные
local result = { 
	wlinks = {},
	categories = {}, categories_raw = {},
	IS_PRS = require( "Module:Header" ).parse_title( mw.title.getCurrentTitle().text , "isPRS" ),  -- isDO = isCurrentPageDO()
}
function result:add(project, manual_link, manual_lang, wd_link, wd_lang)
	link, lang = set_priority_links(manual_link, manual_lang, wd_link, wd_lang)
	table.insert(self.wlinks, {project=project, link=link, lang=lang, manual_link=manual_link, manual_lang=manual_lang, wd_link=wd_link, wd_lang=wd_lang} )
end
function result:add_category(s)
	table.insert(self.categories, '[[Категория:'..s..']]')
	table.insert(self.categories_raw, s)
end

-- сбор данных
function scrape_data( frame )
	local args = frame:getParent().args
	local wikidataInterwiki = populate_interwikis_from_item( args['ВИКИДАННЫЕ'] )
	local interwiki_by_projects = reshape_interwiki_by_projects( wikidataInterwiki )
	-- mw.logObject(interwiki_by_projects, "interwiki_by_projects")

	for _, project in pairs( projects ) do
				local projectCode = project.project  -- like 'wikisource'
                local prj_arg = project.arg
                local prj_code = project.code
                local prj_name = project.name
                local manual_link, wd_link
                local manual_lang, wd_lang
                local manual_title, wd_title
                local interwikis = interwiki_by_projects[ projectCode ]
		repeat 

			-- ручная ссылка в параметре шаблона
			local value = args[prj_arg]  -- значение параметра
			if value and #value > 0 then
				if value ~= '__NULL__' then
					local lang, title = string.match( value, '^:?([a-z]+):(.+)' )  -- ссылка формата ':код языка:Название'
					if lang then 
						manual_link = prj_code .. ':' .. lang .. ':' .. title
						manual_lang = lang	
						manual_title = title
					else
						manual_lang = RU
						manual_title = value
						if projectCode == WIKISOURCE then
							manual_link = value
						else
							manual_link = prj_code .. ':' .. value
						end
					end
					result:add_category('Ручная ссылка:' .. prj_name)
				end
			end
	
			-- поиск ссылки в Викиданных
			-- моноязычные проекты (Викисклад, Викивиды, Викиданные)
			if project.monolanguage then
				-- При projectCode == "wikidata":
				-- - для энциклопедических статей категория "Ссылка из Викиданных" ставится по property P921 ("основная тема")
				-- - для других страниц - если Викитека указана в списке проектов 
				local title = wikidataInterwiki[ projectCode ]
				if projectCode == 'commons' and not title then
					title = wikidataInterwiki[ 'commonswiki' ]
				end
				if title then
					wd_link = prj_code .. ':' .. title
					wd_title = title
					result:add_category('Ссылка из Викиданных:' .. prj_name)
				-- elseif projectCode == 'commons' then
				-- 	'P373'
				end

			-- мультиязычные проекты
			elseif interwikis then
				-- mw.logObject(interwikis, "interwikis")
				local foundProjectLink = false
				
				-- перебор предпочтительных языков preferredLanguages
				for _, preflang in pairs( preferredLanguages ) do
					
					if preflang == '*' then 
						-- языки под '*' в preferredLanguages
						for lang, title in pairs( interwikis ) do 
							if lang and projectCode ~= WIKISOURCE then  -- ссылки на малоразвитые интерВикитеки не нужны
								wd_link = prj_code .. ':' .. lang .. ':' .. title
								wd_lang = lang
								wd_title = title
								result:add_category('Ссылка из Викиданных на интервики:' .. prj_name)
								foundProjectLink = true
								break
							end
						end
						
					else  
						-- языки не под '*' в preferredLanguages
						local lang = preflang
						local title = interwikis[ lang ]
						if title then
							if lang == RU then
								if projectCode == WIKISOURCE then
									wd_link = ':' .. title
								else
									wd_link = prj_code .. ':' .. title
								end
								if not manual_link and not (is_author_page and projectCode == WIKISOURCE) then
									result:add_category('Ссылка из Викиданных:' .. prj_name)
								end
							else  
								if projectCode == WIKISOURCE then
									wd_link = ':' .. lang .. ':' .. title
								else
									wd_link = prj_code .. ':' .. lang .. ':' .. title
								end
								if not manual_link and not (is_author_page and projectCode == WIKISOURCE) then
									result:add_category('Ссылка из Викиданных на интервики:' .. prj_name .. ':язык:' .. lang)
								end
							end
							wd_lang = lang
							wd_title = title
							foundProjectLink = true
						end
					end
					
					if foundProjectLink then break end
				end
				
				if manual_link and manual_title then
					-- категоризация: есть ли ручная ссылка в интервиках ВД
					local is_manual_link_in_wd
					manual_title = mw.ustring.gsub(manual_title, '_', ' ')
					for lang, title in pairs( interwikis ) do 
						if lang == manual_lang and title == manual_title then
							is_manual_link_in_wd = true
							result:add_category('Ручная ссылка совпадает со ссылкой из Викиданных:'..prj_name)
							break
						end
					end
					if not is_manual_link_in_wd then
						result:add_category('Ручная ссылка отличается от ссылки из Викиданных:'..prj_name)
					end
				end
			end
			
			if not wd_link and not manual_link and projectCode == 'wiki' then
				-- если нет ссылок на Википедию
				local searchString = args[ argSearch ] or frame.args.search
				if searchString and #searchString > 0 then
					manual_link = prj_code .. ':Special:Search/' .. searchString
					manual_lang = RU
				end
			end
			
			if manual_link or wd_link then
				result:add( project, manual_link, manual_lang, wd_link, wd_lang )
				break  -- continue to next project
			end
		until true
	end
	-- Для страниц авторов категории дальше меняются в Модуль:Обавторе в change_wd_categories()
	
	-- mw.logObject(result, "result")
	return result
end
function p.scrape_data( frame , _is_author_page)
	is_author_page = _is_author_page
	return scrape_data( frame ) 
end


-- Приоритет ссылок: ручных ссылок или из ВД.
function set_priority_links(manual_link, manual_lang, wd_link, wd_lang)
    local link, lang
	if manual_link and wd_link then
		-- RU ссылки приоритетней из ВД
		-- Ручные обычно устарели на 5-10 лет, часто ведут на ошибочные, удалённые статьи или переделанные в неоднозначности
		if wd_lang == RU then 
			link, lang = wd_link, wd_lang 
			
		-- Ссылка на интервики из ВД только если нет ручной ссылки. Поскольку ручная ссылка обычно точнее общей, указанной в preferredLanguages
		else
			for _, preflang in pairs( preferredLanguages ) do 
				if preflang ~= RU and preflang ~= '*' then 
					if preflang == wd_link then 
						link, lang = wd_link, wd_lang 
			 			break
			 		end
		 		end
			end
		end
		if link == nil then link, lang = manual_link, manual_lang end
	elseif manual_link then 
		link, lang = manual_link, manual_lang 
	elseif wd_link then 
		link, lang = wd_link, wd_lang 
	end
	return link, lang
end

-- оформление строки для {{отексте}} и Модуль:Отексте
function p.render( frame )
	local dataset = scrape_data( frame )
	if #dataset.wlinks > 0 then 
		local links = {}
		for _, v in pairs(dataset.wlinks) do
			local project, link, lang = v.project, v.link, v.lang
			if link then
				local prj_title = '';	if dataset.IS_PRS then prj_title = project.titlePRS else prj_title = project.title end
				local lang_label = ''; if lang and lang ~= RU then lang_label = "&thinsp;<sup><small>("..lang..")</small></sup>" end
				table.insert(links, "[[Файл:"..project.logo.."|13px|link="..link .."]]&nbsp;[["..link.."|"..prj_title.."]]" .. lang_label)
			end
		end
		
		local result = ''
		if #links > 0 then 
			links = table.concat(links, '&nbsp;')
			result = "&nbsp;<span style=\"float:right;\">'''Википроекты:'''&nbsp;"..links..'</span>'
		end
		if #dataset.categories > 0 then result = result .. table.concat(dataset.categories) end
		return result
	end
end

return p