Module:Cite Wikidata

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules

The template supports journal and book publications:

Code

--[[  
  __  __           _       _         ____ _ _        __        ___ _    _     _       _        
 |  \/  | ___   __| |_   _| | ___ _ / ___(_) |_ ___  \ \      / (_) | _(_) __| | __ _| |_ __ _ 
 | |\/| |/ _ \ / _` | | | | |/ _ (_) |   | | __/ _ \  \ \ /\ / /| | |/ / |/ _` |/ _` | __/ _` |
 | |  | | (_) | (_| | |_| | |  __/_| |___| | ||  __/   \ V  V / | |   <| | (_| | (_| | || (_| |
 |_|  |_|\___/ \__,_|\__,_|_|\___(_)\____|_|\__\___|    \_/\_/  |_|_|\_\_|\__,_|\__,_|\__\__,_|

This module is intended for creating citation templates based on wikidata items.

Please do not modify this code without applying the changes first at Module:Cite Wikidata/sandbox and testing 
at Module:Cite Wikidata/sandbox/testcases and Module talk:Cite Wikidata/sandbox/testcases.

Authors and maintainers:
* User:Jarekt 
]]
require('strict') -- used for debugging purposes as it detects cases of unintended global variables
local ISOdate = require('Module:ISOdate')._ISOdate -- date localization
local core    = require('Module:Core')

-- =======================================
-- === Local Functions ===================
-- =======================================

-------------------------------------------------------------------------------
local function getProperty(entity, prop, lang)
	return (core.parseStatements(entity:getBestStatements( prop ), lang) or {nil})[1]
end

-------------------------------------------------------------------------------
local function getAllProperties(entity, prop, lang)
	return core.parseStatements(entity:getAllStatements( prop ), lang)
end

-------------------------------------------------------------------------------
local function getItemProperty(item, prop)
	return (core.parseStatements(mw.wikibase.getAllStatements( item, prop ), nil) or {nil})[1]
end

---------------------------------------------------------------------------
-- use different sitelink call depending if you already have an entity or not
-- INPUTS:
--  * item and entity - entity id and entity: if full entity already uploded than use that
--                      otherwise use entity id to look up sitelink
--  * prop - property for which to return the best statment
-- OUTPUT:
--  * value of the best statment (only from the first one)
local function getAllProperties2(item, entity, prop)
	local statements
	if entity then
		statements = entity:getBestStatements(prop)
	else
		statements = mw.wikibase.getBestStatements(item, prop)
	end
	return core.parseStatements(statements, nil)
end

-------------------------------------------------------------------------------
local function monolingualtext2table(statements, lang)
	local Table = {}
	for _, statement in pairs( statements ) do 
		if (statement.mainsnak.snaktype == "value") then 
			local val = statement.mainsnak.datavalue.value
			Table[val.language] = val.text -- look for multiple values each with a language code
		end
	end
	return Table
end

-- ===========================================================================
-- === harvest properties with qualifiers                                   ===
-- ===========================================================================
local function getPropertyQual(entity, prop, qualifiers, lang)
	local Output = {}
	if entity.claims and entity.claims[prop] then
		for _, statement in pairs( entity:getBestStatements( prop )) do
			local output = {} -- table with fields: key, value, P... (qualifiers)
			output.value = core.parseSnak(statement.mainsnak, lang)		
			for iQual, qual in ipairs( qualifiers ) do
				if statement.qualifiers and statement.qualifiers[qual] then
					output[qual] = core.parseSnak(statement.qualifiers[qual][1], lang)	
				end
				table.insert(Output, output)
			end
		end
	end
	return Output
end

-- ===========================================================================
-- === Harvest wikidata properties matching creator template fields        ===
-- === INPUTS:                                                             ===
-- ===  * entity - wikidata entity                  ==
-- ===  * itemID2 - item id or a q-code from SDC                           ===
-- ===  * lang  - language id of the desired language                      ===
-- ===  * namespace - namespace number of the page calling the module      ===
-- ===========================================================================
local function harvest_wikidata(entity, lang)
	local data = {}
	local comma = mw.message.new( "comma-separator"):inLanguage(lang):plain()
	           .. mw.message.new( "Word-separator" ):inLanguage(lang):plain()

	-- harvest string or Q-code properties where a single value is expected
	local property = { P123='publisher', P291='location', P629='edition_of', P1433='journal',     
					   P275='license',   P393='edition',  P478='volume',     P433='issue', 
					   P356='doi',       P698='PubMedID', P932='PMCID',      P212='ISBN', -- IDs
					   P571='inc_date',  P577='pub_date' }                                 -- dates
	for prop, field in pairs( property ) do
		data[field] = getProperty(entity, prop, lang)
	end

	-- get External identifiers
	local T = {}
	local fields = {doi= 'doi: [https://dx.doi.org/%s %s]', 
			PubMedID = 'PubMed ID: [https://www.ncbi.nlm.nih.gov/pubmed/?term=/%s %s]', 
			PMCID    = 'PubMed Central ID: [https://www.ncbi.nlm.nih.gov/pmc/articles/PMC?term=/%s %s]', 
			ISBN     = 'ISBN [[Special:BookSources/%s|%s]]',
			inc_date = 'date', pub_date = 'date'}
	for field, val in pairs( fields ) do
		if data[field] and val == 'date' then
			data[field] = ISOdate(data[field], lang, '', 'dtstart', '100-999')
		elseif data[field]  then
			table.insert(T, '<small>' .. string.format( val, data[field], data[field]) .. '</small>')
		end
	end
	data.ident = table.concat(T, comma)
	data.date  = data.pub_date or data.inc_date
	
	-- harvest string or Q-code properties where multiple values are expected
	local property = {  P407='lang', P98='editor'} 
	for prop, field in pairs( property ) do
		local ids = getAllProperties(entity, prop, lang)
		if ids then
			data[field] = table.concat(ids, comma) 
		end
	end
	
	-- harvest monolingualtext text properties 
	local property = { P1476 = 'title', P1680='subtitle', P179='series_title'}
	for prop, field in pairs( property ) do
		local Table = monolingualtext2table( entity:getBestStatements( prop ), lang)
		data[field] = core.langSwitch(Table, lang)
	end
	data.title = data.title or core.getLabel(entity.id, lang) -- if title not provided than use label
	
	-- harvest author properties while using 'P1545' (series ordinal ) as sort key 
	local AuthorTable = {}
	local property = { P50='author', P2093='authorStr'} 
	for prop, field in pairs( property ) do
		local authors = getPropertyQual(entity, prop, {'P1545'}, lang)
		if authors then
			for _, author in ipairs(authors) do
				author.P1545 = author.P1545 or (#AuthorTable+1000) -- if no P1545 than keep original order
				table.insert(AuthorTable, author)
			end
		end
	end
	if #AuthorTable>0 then
		local tableComp = function (a, b) return a.P1545<b.P1545 end
		table.sort(AuthorTable, tableComp)
		T = {}
		for _, author in ipairs(AuthorTable) do
			table.insert(T, author.value)
		end
		data.author = table.concat(T, comma) 
	end

	return data
end

-- ===========================================================================
local function getFormatString(strType, lang)
	-- fetch a text template for a given language from [[Data:I18n/Cite Wikidata.tab]]
	local text
	local key  = (strType=='book' and 'book' ) or 'journal'
	local tab  = mw.ext.data.get('I18n/Cite Wikidata.tab', lang)
	for _, row in pairs(tab.data) do
		local id, _, msg = unpack(row)
		if id == key then
			text = msg
			break
		end
	end
	return mw.ustring.gsub(text, '\|', '!')
end

-- ===========================================================================
local function load_LUT()
	-- fetch a look-up-table from [[Data:Cite Wikidata LUT.tab]]. The table is handy 
	-- so we can add more items easy without changing the source code
	local LUT = {}
	local tab  = mw.ext.data.get('Cite Wikidata LUT.tab', 'en')
	for _, row in pairs(tab.data) do
		local item, _, template = unpack(row)
		LUT[item] = template
	end
	return LUT
end

-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}

-- ===========================================================================
-- === Version of the function to be called from other LUA codes
-- ===========================================================================

-- ===========================================================================
function p.getCitationType(entity, item)	
	-- Determine if we should use book or article cite pattern
	local iType
	
	-- first check "instance_of" properties
	local LUT =load_LUT()
	for _, instance in pairs(getAllProperties2(item, entity, 'P31')) do
		iType = LUT[instance]
		if iType then 
			return iType
		end
	end
	
	-- if that did not work check for presence of key properties
	local P = { edition_of='P629', journal='P1433', issue='P433'} 
	if entity and entity.claims then 
		if (entity.claims[P.edition_of]) then 
			iType = 'book'
		elseif (entity.claims[P.journal] or entity.claims[P.issue]) then 
			iType = 'article'
		end
	elseif item then
		if getItemProperty(item, P.edition_of) then
			iType = 'book'
		elseif (getItemProperty(item, P.journal) or getItemProperty(item, P.issue)) then 
			iType = 'article'
		end		
	end
	
	return iType
end

-- ===========================================================================
function p._citeWikidata(item, lang, page)
	local entity
	if type(item) == 'string' then
		entity = mw.wikibase.getEntity(item)
	else
		entity = item
	end
	if not entity then
		return nil
	end
	local data = harvest_wikidata(entity, lang)
	data.type  = p.getCitationType(entity, nil)
	page = tonumber(page)
	if page and page>0 then
		data.page = page
	end
	
	-- fetch a text template for a given language
	local text = getFormatString(data.type, lang)
	
	-- build text of the citation based on the text template
	-- replace "$FIELD" in the "text" with data.field value
	local fields = {'author', 'editor', 'title', 'edition', 'location', 'publisher', 
		'date', 'journal', 'volume', 'issue', 'lang', 'subtitle', 'ident', 'page'}		
	for _, field in ipairs(fields) do
		if data[field] then
		    -- replace string like "$DATE" with data.date
			text = mw.ustring.gsub(text, '$'..string.upper(field), data[field])
		end
	end
	for i = 1,5,1 do 
		-- delete and cell bracketed with "|" (now changed to "!") so we do not break 
		-- links with a single | (!)
		text = mw.ustring.gsub(text, '\![^$\!]*$[^\!]*\!', '!')
	end
	text = mw.ustring.gsub(text, '\!', '') -- remove all "|" (now changed to "!")
	text = mw.ustring.gsub(text, "^[%s\.\,]*(.-)%s*$", "%1") -- trim leading whitespaces
	return text
end

-- ===========================================================================
-- === Versions of the function to be called from template namespace
-- ===========================================================================
function p.debug(frame)
	local args   = core.getArgs(frame)
	local entity = mw.wikibase.getEntity(args.item)
	local data   = harvest_wikidata(entity, args.lang)
	local str = ''
	for field, val in pairs( data ) do
		if type(val)=='string' then
			str = str ..  '*' .. field .. ' = ' .. val .. '\n'
		else
			str = str ..  '*' .. field .. ' = ' .. table.concat(val, ' / ') .. '\n'
		end
	end
	local formatStr = getFormatString(data.type, args.lang)
	str = str ..  '* format string = ' .. formatStr .. ' for language ' .. args.lang .. '\n'
	return str
end

-- ===========================================================================
function p.citeWikidata(frame)
	local args = core.getArgs(frame)
	return p._citeWikidata(args.item, args.lang, args.page)
end

-- ===========================================================================
function p.reflist(frame)
	local args = core.getArgs(frame)
	local str = ''
	for i, j in pairs(arg.list) do
		str = str ..  '<li> ' .. p.citeitem(mw.text.trim(j),arg.lang) .. '</li>'
	end
	return str
end

return p