1031 lines
38 KiB
Lua
1031 lines
38 KiB
Lua
require('strict')
|
||
|
||
local wdib = require('Module:WikidataIB')
|
||
local getValue = wdib._getValue
|
||
local _getPropOfProp = wdib._getPropOfProp
|
||
local followQid = wdib._followQid
|
||
--local getPropertyIDs = wdib._getPropertyIDs
|
||
local cite_cfg = mw.loadData ('Module:Cite/config')
|
||
local cfg = mw.loadData ('Module:Cite_Q/config')
|
||
local cs1_module = 'Module:Citation/CS1' -- don't load the module yet, may not be needed
|
||
local cs1_cfg = mw.loadData('Module:Citation/CS1/Configuration')
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- makeOrdinal needs to be internationalised along with cfg.i18n_t
|
||
-- 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.
|
||
--------------------------------------------------------------------------------
|
||
local makeOrdinal = function(cardinal)
|
||
local card = tonumber(cardinal)
|
||
if not card then return cardinal end
|
||
local ordsuffix = cfg.i18n_t.ordinal.default
|
||
if card % 10 == 1 then
|
||
ordsuffix = cfg.i18n_t.ordinal[1]
|
||
elseif card % 10 == 2 then
|
||
ordsuffix = cfg.i18n_t.ordinal[2]
|
||
elseif card % 10 == 3 then
|
||
ordsuffix = cfg.i18n_t.ordinal[3]
|
||
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 = cfg.i18n_t.ordinal.default
|
||
end
|
||
return card .. ordsuffix
|
||
end
|
||
|
||
--[[--------------------------< 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"
|
||
|
||
-- Last name, first name(s)
|
||
local last_name = getValue({"P734", qid=qnumber, maxvals=1, ps=2})
|
||
local first_names = getValue({"P735", qid=qnumber, maxvals=0, ps=2})
|
||
if first_names then
|
||
first_names = mw.text.trim(first_names:gsub(', ', ' '))
|
||
end
|
||
|
||
if v.qualifiers and v.qualifiers.P1932 then
|
||
label = v.qualifiers.P1932[1].datavalue.value -- "object named as"
|
||
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.qualifiers and v.qualifiers.P1545 and v.qualifiers.P1545[1] then
|
||
position = tonumber(v.qualifiers.P1545[1].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
|
||
if last_name and first_names then
|
||
out[position] = {
|
||
label = label,
|
||
last = last_name or nil,
|
||
first = first_names or nil,
|
||
}
|
||
else
|
||
out[position] = label
|
||
end
|
||
link[position] = sitelink
|
||
else
|
||
if wdl then
|
||
-- show that there's a Wikidata entry available
|
||
out[position] = string.format(
|
||
"[[:d:Q%s|%s]] <span title='%s'>[[File:Wikidata-logo.svg|16px|alt=|link=]]</span>",
|
||
v.mainsnak.datavalue.value["numeric-id"],
|
||
label,
|
||
cfg.i18n_t["errors"]["local-article-not-found"]
|
||
)
|
||
else
|
||
-- no Wikidata links wanted, so just give the plain label
|
||
if last_name and first_names then
|
||
out[position] = {
|
||
label = label,
|
||
last = last_name or nil,
|
||
first = first_names or nil,
|
||
}
|
||
else
|
||
out[position] = label
|
||
end
|
||
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.qualifiers and v.qualifiers.P1545 and v.qualifiers.P1545[1] then
|
||
position = tonumber(v.qualifiers.P1545[1].datavalue.value)
|
||
end
|
||
maxpos = math.max(maxpos, position)
|
||
|
||
-- Names from "author given names" and "author last names" qualifiers
|
||
local last_name
|
||
local first_names
|
||
if v.qualifiers and v.qualifiers.P9688 and v.qualifiers.P9688[1] then
|
||
last_name = v.qualifiers.P9688[1].datavalue.value
|
||
end
|
||
if v.qualifiers and v.qualifiers.P9687 and v.qualifiers.P9687[1] then
|
||
first_names = mw.text.trim(v.qualifiers.P9687[1].datavalue.value:gsub(', ', ' '))
|
||
end
|
||
|
||
if last_name and first_names then
|
||
out[position] = {
|
||
label = v.mainsnak.datavalue.value,
|
||
last = last_name or nil,
|
||
first = first_names or nil,
|
||
}
|
||
else
|
||
out[position] = v.mainsnak.datavalue.value
|
||
end
|
||
else
|
||
-- not a wikibase-item or a string!
|
||
end
|
||
else
|
||
-- try the "object named as" qualifier, otherwise use the unknown author item
|
||
if v.qualifiers and v.qualifiers.P1932 then
|
||
label = v.qualifiers.P1932[1].datavalue.value -- "object named as"
|
||
|
||
-- Names from "author given names" and "author last names" qualifiers
|
||
local last_name
|
||
local first_names
|
||
if v.qualifiers and v.qualifiers.P9688 and v.qualifiers.P9688[1] then
|
||
last_name = v.qualifiers.P9688[1].datavalue.value
|
||
end
|
||
if v.qualifiers and v.qualifiers.P9687 and v.qualifiers.P9687[1] then
|
||
first_names = mw.text.trim(v.qualifiers.P9687[1].datavalue.value:gsub(', ', ' '))
|
||
end
|
||
if last_name and first_names then
|
||
out[position] = {
|
||
label = label,
|
||
last = last_name or nil,
|
||
first = first_names or nil,
|
||
}
|
||
else
|
||
out[position] = label
|
||
end
|
||
else
|
||
label = mw.wikibase.getLabel("Q4233718"):gsub("^%l", mw.ustring.upper) .. (cfg.i18n_t.trackingcats["unknown-author"] or "")
|
||
end
|
||
maxpos = maxpos + 1
|
||
out[maxpos] = 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
|
||
local fallbackID
|
||
|
||
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[1]) and not (fallback and fallback[1]) 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[1] then
|
||
for k, v in pairs(props) do
|
||
if 'deprecated' ~= v.rank then -- ignore deprecated names
|
||
maxpos = makelink(v, out, link, maxpos, wdl)
|
||
end
|
||
end
|
||
end
|
||
if fallback and fallback[1] then
|
||
-- second properties
|
||
for k, v in pairs(fallback) do
|
||
if 'deprecated' ~= v.rank then -- ignore deprecated names
|
||
maxpos = makelink(v, out, link, maxpos, wdl)
|
||
end
|
||
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[#keys + 1] = k
|
||
end
|
||
table.sort(keys) -- as they might be out of order
|
||
for i, k in ipairs(keys) do
|
||
local label
|
||
if out[k].label then
|
||
label = out[k].label
|
||
else
|
||
label = out[k]
|
||
end
|
||
label = label:gsub(''', '\''); -- prevent cs1|2 multiple names categorization; replace html entity with the actual character
|
||
mw.log(i .. " " .. k .. " " .. label)
|
||
if args[nl_type .. i] then -- name gets overwritten
|
||
-- pull corresponding -link only if overwritten name is same as WD name
|
||
if link[k] and (args[nl_type .. i] == label) then
|
||
args[nl_type .. '-link' .. i] = args[nl_type .. '-link' .. i] or link[k] -- author-linkn or editor-linkn
|
||
end
|
||
else -- name does not get overwritten, so pull name from WD
|
||
if out[k].first and out[k].last then
|
||
-- First/last names were found, so add them
|
||
args[nl_type .. '-last' .. i] = args[nl_type .. '-last' .. i] or out[k].last
|
||
args[nl_type .. '-first' .. i] = args[nl_type .. '-first' .. i] or out[k].first
|
||
else
|
||
args[nl_type .. i] = label
|
||
end
|
||
if link[k] then
|
||
args[nl_type .. '-link' .. i] = args[nl_type .. '-link' .. i] or link[k] -- author-linkn or editor-linkn
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- gets language codes used for a monolingual text property as a table
|
||
local function _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[#out + 1] = v.mainsnak.datavalue.value.language
|
||
end
|
||
end
|
||
return out
|
||
end
|
||
local function getLangOfProp(frame)
|
||
local pid = frame.args.pid or mw.text.trim(frame.args[1] or "")
|
||
if pid == "" then return end
|
||
local qid = frame.args.qid
|
||
if qid == "" then qid = nil end
|
||
return table.concat(_getLangOfProp(qid, pid), ", ")
|
||
end
|
||
|
||
-- gets the language codes of a Wikidata entry as a table
|
||
local function _lang_code(qid)
|
||
-- P407 "language of work or name"
|
||
local lc = _getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P424", ps = 1} ) -- "Wikimedia language code"
|
||
if lc then return mw.text.split( lc, "[, ]+" ) end
|
||
lc = _getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P218", ps = 1} ) -- "ISO 639-1 code"
|
||
if lc then return mw.text.split( lc, "[, ]+" ) end
|
||
return _getLangOfProp(qid, "P1476") -- "title"
|
||
end
|
||
local function lang_code(frame)
|
||
return table.concat(_lang_code(frame.args.qid or mw.text.trim(frame.args[1] or "")), ", ")
|
||
end
|
||
|
||
-- export for debug
|
||
local function 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
|
||
|
||
-- returns the first string sequence of 3 or more digits in a date string, or nil
|
||
local function yearFromDate(datestr)
|
||
if datestr then
|
||
return string.match(datestr, '%d%d%d+')
|
||
end
|
||
return datestr
|
||
end
|
||
|
||
-- Attempt to determine heuristically if we're citing a news article
|
||
local function isNewsArticle(qid, args)
|
||
local newsitems = {
|
||
'Q5707594', -- news article
|
||
'Q30070590', -- magazine article
|
||
-- TODO: somehow use WHERE {?item (wdt:P31|wdt:P31/wdt:P279*) wd:Q5707594.}
|
||
-- main subclasses of news article:
|
||
'Q87849013', -- AP article
|
||
'Q2144639', -- feature story
|
||
'Q139172839', -- top story
|
||
'Q18339884', -- cover story
|
||
'Q3326038', -- critical review
|
||
'Q4202018', -- interview
|
||
'Q309481', -- obituary
|
||
'Q108970988', -- column
|
||
'Q2602337', -- op-ed
|
||
'Q59908', -- opinion piece
|
||
}
|
||
-- most of the time a cheap lookup to a single value will work
|
||
if args['instance-of'] and in_array(args['instance-of'], newsitems) then
|
||
return true
|
||
end
|
||
-- otherwise check multiple non-deprecated values
|
||
local instance_of = mw.wikibase.getBestStatements(qid, 'P31')
|
||
if instance_of and instance_of[1] then
|
||
for k, v in pairs(instance_of) do
|
||
if v.mainsnak.snaktype == "value" and v.mainsnak.datatype == "wikibase-item" then
|
||
if in_array(v.mainsnak.datavalue.value.id, newsitems) then
|
||
return true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
-- Figure out a URL access value, from the "online access status" (P6954) qualifier of an identifier
|
||
-- The [[Template:Cite journal]] docs state these should only allow "free"
|
||
-- e.g. DOI = P356
|
||
local function getIdentifierAccess(qid, args, identifier)
|
||
if not args[identifier] then
|
||
return nil
|
||
end
|
||
|
||
-- check DOI registrant is known free, using CS1 config
|
||
-- TODO: this should also be modelled in Wikidata
|
||
if identifier == 'doi' then
|
||
local registrant, suffix = mw.ustring.match(args.doi:lower(), '^10%.([^/]+)/([^%s–]+)$');
|
||
if registrant and suffix and cs1_cfg.known_free_doi_registrants_t[registrant] then
|
||
return 'free'
|
||
elseif registrant and suffix and cs1_cfg.extended_registrants_t[registrant] then
|
||
for _, incipit in ipairs(cs1_cfg.extended_registrants_t[registrant]) do
|
||
if mw.ustring.find (suffix, '^' .. incipit:lower()) then
|
||
return 'free'
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- fetch statement values to examine qualifiers
|
||
local ids = mw.wikibase.getBestStatements(qid, cfg.simple_properties_t[identifier]['id'])
|
||
if not ids or not ids[1] then
|
||
return nil
|
||
end
|
||
if not ids[1].qualifiers or not ids[1].qualifiers.P6954 then
|
||
return nil
|
||
end
|
||
|
||
-- Check property constraints (P2302) in definition of online access status (P6954)
|
||
-- Q66439598 = 'limited', -- partial open access
|
||
-- Q107459441 = 'registration', -- registration required
|
||
-- Q910845 = 'subscription', -- paywall
|
||
-- Q16572632 = 'subscription', -- login (see note on P6954)
|
||
local free_status = {
|
||
'Q24707952', -- free to read
|
||
'Q232932', -- open access
|
||
}
|
||
local access = ids[1].qualifiers.P6954[1]
|
||
if access.datatype == "wikibase-item" then
|
||
local accessQID = access.datavalue.value.id
|
||
if in_array(accessQID, free_status) then
|
||
return 'free'
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- sort sequence table whose values are key-value pairs by key
|
||
local function comp_key(a, b)
|
||
local param_a, enum_a = a[1]:match ('(%D+)(%d+)$'); -- get param name and enumerator from <a>
|
||
local param_b, enum_b = b[1]:match ('(%D+)(%d+)$'); -- get param name and enumerator from <b>
|
||
|
||
if enum_a and enum_b then -- if both parameters have neumerators
|
||
if param_a == param_b then -- are parameter names the same?
|
||
return tonumber (enum_a) < tonumber (enum_b); -- yes: compare enumerators
|
||
end
|
||
end
|
||
|
||
return a[1] < b[1]; -- alpha sort if here
|
||
end
|
||
|
||
-- sort sequence table whose values are key-value pairs by value
|
||
local function comp_val(a, b)
|
||
return a[2] < b[2]
|
||
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.
|
||
--]]
|
||
local function _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[1]
|
||
local wdl = citeq_args.wdl
|
||
local template = citeq_args.template
|
||
citeq_args.expand = nil
|
||
citeq_args[1] = 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[#langcodes+1] = mw.language.isSupportedLanguage(citeq_args.language) and citeq_args.language
|
||
end
|
||
end
|
||
if not langcodes[1] then
|
||
-- try to find language of work
|
||
langcodes = _lang_code(qid)
|
||
end
|
||
if not langcodes[1] 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(cfg.simple_properties_t) do
|
||
if data.qidonly then
|
||
citeq_args[name] = followQid({qid = qid, props = data.id})
|
||
if citeq_args[name] == qid then
|
||
citeq_args[name] = nil
|
||
end
|
||
end
|
||
if not citeq_args[name] then
|
||
citeq_args[name] = getValue( {data.id, fwd = "ALL", osd = "no", noicon = "true", qid = qid, maxvals = data.maxvals, linked = data.linked, rank = data.rank or "best", citeq_args[name] } )
|
||
end
|
||
if data.populate_from_journal then
|
||
local publishedin = getValue( {"P1433", ps = 1, qid = qid, maxvals = 0, citeq_args[name], qual = data.id, qualsonly = 'yes'} )
|
||
citeq_args[name] = publishedin or _getPropOfProp({qid = qid, prop1 = "P1433", prop2 = data.id, maxvals = data.maxvals, ps = 1})
|
||
end
|
||
if citeq_args[name] and citeq_args[name]:find (cfg.i18n_t.trackingcats["missing-wikidata"], 1, true) then
|
||
-- try fallback to work's native language
|
||
citeq_args[name] = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = langcodes[1] } )
|
||
if citeq_args[name]:find('^Q%d+$') then -- qid was returned
|
||
-- try fallback to qid's native language
|
||
local qid_languages = _lang_code(citeq_args[name])
|
||
citeq_args[name] = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = qid_languages[1] } )
|
||
if citeq_args[name]:find('^Q%d+$') then -- qid was returned again
|
||
citeq_args[name] = nil
|
||
else
|
||
-- record the language found if no lang specified
|
||
citeq_args.language = citeq_args.language or qid_languages[1]
|
||
end
|
||
end
|
||
end
|
||
if data.others then
|
||
oth[#oth + 1] = citeq_args[name] and (name:gsub("^%l", string.upper) .. ": " .. citeq_args[name])
|
||
citeq_args[name] = nil
|
||
end
|
||
end
|
||
|
||
citeq_args.doi = citeq_args.doi and citeq_args.doi:lower() or nil; -- force |doi= value to be lower case
|
||
|
||
-- URL access for identifiers, from P6954 online access status
|
||
-- The [[Template:Cite journal]] docs state these should only allow "free"
|
||
citeq_args['bibcode-access'] = citeq_args['bibcode-access'] or getIdentifierAccess(qid, citeq_args, 'bibcode');
|
||
citeq_args['doi-access'] = citeq_args['doi-access'] or getIdentifierAccess(qid, citeq_args, 'doi');
|
||
citeq_args['hdl-access'] = citeq_args['hdl-access'] or getIdentifierAccess(qid, citeq_args, 'hdl');
|
||
citeq_args['jstor-access'] = citeq_args['jstor-access'] or getIdentifierAccess(qid, citeq_args, 'jstor');
|
||
-- citeq_args['ol-access'] = citeq_args['ol-access'] or getIdentifierAccess(qid, citeq_args, 'ol');
|
||
citeq_args['osti-access'] = citeq_args['osti-access'] or getIdentifierAccess(qid, citeq_args, 'osti');
|
||
citeq_args['ssrn-access'] = citeq_args['ssrn-access'] or getIdentifierAccess(qid, citeq_args, 'ssrn');
|
||
citeq_args['s2cid-access'] = citeq_args['s2cid-access'] or getIdentifierAccess(qid, citeq_args, 's2cid');
|
||
|
||
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 )[1]
|
||
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["archive-url"] = arcurl
|
||
citeq_args["archive-date"] = tonumber(arcd) .. " " .. cfg.i18n_t.months[tonumber(arcm)] .. " " .. 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
|
||
|
||
-- Handle thesis args - degree, supervising institution
|
||
local is_thesis = false
|
||
if citeq_args.thesis_degree then
|
||
is_thesis = true
|
||
citeq_args.degree = citeq_args.thesis_degree
|
||
citeq_args.thesis_degree = nil
|
||
end
|
||
if citeq_args.thesis_submitted_to then
|
||
is_thesis = true
|
||
citeq_args.publisher = citeq_args.thesis_submitted_to
|
||
citeq_args.thesis_submitted_to = nil
|
||
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[1].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[1] and mw.wikibase.getLabelByLang(qid, langcodes[1]))
|
||
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, cfg.i18n_t.wikisource) 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 = '[[' .. slink .. ']]'
|
||
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 = '[[' .. slink .. ']]'
|
||
else -- piped link
|
||
citeq_args.title = '[[' .. slink .. '|' .. wrap_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, "[,;%s]%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['author-last']) and not is_set (citeq_args['author-last1']) and not is_set (citeq_args['author1-last'])
|
||
and not is_set (citeq_args['author-surname']) and not is_set (citeq_args['author-surname1']) and not is_set (citeq_args['author1-surname1']) 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['editor-last']) and not is_set (citeq_args['editor-last1']) and not is_set (citeq_args['editor1-last'])
|
||
and not is_set (citeq_args['editor-surname']) and not is_set (citeq_args['editor-surname1']) and not is_set (citeq_args['editor1-surname']) 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['translator-last']) and not is_set (citeq_args['translator-last1']) and not is_set (citeq_args['translator1-last'])
|
||
and not is_set (citeq_args['translator-surname']) and not is_set (citeq_args['translator-surname1']) and not is_set (citeq_args['translator1-surname']) 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[k] = nil
|
||
elseif in_array (v, {'((unset))', '((ignore))'}) then -- strip off markup for free-text values clashing with local keywords
|
||
citeq_args[k] = '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['display-authors'] then
|
||
citeq_args['display-authors'] = nil; -- unset because no longer needed
|
||
else
|
||
citeq_args['display-authors'] = citeq_args['display-authors'] 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['display-editors'] then
|
||
citeq_args['display-editors'] = nil; -- unset because no longer needed
|
||
else
|
||
citeq_args['display-editors'] = citeq_args['display-editors'] 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 makeOrdinal(citeq_args.edition)
|
||
|
||
-- code to make a guess what template to use from the supplied parameters
|
||
-- (first draft for proof-of-concept)
|
||
local pubyear = yearFromDate(citeq_args['publication-date'])
|
||
local is_news = isNewsArticle(qid, citeq_args)
|
||
if is_news then
|
||
template = template or "news"
|
||
-- some items use P123 (publisher) as the newspaper instead of P1433 (published in)
|
||
if citeq_args.publisher and not citeq_args.journal then
|
||
citeq_args.journal = citeq_args.publisher
|
||
citeq_args.publisher = nil
|
||
end
|
||
elseif citeq_args.journal then
|
||
template = template or "journal"
|
||
citeq_args['publication-date'] = pubyear or citeq_args['publication-date']
|
||
-- articles can be published in books/proceedings, otherwise suppress publisher
|
||
if not citeq_args.isbn then
|
||
citeq_args.publisher = nil
|
||
end
|
||
-- only display ISSN if there are no article identifiers
|
||
if citeq_args.issn then
|
||
if citeq_args.doi or citeq_args.pmid or citeq_args.pmc or citeq_args.arxiv or
|
||
citeq_args.bibcode or citeq_args.jstor or citeq_args.ssrn or citeq_args.s2cid or
|
||
citeq_args.hdl or citeq_args.biorxiv or citeq_args.osti or citeq_args.zbl or
|
||
citeq_args.jfm or citeq_args.mr then
|
||
citeq_args.issn = nil
|
||
end
|
||
end
|
||
elseif is_thesis then
|
||
template = template or "thesis"
|
||
elseif citeq_args.isbn then
|
||
template = template or "book"
|
||
citeq_args.asin = nil -- suppress ASIN if ISBN exists
|
||
citeq_args['publication-date'] = pubyear or citeq_args['publication-date']
|
||
elseif citeq_args.website then
|
||
template = template or "web"
|
||
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 = cfg.i18n_t.wdq.. ' [[:d:' .. qid .. '|' .. qid .. ']]'
|
||
local old_id = citeq_args.id
|
||
if wdl then -- show WD logo
|
||
id = id .. '[[File:Wikidata-logo.svg|16px|alt=|link=]]' -- 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
|
||
|
||
local opt_cat = ''
|
||
|
||
-- Indicator if item has been retracted
|
||
local retract = ''
|
||
if citeq_args.retracted_by then
|
||
opt_cat = cfg.i18n_t.trackingcats.retracted
|
||
local retract_id = string.gsub(citeq_args.retracted_by, 'Q', '')
|
||
local retr_params = {
|
||
citeq = 'yes',
|
||
id = frame:expandTemplate{
|
||
title = 'QID', args = {qid = retract_id}
|
||
}
|
||
}
|
||
retr_params.doi = getValue({"P356", ps = 1, qid = citeq_args.retracted_by}) or nil
|
||
retr_params.doi = retr_params.doi and retr_params.doi:lower() or nil
|
||
retr_params.bibcode = getValue({"P819", ps = 1, qid = citeq_args.retracted_by}) or nil
|
||
retr_params.pmc = getValue({"P932", ps = 1, qid = citeq_args.retracted_by}) or nil
|
||
retr_params.pmid = getValue({"P698", ps = 1, qid = citeq_args.retracted_by}) or nil
|
||
-- check qualifiers for access=free
|
||
retr_params['doi-access'] = getIdentifierAccess(citeq_args.retracted_by, retr_params, 'doi')
|
||
retr_params['bibcode-access'] = getIdentifierAccess(citeq_args.retracted_by, retr_params, 'bibcode')
|
||
if 'yes' == citeq_args['retracted-intentional'] then
|
||
retr_params.intentional = 'yes'
|
||
end
|
||
retract = ' ' .. frame:expandTemplate{ title = "Retracted", args = retr_params }
|
||
end
|
||
-- remove non-CS1 retraction properties
|
||
citeq_args.retracted_by = nil
|
||
citeq_args['retracted-intentional'] = nil
|
||
|
||
-- clean up any blank parameters
|
||
for k, v in pairs(citeq_args) do
|
||
if v == "" then citeq_args[k] = 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 and ("Cite "..template) or "Citation") } -- init with citation template
|
||
if expand == "self" then
|
||
citeq_args.id = old_id -- restore original |id= parameter
|
||
expand_args = { cfg.i18n_t.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[1] .. '=' .. val[2])
|
||
end
|
||
-- make the nowiki'd string and done
|
||
return frame:preprocess (table.concat ({'<syntaxhighlight lang="wikitext" inline="1">', table.concat (expand_args, ' |') .. '}}', '</syntaxhighlight>'}));
|
||
end
|
||
|
||
-- Indicator if item has corrigendum/erratum
|
||
local erratumid = ""
|
||
if citeq_args.erratum then
|
||
opt_cat = opt_cat .. cfg.i18n_t.trackingcats.erratum
|
||
local erratum_params = {
|
||
citeq = 'yes',
|
||
id = frame:expandTemplate{
|
||
title = 'QID', args = {qid = string.gsub(citeq_args.erratum, 'Q', '')}
|
||
}
|
||
}
|
||
erratum_params.doi = getValue({"P356", ps = 1, qid = citeq_args.erratum}) or nil
|
||
erratum_params.doi = erratum_params.doi and erratum_params.doi:lower() or nil
|
||
erratum_params.bibcode = getValue({"P819", ps = 1, qid = citeq_args.erratum}) or nil
|
||
erratum_params.pmc = getValue({"P932", ps = 1, qid = citeq_args.erratum}) or nil
|
||
erratum_params.pmid = getValue({"P698", ps = 1, qid = citeq_args.erratum}) or nil
|
||
-- check qualifiers for access=free
|
||
erratum_params['doi-access'] = getIdentifierAccess(citeq_args.erratum, erratum_params, 'doi')
|
||
erratum_params['bibcode-access'] = getIdentifierAccess(citeq_args.erratum, erratum_params, 'bibcode')
|
||
if 'yes' == citeq_args['erratum-checked'] then
|
||
erratum_params.checked = 'yes'
|
||
end
|
||
erratumid = ' ' .. frame:expandTemplate{ title = "Erratum", args = erratum_params }
|
||
end
|
||
citeq_args.erratum = nil
|
||
citeq_args['erratum-checked'] = nil
|
||
|
||
-- Indicator if item has an expression of concern notice (P14891)
|
||
local eoc = ""
|
||
if citeq_args['expression-of-concern'] then
|
||
opt_cat = opt_cat .. cfg.i18n_t.trackingcats['expression-of-concern']
|
||
local eoc_params = {
|
||
citeq = 'yes',
|
||
id = frame:expandTemplate{
|
||
title = 'QID', args = {qid = string.gsub(citeq_args['expression-of-concern'], 'Q', '')}
|
||
}
|
||
}
|
||
eoc_params.doi = getValue({"P356", ps = 1, qid = citeq_args['expression-of-concern']}) or nil
|
||
eoc_params.doi = eoc_params.doi and eoc_params.doi:lower() or nil
|
||
eoc_params.bibcode = getValue({"P819", ps = 1, qid = citeq_args['expression-of-concern']}) or nil
|
||
eoc_params.pmc = getValue({"P932", ps = 1, qid = citeq_args['expression-of-concern']}) or nil
|
||
eoc_params.pmid = getValue({"P698", ps = 1, qid = citeq_args['expression-of-concern']}) or nil
|
||
-- check qualifiers for access=free
|
||
eoc_params['doi-access'] = getIdentifierAccess(citeq_args['expression-of-concern'], eoc_params, 'doi')
|
||
eoc_params['bibcode-access'] = getIdentifierAccess(citeq_args['expression-of-concern'], eoc_params, 'bibcode')
|
||
if 'yes' == citeq_args['eoc-intentional'] then
|
||
eoc_params.intentional = 'yes'
|
||
end
|
||
eoc = ' ' .. frame:expandTemplate{ title = "Expression of concern", args = eoc_params }
|
||
end
|
||
citeq_args['expression-of-concern'] = nil
|
||
citeq_args['eoc-intentional'] = nil
|
||
|
||
if citeq_args.replaced_by then
|
||
opt_cat = opt_cat .. cfg.i18n_t.trackingcats.replaced
|
||
citeq_args.replaced_by = nil
|
||
end
|
||
if not citeq_args.title then -- emit category link if missing title
|
||
opt_cat = opt_cat .. cfg.i18n_t.trackingcats['no-label-or-title'];
|
||
end
|
||
|
||
-- Disable CiteSeerX IDs for now
|
||
citeq_args.citeseerx = nil
|
||
|
||
-- remove any remaining non-CS1 fields
|
||
citeq_args['instance-of'] = nil
|
||
|
||
-- render the template
|
||
local lc_template = template and template:lower() or "citation"
|
||
local output
|
||
if cite_cfg.known_templates_t[lc_template] then
|
||
local config_t = {['CitationClass'] = cite_cfg.citation_classes_t[lc_template] or lc_template}; -- set CitationClass value
|
||
output = require (cs1_module)._citation (nil, citeq_args, config_t) -- go render the citation
|
||
else
|
||
output = frame:expandTemplate{ -- render the template
|
||
title = "Cite "..template, args = citeq_args
|
||
} .. cfg.i18n_t.trackingcats["unknown-template"]
|
||
end
|
||
return output .. erratumid .. eoc .. retract .. opt_cat
|
||
end
|
||
|
||
local function cite_q (frame)
|
||
local args = {}
|
||
for k, v in pairs(frame:getParent().args) do
|
||
if v ~= "" then args[k] = v end
|
||
end
|
||
for k, v in pairs(frame.args) do
|
||
if v ~= "" then args[k] = v end
|
||
end
|
||
args.qid = args.qid or args[1] or ""
|
||
if args.qid == "" then return nil end
|
||
args[1] = 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[k] = v
|
||
end
|
||
end
|
||
newargs.qid = q
|
||
if tag == "ref" then
|
||
cites[#cites + 1] = frame:callParserFunction{ name = "#tag:ref", args = { _cite_q(newargs), name = q } }
|
||
-- expand like this: args = { _cite_q(newargs), name = 'foo', group = 'bar' }
|
||
else
|
||
cites[#cites + 1] = _cite_q(newargs)
|
||
end
|
||
end
|
||
|
||
if list then
|
||
return frame:expandTemplate{ title = list, args = cites }
|
||
else
|
||
return table.concat(cites, citesep)
|
||
end
|
||
end
|
||
|
||
--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------
|
||
]]
|
||
|
||
return {
|
||
makeOrdinal = makeOrdinal,
|
||
_getLangOfProp = _getLangOfProp,
|
||
getLangOfProp = getLangOfProp,
|
||
lang_code = lang_code,
|
||
getPropOfProp = getPropOfProp,
|
||
_cite_q = _cite_q,
|
||
cite_q = cite_q
|
||
}
|