Module:Cite Q

require('Module:No globals') local wdib = require('Module:WikidataIB') local getValue = wdib._getValue local getPropOfProp = wdib._getPropOfProp local followQid = wdib._followQid

local i18n = { ["unknown-author"] = mw.wikibase.getLabel("Q4233718"):gsub("^%l", mw.ustring.upper), ["unknown-author-trackingcat"] = "", }

local simple_properties = { -- PXXX, is multiple?, linked? publisher = {id = "P123", maxvals = 1}, oclc = {id = "P243", maxvals = 1}, ['publication-place'] = {id = "P291", maxvals = 0, linked = 'no'}, -- publication place (don't put into |place=; is treated specially in 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, but will be supported in the future) pmid = {id = "P698", maxvals = 1}, --	gbooks = {id = "P675", maxvals = 1}, -- to be added to --	ia = {id = "P724", maxvals = 1}, -- to be added to 	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 --	['total-pages'] = {id = "P1104", maxvals = 0, linked = 'no'}, -- to be added to / COinS &rft.tpages= --	coden = {id = "P1159", maxvals = 1}, -- to be added to / 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) --	['catalog-number'] = {id = "P528", maxvals = 0}, -- to be added to / 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}, ['publication-date'] = {id = "P577", maxvals = 1, populate_from_journal = true}, -- publication date (don't use |date=; is treated specially in 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  / COinS &rft.part= title = {id = "P1476", maxvals = 1}, url = {id = "P953", maxvals = 1}, -- full work available at	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, 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 }

local citeq = {}

--[[--< 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 v.qualifiers and v.qualifiers.P1932 then label = v.qualifiers.P1932[1].datavalue.value elseif v.qualifiers and v.qualifiers.P1810 then label = v.qualifiers.P1810[1].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["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 out[position] = label link[position] = sitelink else if wdl then -- show that there's a Wikidata entry available out[position] = "" .. label .. "  "				else -- no Wikidata links wanted, so just give the plain label out[position] = 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["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) out[position] = 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[1].datavalue.value elseif v.qualifiers and v.qualifiers.P1810 then label = v.qualifiers.P1810[1].datavalue.value else label = i18n["unknown-author"] .. (i18n["unknown-author-trackingcat"] or "") end maxpos = maxpos + 1 out[maxpos] = label end return maxpos end -- of local function makelink

--[=[-< 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 entity = mw.wikibase.getEntity(qid) local props = nil local fallback = nil if entity and entity.claims then props = entity.claims[propertyID] if fallbackID then fallback = entity.claims[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			maxpos = makelink(v, out, link, maxpos, wdl) end end if fallback and fallback[1] 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[#keys + 1] = k	end table.sort(keys) -- as they might be out of order for i, k in ipairs(keys) do mw.log(i .. " " .. k .. " " .. (out[k])) 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] == out[k]) 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 args[nl_type .. i] = out[k] 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

--[[-< C I T E _ Q >--

Takes standard CS1|2 template parameters and passes all to. 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 for rendering.

]]

local function wrap_nowiki(str) return mw.text.nowiki(str or '') end

function citeq.cite_q (frame) local citeq_args = {}; local expand = ''; -- when set to anything, causes to render

for k, v in pairs(frame:getParent.args) do		if in_array (k, {'expand', '_debug'}) then if is_set(v) then expand = v; -- record setting but don't pass |expand= to 			end else citeq_args[k] = v		end end

for k, v in pairs(frame.args) do		citeq_args[k] = v	end local qid = citeq_args.qid local wdl = citeq_args.wdl citeq_args.qid = nil citeq_args.wdl = nil local oth = {} citeq_args.language = citeq_args.language or getPropOfProp( {qid = qid, prop1 = "P407", prop2 = "P218", ps = 1} ) if citeq_args.language == '' then citeq_args.language = nil end if not citeq_args.language then -- try fallback to journal's language local journal_qid = followQid({qid = qid, props = "P1433"}) citeq_args.language = journal_qid and getPropOfProp( {qid = journal_qid, prop1 = "P407", prop2 = "P218", ps = 1} ) end

for name, data in pairs(simple_properties) do		citeq_args[name] = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = data.linked, citeq_args[name] } ) if data.populate_from_journal then citeq_args[name] = getValue( {"P1433", ps = 1, qid = qid, maxvals = 0, citeq_args[name], qual = data.id, qualsonly = 'yes'} ) citeq_args[name] = citeq_args[name] or getPropOfProp({qid = qid, prop1 = "P1433", prop2 = data.id, maxvals = data.maxvals, ps = 1}) end if citeq_args[name] and citeq_args[name]:find('', 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 = citeq_args.language } ) if citeq_args[name]:find('^Q%d+$') then -- qid was returned -- try fallback to qid's native language local qid_language = getPropOfProp( {qid = citeq_args[name], prop1 = "P407", prop2 = "P218", ps = 1} ) citeq_args[name] = getValue( {data.id, ps = 1, qid = qid, maxvals = data.maxvals, linked = "no", lang = qid_language } ) if citeq_args[name]:find('^Q%d+$') then -- qid was returned again citeq_args[name] = nil 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.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 = 0, citeq_args.isbn } ) -- try ISBN 10 citeq_args.url = getValue( {"P856", ps = 1, qid = qid, maxvals = 0, citeq_args.url } ) -- try official website citeq_args.url = getValue( {"P2699", ps = 1, qid = qid, maxvals = 0, citeq_args.url } ) -- try url

local slink = mw.wikibase.getSitelink(qid) local label = mw.wikibase.getLabel(qid) or citeq_args.language and mw.wikibase.getLabelByLang(qid, citeq_args.language) local slink_flag = false local wrap_title = '' if citeq_args.title then if slink then citeq_args.url = nil wrap_title = wrap_nowiki(citeq_args.title) slink_flag = true else citeq_args.title = wrap_nowiki(citeq_args.title) end else if slink then citeq_args.url = nil if slink:lower == label:lower then citeq_args.title =  .. slink ..  else wrap_title = wrap_nowiki(slink:gsub("%s%(.+%)$", ""):gsub(",.+$", "")) slink_flag = true end else citeq_args.title = wrap_nowiki(label) end end if slink_flag then if slink == wrap_title then -- direct link citeq_args.title =  .. slink ..  else -- piped link citeq_args.title =  .. wrap_title ..  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 			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? citeq_args['display-authors'] = citeq_args['display-authors'] or 3 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? citeq_args['display-editors'] = citeq_args['display-editors'] or 3 end

-- |id= could hold more than one identifier pulled from Wikidata not supported by, right now only add our qid to the list local list_sep = '. ';	if citeq_args.mode ~= 'cs1' then list_sep = ', '; end local id = 'Wikidata ' .. qid .. ''; -- go through "WDQ (identifier)" redirect to reduce clutter in "What links here" and improve reverse lookup. Keep in sync with. 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

if is_set(expand) then																-- if |expand=, write a nowiki'd version to see what the template call looks like local expand_args = {'{{citation'};										-- 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 for p, v in pairs (citeq_args) do										-- spin through citeq_args and table.insert (expand_args, p .. '=' .. v);								-- add parameter name = value end -- make the nowiki'd string and done return table.concat ({' '}) end local opt_cat = '' if getValue( {"P5824", ps = 1, qid = qid} ) then opt_cat = '' end if getValue( {"P1366", ps = 1, qid = qid} ) then opt_cat = opt_cat .. ''	end return frame:expandTemplate{title = 'citation', args = citeq_args} .. opt_cat -- render the template end

return citeq