Revision as of 08:03, 11 October 2017 view sourceChire (talk | contribs)Extended confirmed users1,803 edits as per Template talk:Cite Q, prefer author name string over Wikidata label auf author entity, if we have both (e.g., maiden name on a paper, when author changed name later)← Previous edit | Latest revision as of 13:32, 5 July 2024 view source Trappist the monk (talk | contribs)Administrators479,918 editsNo edit summary | ||
(29 intermediate revisions by 16 users not shown) | |||
Line 1: | Line 1: | ||
-- Version: 2021-10-19 | |||
require('Module:No globals') | |||
local |
local p = {} | ||
require('strict') | |||
--[[--------------------------< I S _ S E T >------------------------------------------------------------------ | |||
local wdib = require('Module:WikidataIB') | |||
local getValue = wdib._getValue | |||
local getPropOfProp = wdib._getPropOfProp | |||
local followQid = wdib._followQid | |||
local getPropertyIDs = wdib._getPropertyIDs | |||
local i18n = { | |||
Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string. | |||
= mw.wikibase.getLabel("Q4233718"):gsub("^%l", mw.ustring.upper), | |||
= "]", | |||
= { | |||
= "st", | |||
= "nd", | |||
= "rd", | |||
= "th" | |||
}, | |||
= { | |||
"January", "February", "March", "April", "May", "June", | |||
"July", "August", "September", "October", "November", "December" | |||
}, | |||
} | |||
------------------------------------------------------------------------------- | |||
-- makeOrdinal needs to be internationalised along with the above i18n | |||
-- takes cardinal number as a numeric and returns the ordinal as a string | |||
-- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc. | |||
------------------------------------------------------------------------------- | |||
p.makeOrdinal = function(cardinal) | |||
local card = tonumber(cardinal) | |||
if not card then return cardinal end | |||
local ordsuffix = i18n.ordinal.default | |||
if card % 10 == 1 then | |||
ordsuffix = i18n.ordinal | |||
elseif card % 10 == 2 then | |||
ordsuffix = i18n.ordinal | |||
elseif card % 10 == 3 then | |||
ordsuffix = i18n.ordinal | |||
end | |||
-- In English, 1, 21, 31, etc. use 'st', but 11, 111, etc. use 'th' | |||
-- similarly for 12 and 13, etc. | |||
if (card % 100 == 11) or (card % 100 == 12) or (card % 100 == 13) then | |||
ordsuffix = i18n.ordinal.default | |||
end | |||
return card .. ordsuffix | |||
end | |||
-- Table of simple properties that can be fetched in roughly the same way: | |||
-- id = PXXX | |||
-- maxvals = maximum number of multiple values (0 for all) | |||
-- linked = "no" suppresses linking | |||
-- populate_from_journal = true/false determines whether to look in a journal where the source is published | |||
-- rank = "best", "preferred", normal, etc. determines how Wikidata ranks are treated | |||
-- others = true - the value for the property goes to "others" section | |||
local simple_properties = { | |||
publisher = {id = "P123", maxvals = 1}, | |||
oclc = {id = "P243", maxvals = 1}, | |||
= {id = "P291", maxvals = 0, linked = 'no'}, -- publication place (don't put into |place=; is treated specially in {{citation}} if both are given) | |||
doi = {id = "P356", maxvals = 1}, -- take care of |doi-broken-date= (WD "reason for deprecation"/"stated as") and |doi-access= (WD "access status")? | |||
issue = {id = "P433", maxvals = 0, populate_from_journal = true}, -- distinguish from |number= ("P1545"?) if both are given (still blocked by {{citation}}, but will be supported in the future) | |||
pmid = {id = "P698", maxvals = 1}, | |||
-- gbooks = {id = "P675", maxvals = 1}, -- to be added to {{citation}} | |||
-- ia = {id = "P724", maxvals = 1}, -- to be added to {{citation}} | |||
arxiv = {id = "P818", maxvals = 1}, | |||
bibcode = {id = "P819", maxvals = 1}, -- take care of |bibcode-access=? | |||
jstor = {id = "P888", maxvals = 1}, -- take care of |jstor-access=? | |||
mr = {id = "P889", maxvals = 1}, | |||
rfc = {id = "P892", maxvals = 1}, | |||
zbl = {id = "P894", maxvals = 1}, | |||
ssrn = {id = "P893", maxvals = 1}, | |||
place = {id = "P1071", maxvals = 0, linked = 'no'}, -- written-at place | |||
-- = {id = "P1104", maxvals = 0, linked = 'no'}, -- to be added to {{citation}} / COinS &rft.tpages= | |||
-- coden = {id = "P1159", maxvals = 1}, -- to be added to {{citation}} / COinS &rft.coden= | |||
s2cid = {id = "P8299", maxvals = 1}, -- take care of |s2cid-access=? | |||
pmc = {id = "P932", maxvals = 1}, -- take care of |pmc-embargo-date= (WD "reason for deprecation")? | |||
lccn = {id = "P1144", maxvals = 1}, | |||
hdl = {id = "P1184", maxvals = 1}, -- take care of |hdl-access=? | |||
ismn = {id = "P1208", maxvals = 1}, | |||
journal = {id = "P1433", maxvals = 1}, | |||
citeseerx = {id = "P3784", maxvals = 1}, | |||
osti = {id = "P3894", maxvals = 1}, -- take care of |osti-access=? | |||
biorxiv = {id = "P3951", maxvals = 1}, | |||
asin = {id = "P5749", maxvals = 1}, -- What about |asin-tld=? (WD examples resolve to .com at present, but may change) | |||
-- = {id = "P528", maxvals = 0}, -- to be added to {{citation}} / COinS &rft.artnum= | |||
isbn = {id = "P212", maxvals = 1, populate_from_journal = true}, -- ISBN 13 | |||
issn = {id = "P236", maxvals = 1, populate_from_journal = true}, -- distinguish from |eissn= for electronic issues? | |||
-- jfm = {id = "P?", maxvals = 1}, -- Jahrbuch über die Fortschritte der Mathematik (not Zbl) | |||
-- sbn = {id = "P?", maxvals = 1}, -- Standard Book Number (predecessor of ISBN, not ICCU) | |||
-- message-id = {id = "P?", maxvals = 1}, -- Usenet message ID | |||
chapter = {id = "P792", maxvals = 1}, | |||
= {id = "P577", maxvals = 1, populate_from_journal = true}, -- publication date (don't use |date=; is treated specially in {{citation}} if both are given.) | |||
series = {id = "P179", maxvals = 1, populate_from_journal = true}, | |||
version = {id = "P348", maxvals = 0}, | |||
edition = {id = "P393", maxvals = 0}, | |||
volume = {id = "P478", maxvals = 0, populate_from_journal = true}, | |||
-- part = {id = "P1545"?, maxvals = 0}, -- to be added to {{citation}} / COinS &rft.part= | |||
title = {id = "P1476", rank="p n"}, | |||
-- url = {id = "P953", maxvals = 1}, -- deal with this along with archive-url | |||
pages = {id = "P304", maxvals = 0, populate_from_journal = true}, | |||
at = {id = "P958", maxvals = 0, populate_from_journal = true}, -- also incorporate lines (P7421) and columns (P3903) into this (cite map also supports |section=) | |||
-- sheets = {id = "P7416", maxvals = 0, populate_from_journal = true}, | |||
-- interviewer = {id = "P?", maxvals = 0}, -- does **not** go to "others" section! Multiple interviewers should be n-enumerated | |||
illustrator = {id = "P110", maxvals = 10, others = true}, -- goes to "others" section | |||
-- foreword and afterword, when contributions to another author's work, are contributions so belong in |contribution=; | |||
-- the writer's name goes in |contributor=; requires |title= and |author= | |||
-- However, this might need to add support for multiple contributors and their roles to {{citation}}, see Help_talk:Citation_Style_1#Others | |||
-- foreword = {id = "P2679", maxvals = 10, others = true}, -- goes to "others" section | |||
-- afterword = {id = "P2680", maxvals = 10, others = true}, -- goes to "others" section | |||
composer = {id = "P86", maxvals = 10, others = true}, -- goes to "others" section | |||
animator = {id = "P6942", maxvals = 10, others = true}, -- goes to "others" section | |||
director = {id = "P57", maxvals = 10, others = true}, -- goes to "others" section | |||
screenwriter = {id = "P58", maxvals = 10, others = true}, -- goes to "others" section | |||
signatory = {id = "P1891", maxvals = 10, others = true}, -- goes to "others" section | |||
presenter = {id = "P371", maxvals = 10, others = true}, -- goes to "others" section | |||
performer = {id = "P175", maxvals = 10, others = true}, -- goes to "others" section | |||
} | |||
--[[--------------------------< I S _ S E T >-------------------------------------------------------------- | |||
Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string. | |||
]] | ]] | ||
local function is_set( var ) | local function is_set( var ) | ||
return not (var == nil or var == '') |
return not (var == nil or var == '') | ||
end | end | ||
--[[--------------------------< I N _ A R R A Y >-------------------------------------------------------------- | |||
Whether needle is in haystack (taken from Module:Citation/CS1/Utilities) | |||
]] | |||
local function in_array( needle, haystack ) | |||
if needle == nil then | |||
return false | |||
end | |||
for n, v in ipairs( haystack ) do | |||
if v == needle then | |||
return n | |||
end | |||
end | |||
return false | |||
end | |||
--[=[-------------------------< G E T _ N A M E _ L I S T >---------------------------------------------------- | |||
--[[--------------------------< A C C E P T _ V A L U E >------------------------------------------------------- | |||
get_name_list -- adapted from getAuthors code taken from ] | |||
Accept WD value by framing in ((...)) if param_val is equal to keyword; else pass-through WD value as is. | |||
]] | |||
local function accept_value( param_val, wd_val ) | |||
local val = param_val | |||
if val then | |||
if in_array (val, {'accept', '))((', ':d:'}) then | |||
val = '((' .. wd_val .. '))' | |||
elseif '((accept))' == val then | |||
val = 'accept' | |||
elseif '(())(())' == val then | |||
val = '))((' | |||
elseif '((:d:))' == val then | |||
val = ':d:' | |||
else | |||
val = wd_val | |||
end | |||
end | |||
return val | |||
end | |||
-- function to fetch a value to display | |||
local function makelink(v, out, link, maxpos, wdl) | |||
local label | |||
if v.mainsnak.snaktype == "value" then | |||
if v.mainsnak.datatype == "wikibase-item" then | |||
local qnumber = v.mainsnak.datavalue.value.id | |||
local sitelink = mw.wikibase.getSitelink(qnumber) | |||
if qnumber == "Q2818964" then sitelink = nil end -- suppress link to "Various authors" | |||
if v.qualifiers and v.qualifiers.P1932 then | |||
label = v.qualifiers.P1932.datavalue.value | |||
else | |||
label = mw.wikibase.getLabel(qnumber) | |||
if label then | |||
label = mw.text.nowiki(label) | |||
else | |||
label = qnumber -- should add tracking category | |||
end | |||
end | |||
local position = maxpos + 1 -- Default to 'next' author. | |||
-- use P1545 (series ordinal) instead of default position. | |||
if v and v.qualifiers and v.qualifiers then | |||
position = tonumber(v.qualifiers.datavalue.value) | |||
end | |||
maxpos = math.max(maxpos, position) | |||
if sitelink then | |||
-- just the plain name, | |||
-- but keep a record of the links, using the same index | |||
out = label | |||
link = sitelink | |||
else | |||
if wdl then | |||
-- show that there's a Wikidata entry available | |||
out = " .. "|" .. label .. "]] <span title='" .. i18n .. "'>]</span>" | |||
else | |||
-- no Wikidata links wanted, so just give the plain label | |||
out = label | |||
end | |||
end | |||
elseif v.mainsnak.datatype == "string" then | |||
local position = maxpos + 1 -- Default to 'next' author. | |||
-- use P1545 (series ordinal) instead of default position. | |||
if v and v.qualifiers and v.qualifiers then | |||
position = tonumber(v.qualifiers.datavalue.value) | |||
end | |||
maxpos = math.max(maxpos, position) | |||
out = v.mainsnak.datavalue.value | |||
else | |||
-- not a wikibase-item or a string! | |||
end | |||
else | |||
-- code here if we want to return something when author is "unknown" | |||
if v.qualifiers and v.qualifiers.P1932 then | |||
label = v.qualifiers.P1932.datavalue.value | |||
else | |||
label = i18n .. (i18n or "") | |||
end | |||
maxpos = maxpos + 1 | |||
out = label | |||
end | |||
return maxpos | |||
end | |||
--[=[-------------------------< G E T _ N A M E _ L I S T >---------------------------------------------------- | |||
get_name_list -- adapted from getAuthors code taken from Module:RexxS | |||
arguments: | arguments: | ||
nl_type - type of name list to fetch: nl_type = 'author' for authors; 'editor' for editors | nl_type - type of name list to fetch: nl_type = 'author' for authors; 'editor' for editors; 'translator' for translators | ||
args - pointer to the parameter arguments table from the template call | args - pointer to the parameter arguments table from the template call | ||
qid - value from |qid= parameter; the Q-id of the source (book, etc.) in qid | qid - value from |qid= parameter; the Q-id of the source (book, etc.) in qid | ||
wdl - value from the | |
wdl - value from the |wdl= parameter; a Boolean passed to enable links to Wikidata when no article exists | ||
returns nothing; modifies the args table | returns nothing; modifies the args table | ||
]=] | ]=] | ||
Line 29: | Line 240: | ||
local propertyID = "P50" | local propertyID = "P50" | ||
local fallbackID = "P2093" -- author name string | local fallbackID = "P2093" -- author name string | ||
if |
if nl_type =="author" then | ||
propertyID = 'P50' |
propertyID = 'P50' -- for authors | ||
fallbackID = 'P2093' |
fallbackID = 'P2093' -- author-string | ||
elseif |
elseif nl_type =="editor" then | ||
propertyID = ' |
propertyID = 'P5769' -- "editor-in-chief" | ||
fallbackID = 'P98' -- for editors - So-called "fallbacks" are actually a second set of properties processed | |||
fallbackID = nil; | |||
-- TBD. Take book series editors into account as well (if they have a separate P code as well)? | |||
elseif nl_type == "translator" then | |||
propertyID = 'P655' -- for translators | |||
fallbackID = nil | |||
-- elseif 'contributor' == nl_type then | |||
-- f.e. author of forewords (P2679) and afterwords (P2680); requires |contribution=, |title= and |author= | |||
-- propertyID = 'P' -- for contributors | |||
-- fallbackID = nil | |||
else | else | ||
return |
return -- not specified so return | ||
end | end | ||
-- |
-- wdl is a Boolean passed to enable links to Wikidata when no article exists | ||
-- if "false" or "no" or "0" is passed set it false | -- if "false" or "no" or "0" is passed set it false | ||
-- if nothing or an empty string is passed set it false | -- if nothing or an empty string is passed set it false | ||
if wdl and (#wdl > 0) then | if wdl and (#wdl > 0) then | ||
wdl = wdl:lower() | wdl = wdl:lower() | ||
wdl = (wdl |
wdl = in_array (wdl, {"false", "no", "0"}) | ||
else | else | ||
-- wdl is empty, so | -- wdl is empty, so | ||
wdl = false | wdl = false | ||
end | end | ||
local entity = mw.wikibase.getEntity(qid) | |||
local props = nil | local props = nil | ||
local fallback = nil | local fallback = nil | ||
if mw.wikibase.entityExists(qid) then | |||
if entity and entity.claims then | |||
props = |
props = mw.wikibase.getAllStatements(qid, propertyID) | ||
if fallbackID then | if props and fallbackID then | ||
fallback = |
fallback = mw.wikibase.getAllStatements(qid, fallbackID) | ||
end | end | ||
end | end | ||
-- Make sure it actually has at least one of the properties requested | -- Make sure it actually has at least one of the properties requested | ||
if not (props and props) and not (fallback and fallback) then |
if not (props and props) and not (fallback and fallback) then | ||
return nil | return nil | ||
end | end | ||
-- So now we have something to return: | -- So now we have something to return: | ||
-- table 'out' is going to store the names(s): | -- table 'out' is going to store the names(s): | ||
Line 74: | Line 292: | ||
if props and props then | if props and props then | ||
for k, v in pairs(props) do | for k, v in pairs(props) do | ||
maxpos = makelink(v, out, link, maxpos, wdl) | |||
local qnumber = "Q" .. v.mainsnak.datavalue.value | |||
local sitelink = mw.wikibase.sitelink(qnumber) | |||
local label = mw.wikibase.label(qnumber) | |||
if label then | |||
label = mw.text.nowiki(label) | |||
else | |||
label = qnumber | |||
end | |||
local position = maxpos + 1 -- Default to 'next' author. | |||
-- use P1545 (series ordinal) instead of default position. | |||
if v and v.qualifiers and v.qualifiers then | |||
position = tonumber(v.qualifiers.datavalue.value) | |||
end | |||
maxpos = math.max(maxpos, position) | |||
if sitelink then | |||
-- just the plain name, | |||
-- but keep a record of the links, using the same index | |||
out = label | |||
link = sitelink | |||
else | |||
-- no sitelink, so check first for a redirect with that label | |||
-- this code works, but causes the article to appear in WhatLinksHere for the possible destination, so remove | |||
-- local artitle = mw.title.new(label, 0) | |||
-- if artitle.id > 0 then | |||
-- if artitle.isRedirect then | |||
-- no sitelink, | |||
-- but there's a redirect with the same title as the label; | |||
-- so store the link to that | |||
-- out = label | |||
-- link = label | |||
-- else | |||
-- no sitelink and not a redirect but an article exists with the same title as the label | |||
-- that's probably a dab page, so output the plain label | |||
-- out = label | |||
-- end | |||
--else | |||
-- no article or redirect with the same title as the label | |||
if wdl then | |||
-- show that there's a Wikidata entry available | |||
out = " .. "|" .. label .. "]] <span title='" .. i18n .. "'>]</span>" | |||
else | |||
-- no wikidata links wanted, so just give the plain label | |||
out = label | |||
end | |||
-- end | |||
end | |||
end | end | ||
end | end | ||
if fallback and fallback then | if fallback and fallback then | ||
-- second properties | |||
-- Fallback to name-only authors / editors | |||
for k, v in pairs(fallback) do | for k, v in pairs(fallback) do | ||
maxpos = makelink(v, out, link, maxpos, wdl) | |||
local label = v.mainsnak.datavalue | |||
local position = maxpos + 1 -- Default to 'next' author. | |||
-- use P1545 (series ordinal) instead of default position. | |||
if v and v.qualifiers and v.qualifiers then | |||
position = tonumber(v.qualifiers.datavalue.value) | |||
end | |||
maxpos = math.max(maxpos, position) | |||
out = label | |||
end | end | ||
end | end | ||
Line 140: | Line 306: | ||
-- Renumber, in case we have inconsistent numbering | -- Renumber, in case we have inconsistent numbering | ||
local keys = {} | local keys = {} | ||
for k,v in pairs(out) do | for k, v in pairs(out) do | ||
keys = k | keys = k | ||
end | end | ||
table.sort(keys) -- as they might be out of order | table.sort(keys) -- as they might be out of order | ||
for i, k in ipairs(keys) do | for i, k in ipairs(keys) do | ||
out = out:gsub (''', '\''); -- prevent cs1|2 multiple names categorization; replace html entity with the actual character | |||
mw.log(i.." "..k.." "..out) | |||
mw.log(i .. " " .. k .. " " .. (out)) | |||
args = out -- author-n or editor-n | |||
if |
if args then -- name gets overwritten | ||
-- pull corresponding -link only if overwritten name is same as WD name | |||
args = link -- author-linkn or editor-linkn | |||
if link and (args == out) then | |||
args = args or link -- author-linkn or editor-linkn | |||
end | |||
else -- name does not get overwritten, so pull name from WD | |||
args = out | |||
if link then | |||
args = args or link -- author-linkn or editor-linkn | |||
end | |||
end | end | ||
end | end | ||
end | end | ||
-- gets language codes used for a monolingual text property as a table | |||
function p._getLangOfProp(qid, pid) | |||
if not pid then return {} end | |||
local out = {} | |||
local props = mw.wikibase.getAllStatements(qid, pid) | |||
for i, v in ipairs(props) do | |||
if v.mainsnak.datatype == "monolingualtext" and v.mainsnak.datavalue then | |||
out = v.mainsnak.datavalue.value.language | |||
end | |||
end | |||
return out | |||
end | |||
function p.getLangOfProp(frame) | |||
local pid = frame.args.pid or mw.text.trim(frame.args or "") | |||
if pid == "" then return end | |||
local qid = frame.args.qid | |||
if qid == "" then qid = nil end | |||
return table.concat(p._getLangOfProp(qid, pid), ", ") | |||
end | |||
-- gets the language codes of a Wikidata entry as a table | |||
--[[-------------------------< C I T E _ Q >------------------------------------------------------------------ | |||
local function _lang_code(qid) | |||
local lc = getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P424", ps = 1} ) | |||
if lc then return mw.text.split( lc, "+" ) end | |||
lc = getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P218", ps = 1} ) | |||
if lc then return mw.text.split( lc, "+" ) end | |||
return p._getLangOfProp(qid, "P1476") | |||
end | |||
function p.lang_code(frame) | |||
return table.concat(_lang_code(frame.args.qid or mw.text.trim(frame.args or "")), ", ") | |||
end | |||
-- export for debug | |||
Takes standard cs1|2 template parameters and passes all to {{citation}}. If neither of |author= and |author1= | |||
function p.getPropOfProp(frame) | |||
are set, calls get_authors() to try to get an author name-list from wikidata. The result is passed to | |||
return getPropOfProp(frame.args) | |||
end | |||
-- wraps a string in nowiki unless disable flag is set | |||
local function wrap_nowiki(str, disable) | |||
if disable then return str or '' end | |||
return mw.text.nowiki(str or '') | |||
end | |||
-- sort sequence table whose values are key-value pairs by key | |||
local function comp_key(a, b) | |||
return a < b | |||
end | |||
-- sort sequence table whose values are key-value pairs by value | |||
local function comp_val(a, b) | |||
return a < b | |||
end | |||
--[[-------------------------< C I T E _ Q >------------------------------------------------------------------ | |||
Takes standard CS1|2 template parameters and passes all to {{citation}}. If neither of |author= and |author1= | |||
are set, calls get_authors() to try to get an author name-list from Wikidata. The result is passed to | |||
{{citation}} for rendering. | {{citation}} for rendering. | ||
--]] | |||
function p._cite_q (citeq_args) | |||
local frame = mw.getCurrentFrame() | |||
-- parameters that don't get passed to Citation | |||
]] | |||
local expand = citeq_args.expand -- when set to anything, causes {{cite q}} to render <code><nowiki>{{citation|...}}</nowiki></code> | |||
local qid = citeq_args.qid or citeq_args | |||
local wdl = citeq_args.wdl | |||
local template = citeq_args.template | |||
citeq_args.expand = nil | |||
citeq_args = nil | |||
citeq_args.qid = nil | |||
citeq_args.wdl = nil | |||
citeq_args.template = nil | |||
-- if title supplied, flag to not read html title | |||
function citeq.cite_q (frame) | |||
local citeq_args = |
local titleforced = (citeq_args.title ~= nil) | ||
local qid; | |||
local wdl; | |||
local |
local oth = {} | ||
local args = pframe.args; -- first get parent frame arguments - these from the template call | |||
-- put the language codes into a sequential table langcodes | |||
for k, v in pairs (args) do -- copy named parameters and their values into citeq_args | |||
local langcodes = {} | |||
if type( k ) == 'string' then -- numbered parameters ignored | |||
if citeq_args.language then | |||
citeq_args = v; | |||
-- check these are a supported language codes | |||
for lc in mw.text.gsplit( citeq_args.language, "+", false ) do | |||
langcodes = mw.language.isSupportedLanguage(citeq_args.language) and citeq_args.language | |||
end | end | ||
end | end | ||
if not langcodes then | |||
-- try to find language of work | |||
args = frame.args; -- now get frame arguments (from the template wikisource) | |||
langcodes = _lang_code(qid) | |||
end | |||
if not langcodes then | |||
-- try fallback to journal's language | |||
local journal_qid = followQid({qid = qid, props = "P1433"}) | |||
langcodes = journal_qid and _lang_code(journal_qid) | |||
end | |||
citeq_args.language = citeq_args.language or table.concat(langcodes, ", ") | |||
|
-- loop through list of simple properties and get their values in citeq_args | ||
for name, data in pairs(simple_properties) do | |||
if 'qid' == k then -- don't copy qid | |||
citeq_args = getValue( {data.id, fwd = "ALL", osd = "no", noicon = "true", qid = qid, maxvals = data.maxvals, linked = data.linked, rank = data.rank or "best", citeq_args } ) | |||
qid = v; -- save its value | |||
if data.populate_from_journal then | |||
elseif 'wdlinks' == k then -- don't copy wdlinks | |||
local publishedin = getValue( {"P1433", ps = 1, qid = qid, maxvals = 0, citeq_args, qual = data.id, qualsonly = 'yes'} ) | |||
wdl = v; -- save its value | |||
citeq_args = publishedin or getPropOfProp({qid = qid, prop1 = "P1433", prop2 = data.id, maxvals = data.maxvals, ps = 1}) | |||
end | |||
if citeq_args and citeq_args:find(']', 1, true) then | |||
-- try fallback to work's native language | |||
citeq_args = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = langcodes } ) | |||
if citeq_args:find('^Q%d+$') then -- qid was returned | |||
-- try fallback to qid's native language | |||
local qid_languages = _lang_code(citeq_args) | |||
citeq_args = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = qid_languages } ) | |||
if citeq_args:find('^Q%d+$') then -- qid was returned again | |||
citeq_args = nil | |||
else | |||
-- record the language found if no lang specified | |||
citeq_args.language = citeq_args.language or qid_languages | |||
end | |||
end | |||
end | |||
if data.others then | |||
oth = citeq_args and (name:gsub("^%l", string.upper) .. ": " .. citeq_args) | |||
citeq_args = nil | |||
end | |||
end | |||
citeq_args.others = citeq_args.others or table.concat(oth, ". ") | |||
if citeq_args.others == "" then | |||
citeq_args.others = nil | |||
end | |||
citeq_args.journal = citeq_args.journal and citeq_args.journal:gsub("^''", ""):gsub("''$", ""):gsub("|''", "|"):gsub("'']]", "]]") | |||
citeq_args.ol = (getValue( {"P648", ps = 1, qid = qid, maxvals = 1, citeq_args.ol } ) or ''):gsub("^OL(.+)$", "%1") | |||
if citeq_args.ol == "" then | |||
citeq_args.ol = nil | |||
end | |||
-- TBD. Take care of |ol-access=? | |||
citeq_args.biorxiv = citeq_args.biorxiv and ("10.1101/" .. citeq_args.biorxiv) | |||
citeq_args.isbn = getValue( {"P957", ps = 1, qid = qid, maxvals = 1, rank="best", citeq_args.isbn } ) -- try ISBN 10 (only one value accepted) | |||
-- if url then see if there's an archive: citeq_args.url | |||
local url | |||
if not citeq_args.url then | |||
for i, pr in ipairs( {"P953", "P856", "P2699"} ) do | |||
url = getValue( {pr, ps = 1, qid = qid, maxvals = 1, qual="P1065" } ) | |||
if url then | |||
citeq_args.url = mw.text.split( url, " (", true ) | |||
local arcurl = mw.ustring.match( url, " %((.*)%)" ) -- when there is an archive url, <url> holds: url<space>(archive url); here extract the archive url if present | |||
if arcurl then | |||
local arcy, arcm, arcd = arcurl:match("(20%d%d)%p?(%d%d)%p?(%d%d)") | |||
if arcy and arcm and arcd then | |||
citeq_args = arcurl | |||
citeq_args = tonumber(arcd) .. " " .. i18n.months .. " " .. arcy | |||
end | |||
end | |||
break | |||
end | |||
end | |||
end | |||
if citeq_args.publisher == "Unknown" then -- look for "stated as" (P1932) | |||
local stated_as = getValue( {"P123", ps = 1, qid = qid, maxvals = 1, qual="P1932", qo="y"} ) | |||
if stated_as then citeq_args.publisher = stated_as end | |||
end | |||
if not titleforced then | |||
-- Handle subtitle. | |||
if citeq_args.title then | |||
local subtitle = mw.wikibase.getBestStatements (qid, 'P1680'); | |||
if 0 ~= #subtitle then | |||
subtitle = subtitle.mainsnak.datavalue.value.text; | |||
citeq_args.title = citeq_args.title .. ": " .. subtitle | |||
end | |||
end | |||
local htmltitle = getValue( {"P1476", qual = "P6833", ps = 1, qid = qid, maxvals = 1, qo = "y"} ) | |||
if htmltitle then | |||
citeq_args.title = htmltitle:gsub("</?i>", "''") | |||
else | else | ||
local title_display = citeq_args.title | |||
citeq_args = v -- but copy everything else | |||
or mw.wikibase.getLabel(qid) | |||
or (langcodes and mw.wikibase.getLabelByLang(qid, langcodes)) | |||
or ("No label or title -- debug: " .. qid) | |||
if citeq_args.url then | |||
citeq_args.title = wrap_nowiki(title_display) | |||
else | |||
local slink = mw.wikibase.getSitelink(qid) | |||
local slink_flag = false | |||
local wrap_title = '' | |||
local wslink = false | |||
if not slink then | |||
-- See if we have wikisource | |||
if not citeq_args.url then | |||
local wikisource_sitelink = mw.wikibase.getSitelink(qid, "enwikisource") or nil | |||
if wikisource_sitelink then | |||
slink = ':s:'..wikisource_sitelink | |||
wslink = true | |||
end | |||
end | |||
end | |||
if citeq_args.title then | |||
if slink then | |||
wrap_title = wrap_nowiki(citeq_args.title) | |||
slink_flag = true | |||
else | |||
citeq_args.title = wrap_nowiki(citeq_args.title) | |||
end | |||
else | |||
if slink and not wslink then | |||
if slink:lower() == title_display:lower() then | |||
citeq_args.title = ']' | |||
else | |||
wrap_title = wrap_nowiki(slink:gsub("%s%(.+%)$", ""):gsub(",.+$", "")) | |||
slink_flag = true | |||
end | |||
elseif wslink then | |||
wrap_title = wrap_nowiki(title_display) | |||
slink_flag = true | |||
else | |||
citeq_args.title = wrap_nowiki(title_display) | |||
end | |||
end | |||
if slink_flag then | |||
if slink == wrap_title and not wslink then -- direct link | |||
citeq_args.title = ']' | |||
else -- piped link | |||
citeq_args.title = ']' | |||
end | |||
end | |||
end | |||
end | |||
end | |||
-- TBD: incorporate |at, |sheets= and |sheet= here as well | |||
-- Sort out what should happen if several of them are given at the same time | |||
if citeq_args.page or citeq_args.p then -- let single take precedence over multiple | |||
citeq_args.pages = nil | |||
citeq_args.pp = nil | |||
end | |||
if citeq_args.pages then | |||
local _, count = string.gsub(citeq_args.pages, "%d+", "") | |||
if count == 1 then | |||
citeq_args.page = citeq_args.pages | |||
citeq_args.pages = nil | |||
end | end | ||
end | end | ||
if is_set (qid) then | if is_set (qid) then | ||
if not is_set (citeq_args.author) and not is_set (citeq_args.author1) |
if not is_set (citeq_args.author) and not is_set (citeq_args.author1) | ||
|
and not is_set (citeq_args.subject) and not is_set (citeq_args.subject1) | ||
and not is_set (citeq_args.host) and not is_set (citeq_args.host1) | |||
and not is_set (citeq_args.last) and not is_set (citeq_args.last1) | |||
and not is_set (citeq_args.surname) and not is_set (citeq_args.surname1) | |||
and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) | |||
and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) then -- if neither are set, try to get authors from Wikidata | |||
get_name_list ('author', citeq_args, qid, wdl) -- modify citeq_args table with authors from Wikidata | |||
end | end | ||
if not is_set (citeq_args.editor) and not is_set (citeq_args.editor1) |
if not is_set (citeq_args.editor) and not is_set (citeq_args.editor1) | ||
and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) | |||
get_name_list ('editor', citeq_args, qid, wdl); -- modify citeq_args table with editors from wikidata | |||
and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) then -- if neither are set, try to get editors from Wikidata | |||
get_name_list ('editor', citeq_args, qid, wdl) -- modify citeq_args table with editors from Wikidata | |||
end | |||
if not is_set (citeq_args.translator) and not is_set (citeq_args.translator1) | |||
and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) | |||
and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) then -- if neither are set, try to get translators from Wikidata | |||
get_name_list ('translator', citeq_args, qid, wdl) -- modify citeq_args table with translators from Wikidata | |||
end | end | ||
end | end | ||
for k, v in pairs(citeq_args) do | |||
return frame:expandTemplate{title = 'citation', args = citeq_args}; -- render the citation | |||
if in_array (v, {'(())', 'unset', 'ignore'}) or 'string' ~= type(k) then -- empty accept-as-is-written (()) markup to indicate an empty/unused parameter value, other ((...)) markups are deliberately passed down to {{citation}} | |||
citeq_args = nil | |||
elseif in_array (v, {'((unset))', '((ignore))'}) then -- strip off markup for free-text values clashing with local keywords | |||
citeq_args = 'unset' | |||
end | |||
end | |||
local author_count = 0 | |||
for k, v in pairs(citeq_args) do | |||
if k:find("^author%d+$") then | |||
author_count = author_count + 1 | |||
end | |||
end | |||
if author_count > 8 then -- convention in astronomy journals, optional mode for this? | |||
if 'all' == citeq_args then | |||
citeq_args = nil; -- unset because no longer needed | |||
else | |||
citeq_args = citeq_args or 3 -- limit to three displayed names | |||
end | |||
end | |||
local editor_count = 0 | |||
for k, v in pairs(citeq_args) do | |||
if k:find("^editor%d+$") then | |||
editor_count = editor_count + 1 | |||
end | |||
end | |||
if editor_count > 8 then -- convention in astronomy journals, optional mode for this? | |||
if 'all' == citeq_args then | |||
citeq_args = nil; -- unset because no longer needed | |||
else | |||
citeq_args = citeq_args or 3 -- limit to three displayed names | |||
end | |||
end | |||
-- change edition to ordinal if it's set and numeric | |||
citeq_args.edition = citeq_args.edition and p.makeOrdinal(citeq_args.edition) | |||
-- code to make a guess what template to use from the supplied parameters | |||
-- (first draft for proof-of-concept) | |||
if citeq_args.isbn then | |||
template = template or "book" | |||
citeq_args.asin = nil -- suppress ASIN if ISBN exists | |||
elseif citeq_args.journal then | |||
template = template or "journal" | |||
elseif citeq_args.website then | |||
template = template or "web" | |||
end | |||
-- template is CS1 designator: journal, web, news, etc. | |||
if template then | |||
-- citeq_args.mode = citeq_args.mode or "cs1" -- a cs1 template already knows that it is cs1 so this line is superfluous | |||
template = "Cite " .. template | |||
else | |||
-- citeq_args.mode = citeq_args.mode or "cs2" -- a cs2 template already knows that it is cs2 so this line is superfluous | |||
template = "Citation" | |||
end | |||
-- |id= could hold more than one identifier pulled from Wikidata not supported by {{citation}}, right now only add our qid to the list | |||
local list_sep = '. ' | |||
if citeq_args.mode ~= 'cs1' then | |||
list_sep = ', ' | |||
end | |||
local id = '] ]' -- go through "WDQ (identifier)" redirect to reduce clutter in "What links here" and improve reverse lookup. Keep in sync with {{QID}}. | |||
local old_id = citeq_args.id | |||
if wdl then -- show WD logo | |||
id = id .. ']' -- possibly replace by WD edit icon? | |||
end | |||
if is_set (old_id) then | |||
citeq_args.id = old_id .. list_sep .. id -- append to user-specified contents | |||
else | |||
citeq_args.id = id | |||
end | |||
-- clean up any blank parameters | |||
for k, v in pairs(citeq_args) do | |||
if v == "" then citeq_args = nil end | |||
end | |||
-- if |expand=<anything>, write a nowiki'd version to see what the {{citation}} template call looks like | |||
if expand then | |||
local expand_args = { "{{" .. template } -- init with citation template | |||
if expand == "self" then | |||
citeq_args.id = old_id -- restore original |id= parameter | |||
expand_args = { "{{cite Q|" .. qid } -- expand to itself | |||
end | |||
-- make a sortable table and sort it by param name | |||
local sorttable = {} | |||
for param, val in pairs (citeq_args) do | |||
table.insert(sorttable, {param, val}) | |||
end | |||
table.sort(sorttable, comp_key) | |||
-- add contents to expand_args | |||
for idx, val in ipairs(sorttable) do | |||
table.insert(expand_args, val .. '=' .. val) | |||
end | |||
-- make the nowiki'd string and done | |||
return frame:preprocess (table.concat ({'<syntaxhighlight lang="wikitext" inline="1">', table.concat (expand_args, ' |') .. '}}', '</syntaxhighlight>'})); | |||
end | |||
local erratumid = getPropertyIDs( { "P2507", qid = qid, fwd = "ALL", osd = "no", rank = "best", maxvals = 1 } ) | |||
if erratumid then | |||
erratumid = " ]" .. "]" | |||
else | |||
erratumid = "" | |||
end | |||
local opt_cat = '' | |||
if getValue( {"P5824", ps = 1, qid = qid} ) then | |||
opt_cat = ']<!-- retracted -->' | |||
end | |||
if getValue( {"P1366", ps = 1, qid = qid} ) then | |||
opt_cat = opt_cat .. ']<!-- replaced -->' | |||
end | |||
return frame:expandTemplate{title = template, args = citeq_args} .. erratumid .. opt_cat -- render the template | |||
end | |||
function p.cite_q (frame) | |||
local args = {} | |||
for k, v in pairs(frame:getParent().args) do | |||
if v ~= "" then args = v end | |||
end | |||
for k, v in pairs(frame.args) do | |||
if v ~= "" then args = v end | |||
end | |||
args.qid = args.qid or args or "" | |||
if args.qid == "" then return nil end | |||
args = nil | |||
local citesep = (args.citesep or "") | |||
if citesep == "" then citesep = ", " end | |||
citesep = citesep:gsub('"', '') -- strip double quotes after setting default to allow |citesep="" as a blank separator | |||
args.citesep = nil | |||
local tag = args.tag or "" | |||
if tag == "" then tag = nil end | |||
args.tag = nil | |||
local list = args.list or "" | |||
if list == "" then list = nil end | |||
args.list = nil | |||
args.language = args.language or args.lang | |||
args.lang = nil | |||
local cites = {} | |||
for q in args.qid:gmatch("Q%d+") do | |||
-- make a new copy of the arguments | |||
local newargs = {} | |||
for k, v in pairs(args) do | |||
if k ~= "qid" then | |||
newargs = v | |||
end | |||
end | |||
newargs.qid = q | |||
if tag == "ref" then | |||
cites = frame:callParserFunction{ name = "#tag:ref", args = { p._cite_q(newargs), name = q } } | |||
-- expand like this: args = { p._cite_q(newargs), name = 'foo', group = 'bar' } | |||
else | |||
cites = p._cite_q(newargs) | |||
end | |||
end | |||
if list then | |||
return frame:expandTemplate{ title = list, args = cites } | |||
else | |||
return table.concat(cites, citesep) | |||
end | |||
end | end | ||
return |
return p |
Latest revision as of 13:32, 5 July 2024
Module documentation[view] [edit] [history] [purge]This Lua module is used on approximately 53,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
Implements {{Cite Q}}
Test cases at:
The above documentation is transcluded from Module:Cite Q/doc. (edit | history)Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Subpages of this module.
-- Version: 2021-10-19 local p = {} require('strict') local wdib = require('Module:WikidataIB') local getValue = wdib._getValue local getPropOfProp = wdib._getPropOfProp local followQid = wdib._followQid local getPropertyIDs = wdib._getPropertyIDs local i18n = { = mw.wikibase.getLabel("Q4233718"):gsub("^%l", mw.ustring.upper), = "]", = { = "st", = "nd", = "rd", = "th" }, = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }, } ------------------------------------------------------------------------------- -- makeOrdinal needs to be internationalised along with the above i18n -- takes cardinal number as a numeric and returns the ordinal as a string -- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc. ------------------------------------------------------------------------------- p.makeOrdinal = function(cardinal) local card = tonumber(cardinal) if not card then return cardinal end local ordsuffix = i18n.ordinal.default if card % 10 == 1 then ordsuffix = i18n.ordinal elseif card % 10 == 2 then ordsuffix = i18n.ordinal elseif card % 10 == 3 then ordsuffix = i18n.ordinal end -- In English, 1, 21, 31, etc. use 'st', but 11, 111, etc. use 'th' -- similarly for 12 and 13, etc. if (card % 100 == 11) or (card % 100 == 12) or (card % 100 == 13) then ordsuffix = i18n.ordinal.default end return card .. ordsuffix end -- Table of simple properties that can be fetched in roughly the same way: -- id = PXXX -- maxvals = maximum number of multiple values (0 for all) -- linked = "no" suppresses linking -- populate_from_journal = true/false determines whether to look in a journal where the source is published -- rank = "best", "preferred", normal, etc. determines how Wikidata ranks are treated -- others = true - the value for the property goes to "others" section local simple_properties = { publisher = {id = "P123", maxvals = 1}, oclc = {id = "P243", maxvals = 1}, = {id = "P291", maxvals = 0, linked = 'no'}, -- publication place (don't put into |place=; is treated specially in {{citation}} if both are given) doi = {id = "P356", maxvals = 1}, -- take care of |doi-broken-date= (WD "reason for deprecation"/"stated as") and |doi-access= (WD "access status")? issue = {id = "P433", maxvals = 0, populate_from_journal = true}, -- distinguish from |number= ("P1545"?) if both are given (still blocked by {{citation}}, but will be supported in the future) pmid = {id = "P698", maxvals = 1}, -- gbooks = {id = "P675", maxvals = 1}, -- to be added to {{citation}} -- ia = {id = "P724", maxvals = 1}, -- to be added to {{citation}} arxiv = {id = "P818", maxvals = 1}, bibcode = {id = "P819", maxvals = 1}, -- take care of |bibcode-access=? jstor = {id = "P888", maxvals = 1}, -- take care of |jstor-access=? mr = {id = "P889", maxvals = 1}, rfc = {id = "P892", maxvals = 1}, zbl = {id = "P894", maxvals = 1}, ssrn = {id = "P893", maxvals = 1}, place = {id = "P1071", maxvals = 0, linked = 'no'}, -- written-at place -- = {id = "P1104", maxvals = 0, linked = 'no'}, -- to be added to {{citation}} / COinS &rft.tpages= -- coden = {id = "P1159", maxvals = 1}, -- to be added to {{citation}} / COinS &rft.coden= s2cid = {id = "P8299", maxvals = 1}, -- take care of |s2cid-access=? pmc = {id = "P932", maxvals = 1}, -- take care of |pmc-embargo-date= (WD "reason for deprecation")? lccn = {id = "P1144", maxvals = 1}, hdl = {id = "P1184", maxvals = 1}, -- take care of |hdl-access=? ismn = {id = "P1208", maxvals = 1}, journal = {id = "P1433", maxvals = 1}, citeseerx = {id = "P3784", maxvals = 1}, osti = {id = "P3894", maxvals = 1}, -- take care of |osti-access=? biorxiv = {id = "P3951", maxvals = 1}, asin = {id = "P5749", maxvals = 1}, -- What about |asin-tld=? (WD examples resolve to .com at present, but may change) -- = {id = "P528", maxvals = 0}, -- to be added to {{citation}} / COinS &rft.artnum= isbn = {id = "P212", maxvals = 1, populate_from_journal = true}, -- ISBN 13 issn = {id = "P236", maxvals = 1, populate_from_journal = true}, -- distinguish from |eissn= for electronic issues? -- jfm = {id = "P?", maxvals = 1}, -- Jahrbuch über die Fortschritte der Mathematik (not Zbl) -- sbn = {id = "P?", maxvals = 1}, -- Standard Book Number (predecessor of ISBN, not ICCU) -- message-id = {id = "P?", maxvals = 1}, -- Usenet message ID chapter = {id = "P792", maxvals = 1}, = {id = "P577", maxvals = 1, populate_from_journal = true}, -- publication date (don't use |date=; is treated specially in {{citation}} if both are given.) series = {id = "P179", maxvals = 1, populate_from_journal = true}, version = {id = "P348", maxvals = 0}, edition = {id = "P393", maxvals = 0}, volume = {id = "P478", maxvals = 0, populate_from_journal = true}, -- part = {id = "P1545"?, maxvals = 0}, -- to be added to {{citation}} / COinS &rft.part= title = {id = "P1476", rank="p n"}, -- url = {id = "P953", maxvals = 1}, -- deal with this along with archive-url pages = {id = "P304", maxvals = 0, populate_from_journal = true}, at = {id = "P958", maxvals = 0, populate_from_journal = true}, -- also incorporate lines (P7421) and columns (P3903) into this (cite map also supports |section=) -- sheets = {id = "P7416", maxvals = 0, populate_from_journal = true}, -- interviewer = {id = "P?", maxvals = 0}, -- does **not** go to "others" section! Multiple interviewers should be n-enumerated illustrator = {id = "P110", maxvals = 10, others = true}, -- goes to "others" section -- foreword and afterword, when contributions to another author's work, are contributions so belong in |contribution=; -- the writer's name goes in |contributor=; requires |title= and |author= -- However, this might need to add support for multiple contributors and their roles to {{citation}}, see Help_talk:Citation_Style_1#Others -- foreword = {id = "P2679", maxvals = 10, others = true}, -- goes to "others" section -- afterword = {id = "P2680", maxvals = 10, others = true}, -- goes to "others" section composer = {id = "P86", maxvals = 10, others = true}, -- goes to "others" section animator = {id = "P6942", maxvals = 10, others = true}, -- goes to "others" section director = {id = "P57", maxvals = 10, others = true}, -- goes to "others" section screenwriter = {id = "P58", maxvals = 10, others = true}, -- goes to "others" section signatory = {id = "P1891", maxvals = 10, others = true}, -- goes to "others" section presenter = {id = "P371", maxvals = 10, others = true}, -- goes to "others" section performer = {id = "P175", maxvals = 10, others = true}, -- goes to "others" section } --[[--------------------------< I S _ S E T >-------------------------------------------------------------- Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string. ]] local function is_set( var ) return not (var == nil or var == '') end --[[--------------------------< I N _ A R R A Y >-------------------------------------------------------------- Whether needle is in haystack (taken from Module:Citation/CS1/Utilities) ]] local function in_array( needle, haystack ) if needle == nil then return false end for n, v in ipairs( haystack ) do if v == needle then return n end end return false end --[[--------------------------< A C C E P T _ V A L U E >------------------------------------------------------- Accept WD value by framing in ((...)) if param_val is equal to keyword; else pass-through WD value as is. ]] local function accept_value( param_val, wd_val ) local val = param_val if val then if in_array (val, {'accept', '))((', ':d:'}) then val = '((' .. wd_val .. '))' elseif '((accept))' == val then val = 'accept' elseif '(())(())' == val then val = '))((' elseif '((:d:))' == val then val = ':d:' else val = wd_val end end return val end -- function to fetch a value to display local function makelink(v, out, link, maxpos, wdl) local label if v.mainsnak.snaktype == "value" then if v.mainsnak.datatype == "wikibase-item" then local qnumber = v.mainsnak.datavalue.value.id local sitelink = mw.wikibase.getSitelink(qnumber) if qnumber == "Q2818964" then sitelink = nil end -- suppress link to "Various authors" if v.qualifiers and v.qualifiers.P1932 then label = v.qualifiers.P1932.datavalue.value else label = mw.wikibase.getLabel(qnumber) if label then label = mw.text.nowiki(label) else label = qnumber -- should add tracking category end end local position = maxpos + 1 -- Default to 'next' author. -- use P1545 (series ordinal) instead of default position. if v and v.qualifiers and v.qualifiers then position = tonumber(v.qualifiers.datavalue.value) end maxpos = math.max(maxpos, position) if sitelink then -- just the plain name, -- but keep a record of the links, using the same index out = label link = sitelink else if wdl then -- show that there's a Wikidata entry available out = " .. "|" .. label .. "]] <span title='" .. i18n .. "'>]</span>" else -- no Wikidata links wanted, so just give the plain label out = label end end elseif v.mainsnak.datatype == "string" then local position = maxpos + 1 -- Default to 'next' author. -- use P1545 (series ordinal) instead of default position. if v and v.qualifiers and v.qualifiers then position = tonumber(v.qualifiers.datavalue.value) end maxpos = math.max(maxpos, position) out = v.mainsnak.datavalue.value else -- not a wikibase-item or a string! end else -- code here if we want to return something when author is "unknown" if v.qualifiers and v.qualifiers.P1932 then label = v.qualifiers.P1932.datavalue.value else label = i18n .. (i18n or "") end maxpos = maxpos + 1 out = label end return maxpos end --[=[-------------------------< G E T _ N A M E _ L I S T >---------------------------------------------------- get_name_list -- adapted from getAuthors code taken from Module:RexxS arguments: nl_type - type of name list to fetch: nl_type = 'author' for authors; 'editor' for editors; 'translator' for translators args - pointer to the parameter arguments table from the template call qid - value from |qid= parameter; the Q-id of the source (book, etc.) in qid wdl - value from the |wdl= parameter; a Boolean passed to enable links to Wikidata when no article exists returns nothing; modifies the args table ]=] local function get_name_list (nl_type, args, qid, wdl) local propertyID = "P50" local fallbackID = "P2093" -- author name string if nl_type =="author" then propertyID = 'P50' -- for authors fallbackID = 'P2093' -- author-string elseif nl_type =="editor" then propertyID = 'P5769' -- "editor-in-chief" fallbackID = 'P98' -- for editors - So-called "fallbacks" are actually a second set of properties processed -- TBD. Take book series editors into account as well (if they have a separate P code as well)? elseif nl_type == "translator" then propertyID = 'P655' -- for translators fallbackID = nil -- elseif 'contributor' == nl_type then -- f.e. author of forewords (P2679) and afterwords (P2680); requires |contribution=, |title= and |author= -- propertyID = 'P' -- for contributors -- fallbackID = nil else return -- not specified so return end -- wdl is a Boolean passed to enable links to Wikidata when no article exists -- if "false" or "no" or "0" is passed set it false -- if nothing or an empty string is passed set it false if wdl and (#wdl > 0) then wdl = wdl:lower() wdl = in_array (wdl, {"false", "no", "0"}) else -- wdl is empty, so wdl = false end local props = nil local fallback = nil if mw.wikibase.entityExists(qid) then props = mw.wikibase.getAllStatements(qid, propertyID) if props and fallbackID then fallback = mw.wikibase.getAllStatements(qid, fallbackID) end end -- Make sure it actually has at least one of the properties requested if not (props and props) and not (fallback and fallback) then return nil end -- So now we have something to return: -- table 'out' is going to store the names(s): -- and table 'link' will store any links to the name's article local out = {} local link = {} local maxpos = 0 if props and props then for k, v in pairs(props) do maxpos = makelink(v, out, link, maxpos, wdl) end end if fallback and fallback then -- second properties for k, v in pairs(fallback) do maxpos = makelink(v, out, link, maxpos, wdl) end end -- if there's anything to return, then insert the additions in the template arguments table -- in the form |author1=firstname secondname |author2= ... -- Renumber, in case we have inconsistent numbering local keys = {} for k, v in pairs(out) do keys = k end table.sort(keys) -- as they might be out of order for i, k in ipairs(keys) do out = out:gsub (''', '\''); -- prevent cs1|2 multiple names categorization; replace html entity with the actual character mw.log(i .. " " .. k .. " " .. (out)) if args then -- name gets overwritten -- pull corresponding -link only if overwritten name is same as WD name if link and (args == out) then args = args or link -- author-linkn or editor-linkn end else -- name does not get overwritten, so pull name from WD args = out if link then args = args or link -- author-linkn or editor-linkn end end end end -- gets language codes used for a monolingual text property as a table function p._getLangOfProp(qid, pid) if not pid then return {} end local out = {} local props = mw.wikibase.getAllStatements(qid, pid) for i, v in ipairs(props) do if v.mainsnak.datatype == "monolingualtext" and v.mainsnak.datavalue then out = v.mainsnak.datavalue.value.language end end return out end function p.getLangOfProp(frame) local pid = frame.args.pid or mw.text.trim(frame.args or "") if pid == "" then return end local qid = frame.args.qid if qid == "" then qid = nil end return table.concat(p._getLangOfProp(qid, pid), ", ") end -- gets the language codes of a Wikidata entry as a table local function _lang_code(qid) local lc = getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P424", ps = 1} ) if lc then return mw.text.split( lc, "+" ) end lc = getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P218", ps = 1} ) if lc then return mw.text.split( lc, "+" ) end return p._getLangOfProp(qid, "P1476") end function p.lang_code(frame) return table.concat(_lang_code(frame.args.qid or mw.text.trim(frame.args or "")), ", ") end -- export for debug function p.getPropOfProp(frame) return getPropOfProp(frame.args) end -- wraps a string in nowiki unless disable flag is set local function wrap_nowiki(str, disable) if disable then return str or '' end return mw.text.nowiki(str or '') end -- sort sequence table whose values are key-value pairs by key local function comp_key(a, b) return a < b end -- sort sequence table whose values are key-value pairs by value local function comp_val(a, b) return a < b end --[[-------------------------< C I T E _ Q >------------------------------------------------------------------ Takes standard CS1|2 template parameters and passes all to {{citation}}. If neither of |author= and |author1= are set, calls get_authors() to try to get an author name-list from Wikidata. The result is passed to {{citation}} for rendering. --]] function p._cite_q (citeq_args) local frame = mw.getCurrentFrame() -- parameters that don't get passed to Citation local expand = citeq_args.expand -- when set to anything, causes {{cite q}} to render <code><nowiki>{{citation|...}}</nowiki></code> local qid = citeq_args.qid or citeq_args local wdl = citeq_args.wdl local template = citeq_args.template citeq_args.expand = nil citeq_args = nil citeq_args.qid = nil citeq_args.wdl = nil citeq_args.template = nil -- if title supplied, flag to not read html title local titleforced = (citeq_args.title ~= nil) local oth = {} -- put the language codes into a sequential table langcodes local langcodes = {} if citeq_args.language then -- check these are a supported language codes for lc in mw.text.gsplit( citeq_args.language, "+", false ) do langcodes = mw.language.isSupportedLanguage(citeq_args.language) and citeq_args.language end end if not langcodes then -- try to find language of work langcodes = _lang_code(qid) end if not langcodes then -- try fallback to journal's language local journal_qid = followQid({qid = qid, props = "P1433"}) langcodes = journal_qid and _lang_code(journal_qid) end citeq_args.language = citeq_args.language or table.concat(langcodes, ", ") -- loop through list of simple properties and get their values in citeq_args for name, data in pairs(simple_properties) do citeq_args = getValue( {data.id, fwd = "ALL", osd = "no", noicon = "true", qid = qid, maxvals = data.maxvals, linked = data.linked, rank = data.rank or "best", citeq_args } ) if data.populate_from_journal then local publishedin = getValue( {"P1433", ps = 1, qid = qid, maxvals = 0, citeq_args, qual = data.id, qualsonly = 'yes'} ) citeq_args = publishedin or getPropOfProp({qid = qid, prop1 = "P1433", prop2 = data.id, maxvals = data.maxvals, ps = 1}) end if citeq_args and citeq_args:find(']', 1, true) then -- try fallback to work's native language citeq_args = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = langcodes } ) if citeq_args:find('^Q%d+$') then -- qid was returned -- try fallback to qid's native language local qid_languages = _lang_code(citeq_args) citeq_args = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = qid_languages } ) if citeq_args:find('^Q%d+$') then -- qid was returned again citeq_args = nil else -- record the language found if no lang specified citeq_args.language = citeq_args.language or qid_languages end end end if data.others then oth = citeq_args and (name:gsub("^%l", string.upper) .. ": " .. citeq_args) citeq_args = nil end end citeq_args.others = citeq_args.others or table.concat(oth, ". ") if citeq_args.others == "" then citeq_args.others = nil end citeq_args.journal = citeq_args.journal and citeq_args.journal:gsub("^''", ""):gsub("''$", ""):gsub("|''", "|"):gsub("'']]", "]]") citeq_args.ol = (getValue( {"P648", ps = 1, qid = qid, maxvals = 1, citeq_args.ol } ) or ''):gsub("^OL(.+)$", "%1") if citeq_args.ol == "" then citeq_args.ol = nil end -- TBD. Take care of |ol-access=? citeq_args.biorxiv = citeq_args.biorxiv and ("10.1101/" .. citeq_args.biorxiv) citeq_args.isbn = getValue( {"P957", ps = 1, qid = qid, maxvals = 1, rank="best", citeq_args.isbn } ) -- try ISBN 10 (only one value accepted) -- if url then see if there's an archive: citeq_args.url local url if not citeq_args.url then for i, pr in ipairs( {"P953", "P856", "P2699"} ) do url = getValue( {pr, ps = 1, qid = qid, maxvals = 1, qual="P1065" } ) if url then citeq_args.url = mw.text.split( url, " (", true ) local arcurl = mw.ustring.match( url, " %((.*)%)" ) -- when there is an archive url, <url> holds: url<space>(archive url); here extract the archive url if present if arcurl then local arcy, arcm, arcd = arcurl:match("(20%d%d)%p?(%d%d)%p?(%d%d)") if arcy and arcm and arcd then citeq_args = arcurl citeq_args = tonumber(arcd) .. " " .. i18n.months .. " " .. arcy end end break end end end if citeq_args.publisher == "Unknown" then -- look for "stated as" (P1932) local stated_as = getValue( {"P123", ps = 1, qid = qid, maxvals = 1, qual="P1932", qo="y"} ) if stated_as then citeq_args.publisher = stated_as end end if not titleforced then -- Handle subtitle. if citeq_args.title then local subtitle = mw.wikibase.getBestStatements (qid, 'P1680'); if 0 ~= #subtitle then subtitle = subtitle.mainsnak.datavalue.value.text; citeq_args.title = citeq_args.title .. ": " .. subtitle end end local htmltitle = getValue( {"P1476", qual = "P6833", ps = 1, qid = qid, maxvals = 1, qo = "y"} ) if htmltitle then citeq_args.title = htmltitle:gsub("</?i>", "''") else local title_display = citeq_args.title or mw.wikibase.getLabel(qid) or (langcodes and mw.wikibase.getLabelByLang(qid, langcodes)) or ("No label or title -- debug: " .. qid) if citeq_args.url then citeq_args.title = wrap_nowiki(title_display) else local slink = mw.wikibase.getSitelink(qid) local slink_flag = false local wrap_title = '' local wslink = false if not slink then -- See if we have wikisource if not citeq_args.url then local wikisource_sitelink = mw.wikibase.getSitelink(qid, "enwikisource") or nil if wikisource_sitelink then slink = ':s:'..wikisource_sitelink wslink = true end end end if citeq_args.title then if slink then wrap_title = wrap_nowiki(citeq_args.title) slink_flag = true else citeq_args.title = wrap_nowiki(citeq_args.title) end else if slink and not wslink then if slink:lower() == title_display:lower() then citeq_args.title = ']' else wrap_title = wrap_nowiki(slink:gsub("%s%(.+%)$", ""):gsub(",.+$", "")) slink_flag = true end elseif wslink then wrap_title = wrap_nowiki(title_display) slink_flag = true else citeq_args.title = wrap_nowiki(title_display) end end if slink_flag then if slink == wrap_title and not wslink then -- direct link citeq_args.title = ']' else -- piped link citeq_args.title = ']' end end end end end -- TBD: incorporate |at, |sheets= and |sheet= here as well -- Sort out what should happen if several of them are given at the same time if citeq_args.page or citeq_args.p then -- let single take precedence over multiple citeq_args.pages = nil citeq_args.pp = nil end if citeq_args.pages then local _, count = string.gsub(citeq_args.pages, "%d+", "") if count == 1 then citeq_args.page = citeq_args.pages citeq_args.pages = nil end end if is_set (qid) then if not is_set (citeq_args.author) and not is_set (citeq_args.author1) and not is_set (citeq_args.subject) and not is_set (citeq_args.subject1) and not is_set (citeq_args.host) and not is_set (citeq_args.host1) and not is_set (citeq_args.last) and not is_set (citeq_args.last1) and not is_set (citeq_args.surname) and not is_set (citeq_args.surname1) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) then -- if neither are set, try to get authors from Wikidata get_name_list ('author', citeq_args, qid, wdl) -- modify citeq_args table with authors from Wikidata end if not is_set (citeq_args.editor) and not is_set (citeq_args.editor1) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) then -- if neither are set, try to get editors from Wikidata get_name_list ('editor', citeq_args, qid, wdl) -- modify citeq_args table with editors from Wikidata end if not is_set (citeq_args.translator) and not is_set (citeq_args.translator1) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) and not is_set (citeq_args) then -- if neither are set, try to get translators from Wikidata get_name_list ('translator', citeq_args, qid, wdl) -- modify citeq_args table with translators from Wikidata end end for k, v in pairs(citeq_args) do if in_array (v, {'(())', 'unset', 'ignore'}) or 'string' ~= type(k) then -- empty accept-as-is-written (()) markup to indicate an empty/unused parameter value, other ((...)) markups are deliberately passed down to {{citation}} citeq_args = nil elseif in_array (v, {'((unset))', '((ignore))'}) then -- strip off markup for free-text values clashing with local keywords citeq_args = 'unset' end end local author_count = 0 for k, v in pairs(citeq_args) do if k:find("^author%d+$") then author_count = author_count + 1 end end if author_count > 8 then -- convention in astronomy journals, optional mode for this? if 'all' == citeq_args then citeq_args = nil; -- unset because no longer needed else citeq_args = citeq_args or 3 -- limit to three displayed names end end local editor_count = 0 for k, v in pairs(citeq_args) do if k:find("^editor%d+$") then editor_count = editor_count + 1 end end if editor_count > 8 then -- convention in astronomy journals, optional mode for this? if 'all' == citeq_args then citeq_args = nil; -- unset because no longer needed else citeq_args = citeq_args or 3 -- limit to three displayed names end end -- change edition to ordinal if it's set and numeric citeq_args.edition = citeq_args.edition and p.makeOrdinal(citeq_args.edition) -- code to make a guess what template to use from the supplied parameters -- (first draft for proof-of-concept) if citeq_args.isbn then template = template or "book" citeq_args.asin = nil -- suppress ASIN if ISBN exists elseif citeq_args.journal then template = template or "journal" elseif citeq_args.website then template = template or "web" end -- template is CS1 designator: journal, web, news, etc. if template then -- citeq_args.mode = citeq_args.mode or "cs1" -- a cs1 template already knows that it is cs1 so this line is superfluous template = "Cite " .. template else -- citeq_args.mode = citeq_args.mode or "cs2" -- a cs2 template already knows that it is cs2 so this line is superfluous template = "Citation" end -- |id= could hold more than one identifier pulled from Wikidata not supported by {{citation}}, right now only add our qid to the list local list_sep = '. ' if citeq_args.mode ~= 'cs1' then list_sep = ', ' end local id = '] ]' -- go through "WDQ (identifier)" redirect to reduce clutter in "What links here" and improve reverse lookup. Keep in sync with {{QID}}. local old_id = citeq_args.id if wdl then -- show WD logo id = id .. ']' -- possibly replace by WD edit icon? end if is_set (old_id) then citeq_args.id = old_id .. list_sep .. id -- append to user-specified contents else citeq_args.id = id end -- clean up any blank parameters for k, v in pairs(citeq_args) do if v == "" then citeq_args = nil end end -- if |expand=<anything>, write a nowiki'd version to see what the {{citation}} template call looks like if expand then local expand_args = { "{{" .. template } -- init with citation template if expand == "self" then citeq_args.id = old_id -- restore original |id= parameter expand_args = { "{{cite Q|" .. qid } -- expand to itself end -- make a sortable table and sort it by param name local sorttable = {} for param, val in pairs (citeq_args) do table.insert(sorttable, {param, val}) end table.sort(sorttable, comp_key) -- add contents to expand_args for idx, val in ipairs(sorttable) do table.insert(expand_args, val .. '=' .. val) end -- make the nowiki'd string and done return frame:preprocess (table.concat ({'<syntaxhighlight lang="wikitext" inline="1">', table.concat (expand_args, ' |') .. '}}', '</syntaxhighlight>'})); end local erratumid = getPropertyIDs( { "P2507", qid = qid, fwd = "ALL", osd = "no", rank = "best", maxvals = 1 } ) if erratumid then erratumid = " ]" .. "]" else erratumid = "" end local opt_cat = '' if getValue( {"P5824", ps = 1, qid = qid} ) then opt_cat = ']<!-- retracted -->' end if getValue( {"P1366", ps = 1, qid = qid} ) then opt_cat = opt_cat .. ']<!-- replaced -->' end return frame:expandTemplate{title = template, args = citeq_args} .. erratumid .. opt_cat -- render the template end function p.cite_q (frame) local args = {} for k, v in pairs(frame:getParent().args) do if v ~= "" then args = v end end for k, v in pairs(frame.args) do if v ~= "" then args = v end end args.qid = args.qid or args or "" if args.qid == "" then return nil end args = nil local citesep = (args.citesep or "") if citesep == "" then citesep = ", " end citesep = citesep:gsub('"', '') -- strip double quotes after setting default to allow |citesep="" as a blank separator args.citesep = nil local tag = args.tag or "" if tag == "" then tag = nil end args.tag = nil local list = args.list or "" if list == "" then list = nil end args.list = nil args.language = args.language or args.lang args.lang = nil local cites = {} for q in args.qid:gmatch("Q%d+") do -- make a new copy of the arguments local newargs = {} for k, v in pairs(args) do if k ~= "qid" then newargs = v end end newargs.qid = q if tag == "ref" then cites = frame:callParserFunction{ name = "#tag:ref", args = { p._cite_q(newargs), name = q } } -- expand like this: args = { p._cite_q(newargs), name = 'foo', group = 'bar' } else cites = p._cite_q(newargs) end end if list then return frame:expandTemplate{ title = list, args = cites } else return table.concat(cites, citesep) end end return p