Module:WikiJournal/Sandbox

local p = {}

--[[ Function imported from https://en.wikipedia.org/w/index.php?title=Module:Time&oldid=958665680#L-232

decode ISO formatted date/time into a table suitable for os.time. --]] local function parseISODate(isoDate) if isoDate == nil then return nil end

-- Wikibase if type(isoDate) == 'table' then if isoDate.datavalue ~= nil then isoDate = isoDate.datavalue.value.time end

if isoDate.mainsnak ~= nil then isoDate = isoDate.mainsnak.datavalue.value.time end end

local year, month, day, hour, minute, second;

year, month, day, hour, minute, second = isoDate:match('(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)'); if not year then year, month, day, hour, minute, second = isoDate:match('^(%d%d%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)$'); if not year then return nil; end end

return { ['year'] = year, ['month'] = month, ['day'] = day, ['hour'] = hour, ['min'] = minute, ['sec'] = second }; end

-- Iterates through a table of 'significant event' Stops on the first 'submission' event found and returns the event data -- local function getSubmissionFromEvents(events) events = events or {}

for _, event in pairs(events) do       if mw.wikibase.getLabel(event.mainsnak.datavalue.value.id) == 'submission' then return event.qualifiers['P585'][1] end end

return nil end

-- From a table of wikibase time snaks, returns either the lowest or highest date -- local function getMaxMinDate(time, returnType) local max = -math.huge local min = math.huge

time = time or {}

for _, entry in pairs(time) do       local success, timestamp = pcall(os.time, parseISODate(entry.datavalue.value.time)) if success then if timestamp > max then max = timestamp end

if timestamp < min then min = timestamp end else return false end end

if returnType == 'max' then return max end

return min end

--[[ Loads the affiliations for an author with the given id Returns only affiliations that start before a given publication date or end after a given submission date

Arguments submission and publication are expected to be timestamps ]]-- local function getAuthorAffiliations(id, submission, publication, affiliationMap) if not id or type(id) ~= 'string' then return {} end

-- load affiliations (P108 = Employer) local affiliations = mw.wikibase.getBestStatements(id, 'P108')

-- Affiliations that end after submission -- Affiliations that start before publication local validAffiliations = {}

for _, affiliation in pairs(affiliations) do       affiliation.qualifiers = affiliation.qualifiers or {}

local name -- Add the affiliation to the list of all affiliations if affiliation.mainsnak and affiliation.mainsnak.datatype == 'wikibase-item' then name = mw.wikibase.renderSnak(affiliation.mainsnak) if affiliation.qualifiers['P6424'] ~= nil then name = mw.wikibase.renderSnak(affiliation.qualifiers['P6424'][1]) end

affiliationMap[mw.hash.hashValue('md5', name)] = name end

local startTime = affiliation.qualifiers['P580'] local endTime = affiliation.qualifiers['P582']

local startValid = false local endValid = false

if startTime ~= nil and type(publication) == 'number' then -- If P580 (start time) occurs multiple times, we'll use the earliest local timestamp = getMaxMinDate(startTime, 'min')

-- Diff time returns seconds from a to b = b - a           -- If the difference from start time to publication is greater than 0 -- the affiliation has started before the publication -- Timeline: > |Timestamp|  |Publication|  > startValid = timestamp ~= false and os.difftime(publication, timestamp) >= 0 end

if endTime ~= nil and type( submission ) == 'number' then -- If P582 (end time) occurs multiple times, we'll use the latest local timestamp = getMaxMinDate(endTime, 'max')

-- Diff time returns seconds from a to b = b - a           -- If difference the from submission to the end time is greater than 0 -- the affiliation ends after the submission -- Timeline: > |Submission|  |Timestamp|  > endValid = timestamp ~= false and os.difftime(timestamp, submission) >= 0 end

if startTime ~= nil and endTime ~= nil then -- Insert only if both are true if startValid and endValid then table.insert(validAffiliations, affiliation) end else -- Insert if either one is true if startValid or endValid then table.insert(validAffiliations, affiliation) end end end

return validAffiliations end

-- Iterates through a list of affiliations, saves the affiliation by its md5 hashed name Adds the affiliation hash to the authorm -- local function addAffiliations(affiliationData, allAffiliations, auths, ordinal) if type(affiliationData) ~= 'table' then return nil end

for _, affiliation in pairs(affiliationData) do       if affiliation.mainsnak and affiliation.mainsnak.datatype == 'wikibase-item' then if affiliation.qualifiers and affiliation.qualifiers['P6424'] ~= nil then affiliation = affiliation.qualifiers['P6424'][1] else affiliation = affiliation.mainsnak end end

local name = mw.wikibase.renderSnak(affiliation)

-- Key is the MD5 value of the affiliation name -- We can't use affiliation.hash as mainsnaks do not contain such a key local affiliationKey = mw.hash.hashValue('md5', name)

if not allAffiliations[affiliationKey] then allAffiliations[affiliationKey] = name end

if type(auths[ordinal].affiliation) ~= 'table' then auths[ordinal].affiliation = {} end

-- 'Set' like behaviour, does not add duplicate keys auths[ordinal].affiliation[affiliationKey] = true end end

-- Iterates through a list of qualifiers and adds wanted properties to the author table -- local function processQualifiers( qualifiers, affiliations, auths, ordinal ) if qualifiers == nil then return end

-- Set name to affiliation_string (P6424) if one is a substring of another if qualifiers['P1416'] and qualifiers['P6424'] then local affiliation = mw.wikibase.renderSnak(qualifiers['P1416'][1]) local affiliation_string = mw.wikibase.renderSnak(qualifiers['P6424'][1])

if string.find(affiliation, affiliation_string, 1, true) ~= nil or          string.find(affiliation_string, affiliation, 1, true) ~= nil then qualifiers['P1416'] = qualifiers['P6424'] end end

for id, data in pairs( qualifiers ) do       -- E-Mail qualifier if id == 'P968' then local email = mw.wikibase.renderSnak( data[1] ) local split = mw.text.split( email, ':', true )

-- Remove 'mailto:' part if #split == 2 then auths[ordinal].email = split[2] else auths[ordinal].email = email end end

-- affiliation / affiliation_string if id == 'P1416' or id == 'P6424' then addAffiliations(data, affiliations, auths, ordinal) end end end

-- Return the entity ID of the item linked to the current page. function p.QID(frame) if not mw.wikibase then return "no mw.wikibase" end return mw.wikibase.getEntityIdForTitle( mw.title.getCurrentTitle.subjectPageTitle.text ) or "" end

--[[ Fetch info from Wikidata and format lists

Author info (name, employer, orcid) --]]

function p.getAuthors(frame) local args= frame.args if not args.qid and not args[1] then args = frame:getParent.args end

local qid = mw.text.trim(args[1] or args.qid or "") if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return end

-- qualID is a string list of wanted qualifiers or "ALL" local qualID = mw.text.trim(args.qual or ""):upper local allflag = (qualID == "ALL") -- create table of wanted qualifiers as key local qwanted = {} -- create sequence of wanted qualifiers local qorder = {} for q in mw.text.gsplit(qualID, "%p") do -- split at punctuation and iterate local qtrim = mw.text.trim(q) if qtrim ~= "" then qwanted[mw.text.trim(q)] = true qorder[#qorder+1] = qtrim end end

-- Returns all wanted qualifiers as a comma separated string local function renderQualifiers(qualifiers) if type(qualifiers) ~= 'table' then return '' end

if type(qorder) ~= 'table' then qorder = {} end

-- get author string's qualifiers if wanted local qtbl = {} local qualtxt = "" if qualifiers and (allflag or #qorder > 0) then for prop, val in pairs(qualifiers) do               -- TODO REMOVE P968 (E-Mail) IN TEMPLATE if allflag or (qwanted[prop] and prop ~= 'P968' and prop ~= 'P2868') then -- render the first value of each qualifier qtbl[#qtbl + 1] = mw.wikibase.renderSnak(val[1]) end end

qualtxt = table.concat(qtbl, ", ") -- use comma space separators end

return qualtxt end

-- construct a table of tables: -- key = series ordinal; -- value = {name=author's name, orcid=author's orcid id, emp=author's employer. quals = qualifiers} local auths = {} -- series ordinal = true when used local ords = {} -- list of affiliations local affiliations = {} -- keep track of values without series ordinals and max ordinal local noord = 0 local maxord = 0

-- Timestamp of the article submission or nil local articleSubmission = getSubmissionFromEvents(mw.wikibase.getBestStatements(qid, 'P793')) -- Timestamp of the article publication or nil local articlePublication = mw.wikibase.getBestStatements(qid, 'P577')[1] _, articleSubmission = pcall(os.time, parseISODate(articleSubmission)) _, articlePublication = pcall(os.time, parseISODate(articlePublication))

-- get authors that have entries local prop50 = mw.wikibase.getBestStatements(qid, "P50") for i, v in ipairs(prop50) do		if v.mainsnak.snaktype == "value" then -- get author's qid local nameid = v.mainsnak.datavalue.value.id

-- get author's name local name = mw.wikibase.getLabel(nameid) or "No label"

-- get author's orcid id			local orcid local orcidtbl = mw.wikibase.getBestStatements(nameid, "P496")[1] if orcidtbl and orcidtbl.mainsnak.snaktype == "value" then orcid = orcidtbl.mainsnak.datavalue.value end

-- get ordinal local ordinal = noord if v.qualifiers and v.qualifiers['P1545'] then local qualP1545 = v.qualifiers['P1545'][1] if qualP1545 then ordinal = tonumber(qualP1545.datavalue.value) or noord end end

if ordinal == noord then noord = noord -1

name = mw.html.create('span') :wikitext(string.format( '%s%s', nameid, name, frame:expandTemplate{ title = 'Tooltip', args = { '!', 'Author is missing P1545 (ordinal)' } }                    	))                         :allDone name = tostring(name) end -- check for a duplicate ordinal if ords[ordinal] then repeat ordinal = ordinal + 1 until not ords[ordinal] end

local qualtxt = renderQualifiers(v.qualifiers, allflag, qorder, qwanted)

auths[ordinal] = {name = name, orcid = orcid, quals = qualtxt} ords[ordinal] = true

-- Add affiliation and email to the author processQualifiers(v.qualifiers, affiliations, auths, ordinal)

-- If author has a wiki username and has corresponding author, set email to 'by online form' -- Else list the value of P2868 if v.qualifiers and v.qualifiers['P2868'] then local username = mw.wikibase.getBestStatements(nameid, 'P4174')

local addToQuals = false local role = mw.wikibase.getLabel(v.qualifiers['P2868'][1].datavalue.value.id) if role == 'corresponding author' then if username ~= nil then auths[ordinal].email = string.format('by online form', username[1].mainsnak.datavalue.value) else addToQuals = true end end

if addToQuals or role ~= 'corresponding author' then if auths[ordinal].quals ~= "" then auths[ordinal].quals = string.format('%s, %s', auths[ordinal].quals, role) else auths[ordinal].quals = role end end end

-- If no affiliation was added, load it from the authors wikibase page if auths[ordinal].affiliation == nil then local validAffiliations = getAuthorAffiliations(nameid, articleSubmission, articlePublication, affiliations)

if #validAffiliations > 0 then addAffiliations(validAffiliations, affiliations, auths, ordinal) end end

if ordinal > maxord then maxord = ordinal end end end

-- add author's name strings to the author's table local propP2093 = mw.wikibase.getBestStatements(qid, "P2093") for i, v in ipairs(propP2093) do		if v.mainsnak.snaktype == "value" then local name = v.mainsnak.datavalue.value local ordinal = noord if v.qualifiers and v.qualifiers['P1545'] then local qualP1545 = v.qualifiers['P1545'][1] if qualP1545 then ordinal = tonumber(qualP1545.datavalue.value) or noord end end

local qualtxt = renderQualifiers(v.qualifiers, allflag, qorder, qwanted)

-- get ordinal if ordinal == noord then noord = noord -1

name = mw.html.create('mark') :attr('title', 'Author is missing P1545 (ordinal)') :wikitext(name) :allDone name = tostring(name) end

-- check for a duplicate ordinal if ords[ordinal] then repeat ordinal = ordinal + 1 until not ords[ordinal] end auths[ordinal] = {name = name, quals = qualtxt} ords[ordinal] = true

-- Add affiliation and email to the author processQualifiers(v.qualifiers, affiliations, auths, ordinal)

if ordinal > maxord then maxord = ordinal end end end

-- compact the auths table into a sequence local authors = {} for idx = 1, maxord do		if auths[idx] then table.insert(authors, auths[idx]) end end for idx = 0, noord, -1 do		if auths[idx] then table.insert(authors, auths[idx]) end end

-- Maps an affiliation hash to the first author that used it   local affiliationsUsed = {}

-- construct some output local out = {}

for i, v in ipairs(authors) do		out[i] = v.name

if type(v.affiliation) == 'table' then for hash, _ in pairs(v.affiliation) do               if affiliationsUsed[hash] == nil then out[i] = out[i] .. frame:expandTemplate{ title = 'efn', args = { name=hash, affiliations[hash] } } else -- Affiliation was used, make a reference out[i] = out[i] .. frame:callParserFunction{ name = '#tag:ref', args = { affiliations[hash], name = hash, group = 'lower-alpha' } }               end end end

if v.email then out[i] = out[i] .. frame:expandTemplate{ title = 'efn-lr', args = { v.email } } end

if v.orcid then out[i] = out[i] .. " "		end

if v.quals ~= "" then out[i] = out[i] .. frame:expandTemplate{ title = 'efn-ua', args = { name=v.quals, v.quals } } end end

-- glue the list of authors together as a comma separated list local returntxt = table.concat(out, ", ") return returntxt end

-- Author plain info (name only) --

function p.getAuthorsPlain(frame) local args= frame.args if not args.qid and not args[1] then args = frame:getParent.args end

local qid = mw.text.trim(args[1] or args.qid or "") if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return end

-- construct a table of tables: -- key = series ordinal; -- value = {name=author's name} local auths = {} -- series ordinal = true when used local ords = {} -- keep track of values without series ordinals and max ordinal local noord = 0 local maxord = 0 -- get authors that have entries local prop50 = mw.wikibase.getBestStatements(qid, "P50") for i, v in ipairs(prop50) do		if v.mainsnak.snaktype == "value" then -- get author's qid local nameid = v.mainsnak.datavalue.value.id			-- get author's name local name = mw.wikibase.getLabel(nameid) or "No label" -- get ordinal local ordinal = noord if ordinal == noord then noord = noord -1 end -- check for a duplicate ordinal if ords[ordinal] then repeat ordinal = ordinal + 1 until not ords[ordinal] end auths[ordinal] = {name = name} ords[ordinal] = true if ordinal > maxord then maxord = ordinal end end end

-- add author's name strings to the author's table local propP2093 = mw.wikibase.getBestStatements(qid, "P2093") for i, v in ipairs(propP2093) do		if v.mainsnak.snaktype == "value" then local name = v.mainsnak.datavalue.value local ordinal = noord -- get ordinal if ordinal == noord then noord = noord -1 end -- check for a duplicate ordinal if ords[ordinal] then repeat ordinal = ordinal + 1 until not ords[ordinal] end auths[ordinal] = {name = name} ords[ordinal] = true if ordinal > maxord then maxord = ordinal end end end

-- compact the auths table into a sequence local authors = {} for idx = 1, maxord do		if auths[idx] then table.insert(authors, auths[idx]) end end for idx = 0, noord, -1 do		if auths[idx] then table.insert(authors, auths[idx]) end end

-- construct some output local out = {} for i, v in ipairs(authors) do		out[i] = v.name end -- glue the list of authors together as a comma separated list local returntxt = table.concat(out, ", ") return returntxt end

-- Returns the number of authors for a given wikidata entry -- function p.getNumberOfAuthors(frame) local args= frame.args if not args.qid and not args[1] then args = frame:getParent.args end

local qid = mw.text.trim(args[1] or args.qid or "") if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return end

-- get authors that have entries local prop50 = mw.wikibase.getBestStatements(qid, "P50") or {}

-- add author's name strings to the author's table local propP2093 = mw.wikibase.getBestStatements(qid, "P2093") or {}

local authors = #prop50 + #propP2093

if authors == 0 then return nil end

return authors end

-- Editor info (name, role) --

function p.getEditors(frame) local args= frame.args if not args.qid and not args[1] then args = frame:getParent.args end

local qid = mw.text.trim(args[1] or args.qid or "") if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return end

-- qualID is a string list of wanted qualifiers or "ALL" local qualID = mw.text.trim(args.qual or ""):upper local allflag = (qualID == "ALL") -- create table of wanted qualifiers as key local qwanted = {} -- create sequence of wanted qualifiers local qorder = {} for q in mw.text.gsplit(qualID, "%p") do -- split at punctuation and iterate local qtrim = mw.text.trim(q) if qtrim ~= "" then qwanted[mw.text.trim(q)] = true qorder[#qorder+1] = qtrim end end

-- construct a table of tables: -- key = series ordinal; -- value = {name=editor's name, orcid=editor's orcid id, quals=qualtxt} local eds = {} -- series ordinal = true when used local ords = {} -- list of roles local roles = {} -- keep track of values without series ordinals and max ordinal local noord = 0 local maxord = 0 -- get editors that have entries local prop98 = mw.wikibase.getBestStatements(qid, "P98") for i, v in ipairs(prop98) do		if v.mainsnak.snaktype == "value" then -- get editor's qid local nameid = v.mainsnak.datavalue.value.id			-- get editor's name local name = mw.wikibase.getLabel(nameid) or "No label" -- get editor's orcid id			local orcid local orcidtbl = mw.wikibase.getBestStatements(nameid, "P496")[1] if orcidtbl and orcidtbl.mainsnak.snaktype == "value" then orcid = orcidtbl.mainsnak.datavalue.value end -- get editor's username local username local usernametbl = mw.wikibase.getBestStatements(nameid, "P4174")[1] if usernametbl and usernametbl.mainsnak.snaktype == "value" then username = usernametbl.mainsnak.datavalue.value end -- get editor's qualifiers if wanted local qtbl = {} local qualtxt = "" if v.qualifiers and (allflag or #qorder > 0) then for prop, val in pairs(v.qualifiers) do					if allflag or qwanted[prop] then -- render the first value of each qualifier qtbl[#qtbl + 1] = mw.wikibase.renderSnak(val[1]) end end qualtxt = table.concat(qtbl, ", ") -- use comma space separators end -- get ordinal local ordinal = noord if ordinal == noord then noord = noord -1 end -- check for a duplicate ordinal if ords[ordinal] then repeat ordinal = ordinal + 1 until not ords[ordinal] end eds[ordinal] = {name = name, orcid = orcid, quals = qualtxt, username = username} ords[ordinal] = true if ordinal > maxord then maxord = ordinal end end end

-- compact the eds table into a sequence local editors = {} for idx = 1, maxord do		if eds[idx] then table.insert(editors, eds[idx]) end end for idx = 0, noord, -1 do		if eds[idx] then table.insert(editors, eds[idx]) end end

-- construct some output local out = {} for i, v in ipairs(editors) do		out[i] = v.name if v.orcid then out[i] = out[i] .. " "		end if v.role then out[i] = out[i] .. " (" .. v.orcid .. ")" end if v.quals ~= "" then out[i] = out[i] .. " (" .. v.quals ..") " end if v.username then out[i] = out[i] .. " contact " end end -- glue the list of editors together as a vertical list local returntxt = " " .. table.concat(out, " ") .. " "	return returntxt end

-- Reviewer info (name, orcid) --

function p.getReviewers(frame) local args= frame.args if not args.qid and not args[1] then args = frame:getParent.args end

local qid = mw.text.trim(args[1] or args.qid or "") if qid == "" then qid = mw.wikibase.getEntityIdForCurrentPage end if not qid then return end

-- construct a table of tables: -- key = series ordinal; -- value = {name=reviewer's name, orcid=reviewer's orcid id, role=reviewer's role} local eds = {} -- series ordinal = true when used local ords = {} -- list of roles local roles = {} -- keep track of values without series ordinals and max ordinal local noord = 0 local maxord = 0 -- get reviewers that have entries local prop4032 = mw.wikibase.getBestStatements(qid, "P4032") for i, v in ipairs(prop4032) do		if v.mainsnak.snaktype == "value" then -- get reviewer's qid local nameid = v.mainsnak.datavalue.value.id			-- get reviewer's name local name = mw.wikibase.getLabel(nameid) or "No label" -- get reviewer's orcid id			local orcid local orcidtbl = mw.wikibase.getBestStatements(nameid, "P496")[1] if orcidtbl and orcidtbl.mainsnak.snaktype == "value" then orcid = orcidtbl.mainsnak.datavalue.value end -- get ordinal local ordinal = noord if ordinal == noord then noord = noord -1 end -- check for a duplicate ordinal if ords[ordinal] then repeat ordinal = ordinal + 1 until not ords[ordinal] end eds[ordinal] = {name = name, orcid = orcid} ords[ordinal] = true if ordinal > maxord then maxord = ordinal end end end

-- compact the eds table into a sequence local reviewers = {} for idx = 1, maxord do		if eds[idx] then table.insert(reviewers, eds[idx]) end end for idx = 0, noord, -1 do		if eds[idx] then table.insert(reviewers, eds[idx]) end end

-- construct some output local out = {} for i, v in ipairs(reviewers) do		out[i] = v.name if v.orcid then out[i] = out[i] .. " "		end end -- glue the list of reviewers together as a vertical list local returntxt = " " .. table.concat(out, " ") .. " "	return returntxt end

-- Extract fig from article (page, fig_number) --

local Transcluder = require('Module:Transcluder')

-- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end return message end

function p.getFig(frame) local args = Transcluder.parseArgs(frame)

-- Make sure the requested page exists local page = args[1] if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText

-- Get the nth fig file local ok, figs = pcall(Transcluder.get, page, { only = 'templates', templates = 'fig' }) if ok then local fig = Transcluder.getTemplates(figs, args.fig_number)[1] local parameters = Transcluder.getParameters(fig) local file = parameters['image'] local caption = parameters['caption'] or '' output = file end return output end

return p