Module:WikiJournal
Module documentation
Usage
This module is used to gather and format the contents of the 'infobox' of a wikijournal article that is drawn from wikidata. Its functions are mainly used by {{Article info}}.
See also
- {{Article info}}
- {{Article info main}}
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 and event.qualifiers['P585'] and 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 author
]]--
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)
for id, data in pairs( qualifiers or {}) 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') 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 then
local qualP1545 = v.qualifiers["P1545"] and v.qualifiers["P1545"][1]
if qualP1545 then
ordinal = tonumber(qualP1545.datavalue.value) or noord
end
end
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
local qualtxt = renderQualifiers(v.qualifiers, allflag, qorder, qwanted)
auths[ordinal] = {name = name, orcid = orcid, emp = emp, quals = qualtxt}
ords[ordinal] = true
-- Add affiliation and email to the author
processQualifiers(v.qualifiers, affiliations, auths, ordinal)
-- 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 then
local qualP1545 = v.qualifiers["P1545"] and 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 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 = { name=v.email , v.email } }
end
if v.orcid then
out[i] = out[i] .. " [[file:ORCID_iD.svg|frameless|text-bottom|16px|link=https://orcid.org/" .. v.orcid .. " ]]"
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
--[[
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] .. " [[file:ORCID_iD.svg|frameless|text-bottom|16px|link=https://orcid.org/" .. v.orcid .. " ]]"
end
if v.role then
out[i] = out[i] .. " (" .. v.orcid .. ")"
end
if v.quals ~= "" then
out[i] = out[i] .. " <small>(" .. v.quals ..")</small>"
end
if v.username then
out[i] = out[i] .. " <small>[[Special:EmailUser/" .. v.username .. "|contact]]</small>"
end
end
-- glue the list of editors together as a vertical list
local returntxt = "<p>" .. table.concat(out, "<br>") .. "</p>"
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] .. " [[file:ORCID_iD.svg|frameless|text-bottom|16px|link=https://orcid.org/" .. v.orcid .. " ]]"
end
end
-- glue the list of reviewers together as a vertical list
local returntxt = "<p>" .. table.concat(out, "<br>") .. "</p>"
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]
if fig ~= nil then
local parameters = Transcluder.getParameters(fig)
local file = parameters['image']
local caption = parameters['caption'] or ''
output = file
end
end
return output
end
return p