Modul:Literatur
Vorlagenprogrammierung | Diskussionen | Lua | Test | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
--[=[ Literatur 2022-09-27
Module for bibliography reuse and ordered aggregation
Author: Vollbracht
Service functions
* byWikiData(qualifier) single bibliography item
* byParamSet(table) single bibliography item
* ISBN(source) test and format isbn
Service object
* dataset universal literature data container
constructor:
new(source)
fields: depending on source; field names are given as values in
transCode tables
methods:
satisfyConstraints
declares information to be redundant or irrelevant
condAdd adds objects to this dataSet with respect to constraints
addList adds an array of names to this dataSet
addSet conditionally adds a transcoded sequence of objects
bequeath inherit transcoded data from a more general dataset
processSingle tostring a singular dataSet element
processList tostring a dataSet array element
processObject tostring a dataset object element
processSet tostring a sequence of dataSet elements
toString do not use externally. This method will be renamed to
__tostring
__tostring not available yet for the sake of better debugging
template functions
* byWikiData(frame, qualifier, position) single bibliography entry
* ISBN(source) format isbn
* list(frame, <unnamed content string>) bibliography in list form
* listEntry(frame) entry as part of list content string
* anchor(frame) bibliography entry with anchor
]=]
--Module globals
local p = {service = {}}
local _, Parser = pcall(require, "Modul:SimpleStruct")
local _, Limited = pcall(require, "Modul:SimpleDataAccess")
local _, bit = pcall(require, 'bit32' )
local _, Titles = pcall(require, "Modul:Title")
local _, Time = pcall(require, "Modul:Time")
local _, URL = pcall(require, "Modul:URL")
Titles = Titles.service
Time = Time.point
-- Wikipedia:Lua/Modul/URIutil functions seam unfeasible for ISBN handling.
-- Don't mention German language in deWiki! (It's expected.)
local PREF_LANG = 'Deutsch'
-- Don't mention persons if their number excedes a certain maximum!
local maxPersons = {
P50=7, P2093=7, authors=7, -- authors (up to 7 + 7)
P767=3, coauthors=3, -- coauthors (up to 3)
P98=7, P5769=7, editors=7 -- editors (up to 7 + 7)
}
-- pairs of <source key>, <drain key>
local transCode = {
-- all properties that could be copied from book data
tcP1476 = {
authors='authors', title='title', subtitle='subtitle',
contribution='contribution', editors='editors', partOf='partOf',
series='series', seriesContribution='seriesContribution',
volume='volume', seriesVolume='seriesVolume', number='number',
seriesNumber='seriesNumber', edition='edition', language='language',
NoPages='NoPages', publisher='publisher', PoPub='PoPub', year='year',
ISBN='ISBN', ISSN='ISSN', ZDB='ZDB', DNB='DNB', LCCN='LCCN', OCLC='OCLC',
ID='ID', Lizenznummer='Lizenznummer', arXiv='arXiv', bibcode='bibcode',
DOI='DOI', archive='archive', JSTOR='JSTOR', PMC='PMC', PMID='PMID',
article='article', URL='URL', archive='archive', Format='fileFormat',
KBytes='dataSize', dataLink='dataLink', originalLanguage,
translator='translator', URN='URN'
},
-- all properties that could be copied from partOf data
tcP361 = {
authors='authors', editors='editors', title='partOf', partOf='partOf',
volume='volume', number='number', series='series',
seriesVolume='seriesVolume', seriesNumber='seriesNumber',
ISBN='ISBN', ISSN='ISSN', ZDB='ZDB', DNB='DNB', LCCN='LCCN', OCLC='OCLC',
Lizenznummer='Lizenznummer', DOI='DOI', URL='URL', ID='ID', URN='URN',
edition='edition', publisher='publisher', PoPub='PoPub', year='year',
dataLink='dataLink'
},
tcP1433 = {
authors='authors', editors='editors', title='partOf', partOf='partOf',
volume='volume', number='number', series='series',
seriesVolume='seriesVolume', seriesNumber='seriesNumber',
ISBN='ISBN', ISSN='ISSN', ZDB='ZDB', DNB='DNB', LCCN='LCCN', URL='URL',
OCLC='OCLC', Lizenznummer='Lizenznummer', DOI='DOI', URN='URN', ID='ID',
edition='edition', publisher='publisher', PoPub='PoPub', year='year',
dataLink='dataLink'
},
-- all properties that could be copied from series data
tcP179 = {
authors='authors', editors='editors', title='series', partOf='series',
volume='seriesVolume', number='seriesNumber',
ZDB='ZDB', DNB='DNB', LCCN='LCCN', OCLC='OCLC',
Lizenznummer='Lizenznummer', DOI='DOI', URN='URN', ID='ID',
edition='edition', publisher='publisher', PoPub='PoPub', year='year',
dataLink='dataLink'
},
-- all properties that could occur in German templates
tcGer = {
Autor='authors', Autoren='authors', Titel='title',
Untertitel='subtitle', TitelErg='contribution', Hrsg='editors',
Herausgeber='editors', HrsgReihe='editors', Sammelwerk='partOf',
Reihe='series', WerkErg='seriesContribution',
Band='volume', BandReihe='seriesVolume', Nummer='number',
NummerReihe='seriesNumber',
Auflage='edition',
Sprache='language', Umfang='NoPages',
Verlag='publisher', Ort='PoPub', Datum='year',
ISBN='ISBN', ISBNformalFalsch='ISBN', ISBNdefekt='ISBN', ISSN='ISSN',
ISSNformalFalsch='ISSN', ZDB='ZDB', DNB='DNB', LCCN='LCCN', OCLC='OCLC',
Lizenznummer='Lizenznummer', arXiv='arXiv', bibcode='bibcode',
DOI='DOI', JSTOR='JSTOR', PMC='PMC', PMID='PMID', URN='URN', ID='ID',
Kapitel='chapter', Seite='page', Seiten='page', Spalten='column',
ArtikelNr='article', Fundstelle='PoFind', Kommentar='comment',
Online='URL', Format='fileFormat', KBytes='dataSize', Abruf='retrieved',
Originaltitel='originalTitle', Originalsprache='originalLanguage',
Originaljahr='retrieved', ['Übersetzer']='translator'
},
-- all literature properties known in wikiData
tcP = {
P50='authors', P2093='authors', P767='contributor', P1476 = 'title',
P1680='subtitle', P98='editors', P5769='editors', P361='partOf',
P1433='partOf', P179='series', P478='volume', P1545='number',
P433='number', P393='edition', P9767='edition', P407='language', P1104='NoPages',
P123='publisher', P291='PoPub', P577='year',
P957='ISBN', P212='ISBN', P236='ISSN', P1042='ZDB-ID', P1292='DNB',
P1144='LCCN', P243='OCLC', P5331='OCLC', P818='arXiv', P1300='bibcode',
P356='DOI', P724='archive', P888='JSTOR', P932='PMC', P698='PMID',
P1813='ID', P2699='URL', P2701='fileFormat', P3575='dataSize',
P813='retrieved', P364='originalLanguage', P655='translator'
},
-- all legators
tcLegator={ title='P1476', partOf='P361', series='P179' },
-- format strings used for properies
tcFormat = {
-- books: (don't rely on magic words:)
ISBN= '[[spezial:ISBN-Suche/%s|ISBN %s]]',
["ISBN-10"]='[[spezial:ISBN-Suche/%s|ISBN %s]]',
["ISBN-13"]='[[spezial:ISBN-Suche/%s|ISBN %s]]',
ISBNwrong='[[spezial:ISBN-Suche/%s|ISBN %s]] <small>(ungült.)</small>',
ISBNinvalid='[[spezial:ISBN-Suche/%s|ISBN %s]] <small>(unmögl.)</small>',
LCCN= '[[Library of Congress Control Number|LCCN]] [https://lccn.'
.. 'loc.gov/%s/ %s]',
PMID= '[https://www.ncbi.nlm.nih.gov/pubmed/%s?dopt=Abstract PMID %s]',
PMC= '[https://www.ncbi.nlm.nih.gov/pmc/articles/PMC%s/ PMC %s]',
-- positions:
page= 'S. %s',
column= 'Spalte %s',
originalTitle='Originaltitel: %s',
originalLanguage='Originalsprache: %s',
translator='Übersetzer: %s',
dataLink= '[[d:%s|wd]]'
},
tcURLprefix = {
DNB= '[[Deutsche Nationalbibliothek|DNB]] [https://portal.dnb.de'
.. '/opac.htm?referrer=Wikipedia&method=simpleSearch&cqlMode=t'
.. 'rue&query=idn%3D',
OCLC= '[[Online Computer Library Center|OCLC]] [https://worldcat.'
.. 'org/oclc/',
-- journals:
ISSN= '[[ISSN]] [https://portal.issn.org/resource/issn/',
ZDB= '[[ZDB]] [https://zdb-katalog.de/title.xhtml?idn=',
-- articles:
DOI= '[[Digital Object Identifier|doi]]:[https://doi.org/',
JSTOR= '[[JSTOR]] [https://www.jstor.org/stable/',
-- online:
arXiv= '[[ArXiv|arxiv]]: [https://arxiv.org/abs/',
archive= '[[Internet Archive]]: [https://archive.org/details/',
bibcode= '[[bibcode]]: [https://ui.adsabs.harvard.edu/abs/'
},
tcClass = {
year=Time,
retrieved=Time,
['URL']=URL
},
tcObjectFormat = {
year='yyyy',
retrieved='abgerufen am %d.%m.%Y',
URL='online unter <domain>'
}
}
-- for some properties lists of dependencies
local constraints={
-- 1. positive: if <key> data available, enter under {<circumstances>}
subtitle={title=true},
seriesEditors={editors=false},
language={target={neq=PREF_LANG}},
DNB={ISBN=false},
OCLC={ISBN=false, DNB=false},
LCCN={ISBN=false, DNB=false, OCLC=false},
ZDB={ISSN=false},
URL={archive=false},
-- 2. negative: if <key> data available, delete {<other keys>} data
anti={
ISBN={'DNB', 'OCLC', 'LCCN'},
DNB={'OCLC', 'LCCN'},
OCLC={'LCCN'},
ISSN={'ZDB'},
archive={'URL'}
}
}
-- list of properties to be requested in a bunch
local identifiers={
'P724', 'P212', 'P957', 'P236', 'P1042', 'P1292', 'P1144', 'P243',
'P818', 'P1300', 'P356', 'P888', 'P932', 'P698', 'P1813'
}
--[[
how to handle a property:
1 - (simple) property is a single value
2 - (list) property is a list of values whereat , 3 - legator, 4 - generic, 5 - object
]]
local cpHandling={
authors=2, title=3, subtitle=1, contribution=1, editors=2, partOf=3,
series=3, seriesContribution=1, volume=1, seriesVolume=1, number=1,
seriesNumber=1, edition=1, language=1, NoPages=1, publisher=1, PoPub=2,
year=5, ISBN=4, ISSN=1, ZDB=1, DNB=1, LCCN=1, OCLC=1, Lizenznummer=1,
arXiv=1, bibcode=1, DOI=1, JSTOR=1, PMC=1, PMID=1, URN=1, ID=1, chapter=1,
page=1, column=1, article=1, PoFind=1, comment=1, URL=5, fileFormat=1,
dataSize=1, retrieved=5, originalTitle=1, originalLanguage=1,
originalYear=5, translator=1
}
-------------------- local functions --------------------
-- if nops met then ops not met
local nops = {
eq = function(a, b) return a ~= b end,
neq = function(a, b) return a == b end,
le = function(a, b) return a > b end,
ge = function(a, b) return a < b end,
lt = function(a, b) return a >= b end,
gt = function(a, b) return a <= b end,
['nil'] = function(a, b) if a then return true end return false end
}
-------------------- service functions --------------------
-- functions to be called from within other modules
-----------------------------------------------------------
p.service.dataset = {
--[[
satisfyConstraints
value if constraints are satisfied
parameters:
property: designated for value; defining constraints
value: designated result
returns: value if all constraints for property are met / nil otherwise
]]
satisfyConstraints = function(this, property, value)
if not value then return nil end
if value == '' then return nil end
local cList = constraints[property]
if not cList then return value end
for cProp, cValue in pairs(cList) do
if type(cValue) == 'boolean' then
if ((this[cProp] or false) and this[cProp] ~= '') ~= cValue then
return nil
end
elseif type(cValue) == 'table' then
local comparator = value
if cProp ~= 'target' then comparator = this[cProp] end
for c2, cV2 in pairs(cValue) do
if nops[c2] == nil then
if not comparator or comparator == '' then return nil end
end
if nops[c2](comparator, cV2) then return nil end
end
else
if this[cProp] and this[cProp] ~= cValue then return nil end
end
end
return value
end,
--[[
dataSet:condAdd(pName, value)
Conditionally adds a value to a dataSet with respect to constraint set.
Will not alter or replace a preexisting property named pName
parameters:
pName property name: value will be stored as dataSet[pName] if
all constraints are met.
value property value to be stored
returns:
true if property could be newly written
false if property has not been written
due to preexistance
due to contradictory constraints
due to missing data
]]
condAdd = function(this, pName, value)
if not pName then return false end
if this[pName] then return false end
value = this:satisfyConstraints(pName, value)
if not value then return false end
this[pName] = value
cList = constraints.anti[pName]
if cList then
for _, ck in ipairs(cList) do this[ck] = nil end
end
return true
end,
--[[
dataSet:addList(pName, list)
Adds an item list (enhancement, no replacement) i. e. adds all listed
values found in item list to a properties list.
parameters:
pName property name: values will be added to dataSet[pName] table
list source of new property values to be added there
Will accept table of strings or comma separated string list.
Will interpret Wikidata object identifiers and will fetch
native language name or label and sitelink if available.
Attention: will not prevent double entries ('Tom' and 'Tom')
returns:
true if at least one new value could be added
false if no value could be added due to missing or malformed data
]]
addList = function(this, pName, list)
if not pName then return false end
if not list then return false end
if type(list) == 'string' then list = mw.text.split(list, ',%s*')
elseif type(list) ~= 'table' then return false end
if not list[1] then return false end
if not this[pName] then this[pName] = {} end
local result = false
--mw.logObject(list, 'list in addList for ' .. pName)
for _, name in ipairs(list) do
local nid = nil
if type(name) == 'string' then nid = name:match('Q%d+') end
if nid then
-- prefer native language name
local n = Limited.MainSnakValue(nid, 'P1559')
-- if not available fall back to label
if not n then n = mw.wikibase.getLabel(nid) end
local l = mw.wikibase.getSitelink(nid)
if l then
if not n or n == '' then n = '[[d:' .. nid .. '|n. n.]]'
elseif l == n then n = '[[' .. l .. ']]'
else n = '[[' .. l .. '|' .. n .. ']]' end
end
nid = n
table.insert(this[pName], nid)
result = true
elseif name ~= '' then
if type(name) == 'string' then
table.insert(this[pName], name)
result = true
else
mw.logObject(name, 'falscher Datentyp')
mw.log(name)
end
end
end
--mw.logObject(this, 'dataSet after addList')
return result
end,
--[[
dataSet:addSet(source, tcName)
Conditionally copies all elements defined in transcode list out of
source. Transcode list is a list of key=value pairs whereat key is
source key and value is drain key (key in this object). Will not copy an
element if this[drain key] is present already.
parameters:
source a dataSet or any structure containing usefull key=value
pairs
tcName name of a transcode list that can be retreived as
transCode[tcName]
no return value
]]
addSet = function(this, source, tcName)
local tc = transCode[tcName]
for k, v in pairs(source) do
local tk = tc[k]
if tk then
this:condAdd(tk, v)
end
end
end,
--[[ --------------------- legator handling ---------------------
1. add a struct containing title and optionally volume and number
2. refine the struct after retaining all primary information
3. copy refined secondary information into dataSet
]]
bequeath = function(this, pName, fbVolume, fbNumber)
if not pName then return false end
local ids = this[pName]
if not ids then return false end
local id = ids:match('Q%d+')
if not id then return false end
local legator = p.service.byWikiData(id)
if legator then
this[pName] = nil
this:addSet(legator, 'tc' .. pName)
this:addSet({volume=fbVolume, number=fbNumber}, 'tc' .. pName)
return true
else
this:condAdd(transCode.tcP[pName],
'[[d:' .. id .. '|<span style="color:red;">' .. id
.. ' ohne Titel</span>]]')
return false
end
end,
--[[ --------------------- output handling ---------------------
dataSet:processSingle(pName, isNotFirst, prefix, postfix)
Extends this.stringVal by formated single value of a single property.
Will append prefix and postfix if property value is present only.
parameters:
pName property name (optional, don't add anything if absent)
isNotFirst Is a punctuation pending already? (true, false or nil)
prefix string value to be prepended to property value
(optional; default by isNotFirst)
postfix string value to be postpended to property value
returns new isNotFirst value
]]
processSingle = function(this, pName, isNotFirst, prefix, postfix)
if not pName then return isNotFirst end
if not prefix then prefix = '' end
if not postfix then postfix = '' end
local val = this[pName]
if not val then return isNotFirst end
if isNotFirst and not prefix:find('^%p') then
this.stringVal = this.stringVal .. ','
end
this.stringVal = this.stringVal .. prefix .. val .. postfix
if postfix:match('%p%s*$') then return false end
return true
end,
--[[
dataSet:processList(pName, prefix, separator, postfix)
Extends this.stringVal by formated list of values of a single property.
Will append prefix and postfix only if property is present with at least
one list element.
parameters:
pName property name (optional, don't add anything if absent)
prefix string value to be prepended to the list
(optional; default by isNotFirst)
separator string value to be inserted in between the list values
postfix string value to be postpended to the list
returns new isNotFirst value
]]
processList = function(this, pName, prefix, separator, postfix)
if this[pName] then
if prefix then
if type(prefix) ~= 'string' then
this.stringVal = this.stringVal .. ', '
else
this.stringVal = this.stringVal .. prefix
end
elseif prefix ~= nil then
this.stringVal = this.stringVal .. ' '
end
if not separator then separator = ', ' end
this.stringVal = this.stringVal
.. table.concat(this[pName], separator)
if postfix then
this.stringVal = this.stringVal .. postfix
if postfix:match('%.') then return false end
end
return true
else return false end
end,
processObject = function(this, pName, prefix, postfix)
local value = this[pName]
if value then
if prefix then
if type(prefix) ~= 'string' then
this.stringVal = this.stringVal .. ', '
else
this.stringVal = this.stringVal .. prefix
end
elseif prefix ~= nil then
this.stringVal = this.stringVal .. ' '
end
if not postfix then postfix = '' end
value = value:format(transCode.tcObjectFormat[pName])
this.stringVal = this.stringVal .. value .. postfix
if postfix:match('%.') then return false end
return true
end
if type(prefix) ~= 'string' then return prefix end
return prefix:find('^,')
end,
processSet = function(this, list, prefix, separator, postfix)
if list then
local l = {}
for _, pName in ipairs(list) do
local n = this[pName]
if n then
local fmtString = transCode.tcFormat[pName]
if fmtString then
--mw.log(fmtString .. ' / ' .. n)
table.insert(l, string.format(fmtString, n, n))
else
fmtString = transCode.tcObjectFormat[pName]
if fmtString then
table.insert(l, n:format(fmtString))
else
fmtString = transCode.tcURLprefix[pName]
if fmtString then
table.insert(l, fmtString .. n .. ' ' .. n .. ']')
else
table.insert(l, n)
end
end
end
end
end
if #l > 0 then list = l
else list = nil end
end
if not list then
if prefix then
if type(prefix) == 'string' then return false end
return true
end
return false
end
local isNotFirst = false
if prefix then
if type(prefix) ~= 'string' then
this.stringVal = this.stringVal .. ', '
else
this.stringVal = this.stringVal .. prefix
end
elseif prefix ~= nil then
this.stringVal = this.stringVal .. ' '
end
if not separator then separator = ', ' end
for _, value in ipairs(list) do
if isNotFirst then
this.stringVal = this.stringVal .. separator
end
this.stringVal = this.stringVal .. value
isNotFirst = true
end
if postfix then
this.stringVal = this.stringVal .. postfix
if postfix:match('%.') then return false end
end
return true
end
}
function p.service.dataset:new (source)
local result = {}
if type(source) == 'string' then
result.dataLink = source:match('Q%d+')
end
result.stringVal = ''
setmetatable(result, self)
self.__index = self
return result
end
--[[
dataSet:toString()
formating a dataset into a wikitext: puting preformated text in order
will be renamed to __tostring()
parameters:
dataSet: bibliographical as
{authors={<name>, ...}, title=<title>,
editors={<name>, ...}, series=<name>, volume=<text>,
publisher=<name>, PoPub=<point of publishing>, year=<text>,
ISBN=<formated number>, DNB=<number>, DOI=<number>,
page=<number>, column=<number>...}
returns: citation string
]]
p.service.dataset.toString = function(this)
mw.logObject(this)
local dot = false
fullstop = function()
if dot then
this.stringVal = this.stringVal .. '.'
dot = false
end
end
addEdition = function()
if this.edition then
if this.edition:match('^[%d]+$') then
dot = this:processSingle('edition', dot, " ", '. Auflage')
elseif this.edition:match('Auflage') then
if this.edition:match('%.$') == '.' then
this:processSingle('edition', dot, " ")
dot=false
else
dot = this:processSingle('edition', dot, " ")
end
else
if this.edition:match('%.$') == '.' then
dot = this:processSingle('edition', dot, " ", ' Auflage')
else
dot = this:processSingle('edition', dot, " Auflage: ")
end
end
end
end
this.stringVal = ''
local scheme = 0
local sProps = {'volume', 'number', 'seriesVolume', 'seriesNumber',
'authors', 'partOf', 'series', 'editors'}
for i, v in ipairs(sProps) do
if this[v] and this[v] ~= '' then
scheme = bit.bor(scheme, bit.lshift(1, i))
end
end
scheme = bit.rshift(scheme, 1)
if not this:processList('authors', '', ', ', ": ") then
this:processList('editors', '', ', ', " (Hrsg.): ")
end
if this.subtitle and this.subtitle ~= ''
and not this.subtitle:find("^%s*%'%'") then
this.subtitle = "''" .. this.subtitle .. "''"
end
dot = this:processSet({'title', 'subtitle'}, "", ': ', "")
if this.contribution and this.contribution ~= '' then
this.stringVal = this.stringVal .. ' ' .. this.contribution
end -- dot pending
if bit.band(scheme, 176) == 176 then -- author, editor, partOf, ...
this:processList('editors', '. In: ', ', ', " (Hrsg.): ")
dot = this:processSingle('partOf', false)
addEdition()
elseif bit.band(scheme, 32) == 32 then -- no author or no editor
dot = this:processSingle('partOf', false, ". In: ", "")
addEdition()
elseif scheme == 208 then -- author, editor, series.
addEdition()
this:processList('editors', '. In: ', ', ', " (Hrsg.): ")
dot = this:processSingle('series', false)
elseif bit.band(scheme, 111) == 64 then -- series only
addEdition()
dot = this:processSingle('series', false, ". In: ", "")
else
addEdition()
end
if bit.band(scheme, 65) == 1 then
dot = this:processSet({'volume', 'number'}, ', Band ', '.')
elseif bit.band(scheme, 66) == 2 then
dot = this:processSingle('number', dot, ", Nummer ", '.')
elseif bit.band(scheme,44) > 0 then
if bit.band(scheme, 1) ~= 0 then
dot = this:processSet({'volume', 'number'}, ', Band ', '.')
elseif bit.band(scheme, 2) > 0 then
dot = this:processSingle('number', dot, ", Nummer ")
end
end
if bit.band(scheme, 243) == 146 then
this:processList('editors', " Hrsg.: ", ', ', '.')
dot = false
elseif bit.band(scheme, 240) == 144 then
this:processList('editors', ". Hrsg.: ", ', ', '.')
dot = false
elseif bit.band(scheme, 111) > 64 then
this.stringVal = this.stringVal .. ' (= '
if bit.band(scheme, 240) == 208 then
this:processList('editors', '', ', ', " (Hrsg.): ")
end
dot = this:processSingle('series', false, "", "")
if bit.band(scheme, 4) == 4 then
this:processSet({'seriesVolume', 'seriesNumber'}, ', Band ', '.')
elseif bit.band(scheme, 8) == 8 then
dot = this:processSingle('seriesNumber', dot, ', Nummer ')
elseif bit.band(scheme, 33) == 1 then
this:processSet({'volume', 'number'}, ', Band ', '.')
elseif bit.band(scheme, 34) == 2 then
dot = this:processSingle('number', dot, ', Nummer ')
end
this.stringVal = this.stringVal .. ').'
dot = false
end
fullstop()
dot = this:processSingle('publisher', false, ' ')
dot = this:processList('PoPub', dot, '/') or dot
dot = this:processObject('year', false) or dot
if this.ISBN then
if dot then this.stringVal = this.stringVal .. ', ' end
this.stringVal = this.stringVal .. p.ISBN({args=this})
dot = true
end
this:processSet({'ISSN', 'DNB', 'ZDB', 'DOI', 'LCCN', 'OCLC', 'Lizenznummer',
'arXiv', 'bibcode', 'JSTOR', 'PMC', 'PMID',
'chapter', 'page', 'column', 'position'}, dot, ', ')
fullstop()
this:processSet({
'originalTitle', 'originalYear', 'originalLanguage', 'translator',
'PoFind', 'URL', 'archive', 'fileFormat', 'dataSize', 'retrieved',
'comment', 'dataLink'
}, ' (', ', ', ').')
return this.stringVal
end
--[[
ISBN(source)
plain value, type and formated value of an ISBN found in source
parameters:
source: string containing a sequence of cyphers and hyphens
optionally followed by a capital X
returns: a structure: {plain, key, formated}
plain all cyphers (and 'X') in direct sequence without hyphens
key 'ISBN-10', or 'ISBN-13' (if valid)
'ISBNinvalid' (if no ISBN)
'ISBNwrong' (if miscalculated)
formated typically formated ISBN (if ISBN-10, ISBN-13, or ISBNwrong)
'' (if ISBNinvalid)
]]
p.service.ISBN = function(source)
local plain = source:gsub('-', ''):match('%d+X?')
if #plain == 14 and plain:sub(14,1) == 'X' then plain = plain:sub(1,13) end
if #plain ~= 10 and #plain ~= 13 then
return { ["plain"] = plain, key = 'ISBNinvalid', formated = '' }
end
local result = { ["plain"] = plain }
local stab = mw.text.split(plain, '')
local checksum = 0
if #plain == 10 then
f = source:match('%d+%-%d+%-%d+%-[0-9X]')
if not f then
return { ["plain"] = plain, key = 'ISBNinvalid', formated = '' }
end
result.formated = f
result.group, result.publisher, result.title
= source:match('(%d+)%-(%d+)%-(%d+)')
for i = 1, 9 do checksum = checksum + i * stab[i] end
result.check = checksum % 11
if result.check == 10 then result.check = 'X'
else result.check = tostring(cr) end
if result.check == stab[10] then result.key = 'ISBN-10'
else result.key = 'ISBNwrong' end
return result
end
-- must be ISBN-13
f = source:match('97[89]%-%d+%-%d+%-%d+%-%d')
if not f then
return { ["plain"] = plain, key = 'ISBNinvalid', formated = '' }
end
result.formated = f
result.prefix, result.group, result.publisher, result.title
= source:match('(%d+)%-(%d+)%-(%d+)%-(%d+)')
for i = 1, 12 do
checksum = checksum + stab[i] + (i+1) % 2 * 2 * stab[i]
end
result.check = tostring(10 - checksum % 10)
if result.check == stab[13] then result.key = 'ISBN-13'
else result.key = 'ISBNwrong' end
return result
end -- p.service.ISBN
--[[
byWikiData(qualifier)
convert wikiData item into Lua struct limited to relevant data
mandantory data: title, year and either authors or editors
parameter:
qualifier: wikidata entity qualifier string ([qQ][0-9]+) of an
appropriate wikiData item
noInherit: do not use legators if set
returns: available data in a struct; items formated
inavailable data is nil
struct contains all mandantory data or is nil itself
]]
p.service.byWikiData = function(qualifier, noInherit)
local result = p.service.dataset:new(qualifier)
--[[
conditionally add legator
part of the legator are the named property itself, its qualifiers volume
and number, as well as volume and number of the containing object
Volume and number ...
* of property are ignored if given in external object already.
* of containing object are ignored if given in property or externally.
This way series volume and number may differ from partOf volume and No.
]]
local function condAddLegator(property)
local bsPartOf = mw.wikibase.getBestStatements(qualifier, property)
if not bsPartOf then return end
if not bsPartOf[1] then return end
local bsP1 = bsPartOf[1]
local id = bsP1.mainsnak.datavalue.value.id
if id then result[property] = id
else return end
local volume = Limited.qualifyingValue(bsP1, 'P478')
if volume == '' then
volume = Limited.MainSnackValue(qualifier, 'P478')
end
local number = Limited.qualifyingValue(bsP1, 'P433')
if number == '' then
number = Limited.qualifyingValue(qualifier, 'P1545')
end
if number == '' then
number = Limited.MainSnackValue(qualifier, 'P433')
end
if number == '' then
number = Limited.MainSnackValue(qualifier, 'P1545')
end
if not result:bequeath(property, volume, number) then
result[property] = mw.wikibase.getLabel(id)
end
end
-- byWikiData starting here:
-- Author(s)
result:addList( transCode.tcP['P50'],
Limited.namedAsList(qualifier, 'P50', false, 'id'))
result:addList( transCode.tcP['P2093'],
Limited.namedAsList(qualifier, 'P2093'))
result:addList( transCode.tcP['P767'],
Limited.namedAsList(qualifier, 'P767', false, 'id'))
-- editor(s)
result:addList( transCode.tcP['P98'],
Limited.namedAsList(qualifier, 'P98', false, 'id'))
result:addList( transCode.tcP['P5769'],
Limited.namedAsList(qualifier, 'P5769'))
-- title: mandantory; with link if available
local p = Titles:new(qualifier)
if not p then return nil end
result.title = p:titleLink()
if p.originalTitle then result.originalTitle = p.originalTitle.text end
if p.originalLanguage then result.originalLanguage = p.originalLanguage
else result:condAdd(transCode.tcP['P364'],
Limited.MainSnackValue(qualifier, 'P364')) end
-- add translator in combination with original language only
if result.originalLanguage then
result:condAdd(transCode.tcP['P655'],
Limited.MainSnackValue(qualifier, 'P655')) end
-- subtitle
result:condAdd( transCode.tcP['P1680'],
Limited.MainSnackValue(qualifier, 'P1680'))
-- edition number: preferably "object named as"
local bsEdition = Limited.namedAsList(qualifier, 'P9767', false)
if not bsEdition or not bsEdition[1] then
bsEdition = Limited.namedAsList(qualifier, 'P393', false)
end
if bsEdition[1] then result.edition = bsEdition[1] end
if bsEdition[2] then
mw.log('Pls. have another entity for each edition of ' .. result.title
.. ' (variant to ' .. qualifier .. ')')
end
-- publisher:
result:condAdd( transCode.tcP.P123,
Limited.MainSnackValue(qualifier, 'P123'))
-- place of publication:
result:addList( transCode.tcP.P291,
Limited.namedAsList(qualifier, 'P291'))
-- year of publication:
result:condAdd( transCode.tcP.P577,
Limited.MainSnackValue(qualifier, 'P577'))
-- language: only if not German
result:condAdd( transCode.tcP.P407,
Limited.MainSnackValue(qualifier, 'P407'))
-- URL
local class = transCode.tcClass.URL
mw.log(Limited.MainSnackValue(qualifier, 'P2699'))
local object = class:new(Limited.MainSnackValue(qualifier, 'P2699'))
mw.logObject(object, 'URL')
result:condAdd( transCode.tcP.P2699, object)
mw.logObject(result, 'URL enterred')
-- identifiers:
for _, v in ipairs(identifiers) do
result:condAdd( transCode.tcP[v],
Limited.MainSnackValue(qualifier, v))
end
-- partOf ("In") and series:
-- fall back to data from legators if available
if noInherit then
result:condAdd( transCode.tcP['P361'],
Limited.MainSnackValue(qualifier, 'P361'))
if not result.partOf then
result:condAdd( transCode.tcP['P1433'],
Limited.MainSnackValue(qualifier, 'P1433'))
end
result:condAdd( transCode.tcP['P179'],
Limited.MainSnackValue(qualifier, 'P179'))
else
condAddLegator('P361')
if not result.partOf then condAddLegator('P1433') end
condAddLegator('P179')
end
return result
end
--[[
byParamSet(source)
convert a set of numerous parameters into Lua struct limited to relevant
data
parameters:
source: string with '|' separating between parameters and '=' separating
between German param name as in transCode.tcGer and param value
returns: available data in a struct; items formated
inavailable data is nil
struct contains all mandantory data or is nil itself
]]
p.service.byParamSet = function(source)
local result = p.service.dataset:new(source)
-- legator for inherited additional infos
local Legator = {
prio = {title=3, partOf=2, series=1},
level = 0,
link = '',
add = function(this, pName, value)
local ppt = transCode.tcLegator[pName]
if not ppt then return false end
local lid = value:match('Q%d+')
if not lid then
return result:condAdd(pName, "''" .. value .. "''")
end
this[pName] = this:new{ property = ppt, ID = lid, name = pName}
return true
end,
bequeath = function(this)
local source = p.service.byWikiData(this.ID)
if source then
result:addSet(source, 'tc' .. this.property)
local newLevel = this.prio[this.name]
if newLevel > this.level then
this.level = newLevel
this.link = id
end
else
result[this.name] = Limited.MainSnackValue(this.ID, 'P1476')
if not result[this.name] then
result[this.name] = mw.wikibase.getLabel(this.ID)
end
end
end,
add2comment = function()
if level > 0 then
local lnk = mw.text.format(transCode.tcFormat.dataLinik, link)
if result.comment then
result.comment = result.comment .. ', ' .. lnk
else
result.comment = lnk
end
end
end
}
function Legator:new (o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
return o
end
-- start here:
if type(source) ~= 'table' then return nil end
for key, value in pairs(source) do
local k2 = transCode.tcGer[key]
local handling = 0
if k2 then handling = cpHandling[k2] end
if not handling then handling = 0 end
if handling == 1 then
result:condAdd(k2, value)
elseif handling == 2 then
result:addList(k2, value)
elseif handling == 3 then
--result:addLegator(k2, value)
Legator:add(k2, value)
elseif handling == 4 then
result:condAdd(k2, value)
elseif handling == 5 then
local class = transCode.tcClass[k2]
if class then
local object = class:new(value)
result:condAdd(k2, object)
else mw.logObject( transCode.tcClass,
'no class tcClass.' .. k2 .. ' in') end
end
end
--result:bequeath(title)
--result:bequeath(partOf)
--result:bequeath(series)
if Legator.title then Legator.title:bequeath() end
if Legator.partOf then Legator.partOf:bequeath() end
if Legator.series then Legator.series:bequeath() end
if result.year and not result.year.valid and result.year.valid ~= nil then
result.year = nil
end
if result.ID then return result end
-- else generate ID
local IDP = result.authors
if not IDP then IDP = result.editors end
if IDP then
local shortPs = {}
for _, full in ipairs(IDP) do
full = mw.ustring.gsub(full, "^%A*(.-)%A*$", "%1")
local lw = mw.ustring.match(full, "%a+$")
if lw then table.insert(shortPs, lw) end
end
if result.year then -- ####
result.ID = table.concat(shortPs, ', ') .. ' '
.. result.year:format('yyyy')
else
result.ID = table.concat(shortPs, ', ')
end
return result
end
-- else generate by title:
local IDT = result.partOf
if not IDT then IDT = result.series end
if not IDT then IDT = result.title end
if not IDT then return nil end
IDT = IDT:gsub("^%A*(.-)%A*$", "%1") -- trim: remove link, e.g.
if result.year then
if #IDT < 15 then
result.ID = '(' .. IDT .. ') ' .. result.year:format('yyyy')
return result
end
elseif #IDT < 20 then
result.ID = '(' .. IDT .. ')'
return result
end
-- if too long use last words
local IDwords = mw.text.split(IDT, '%A+')
local i = #IDwords
IDT = IDwords[i]
while i > 1 and #IDT < 7 do
i = i - 1
IDT = IDwords[i] .. ' ' .. IDT
end
if result.year then
result.ID = '(' .. IDT .. ') ' .. result.year:format('yyyy')
elseif i > 1 and #IDT < 10 then
i = i - 1
result.ID = '(' .. IDwords[i] .. ' ' .. IDT .. ')'
else
result.ID = '(' .. IDT .. ')'
end
return result
end
-------------------- template functions --------------------
-- functions to be called from within templates
------------------------------------------------------------
--[[
Literatur|ISBN|<ISBN>
returns formated ISBN
parameters:
<unnamed> or ISBN= string containing a sequence of cyphers and hyphens
optionally followed by a capital X
returns: ISBN formated with one out of three possible formt strings in
transCode.tcFormat: ISBN, ISBNwrong, ISBNinvalid
]]
p.ISBN = function(frame)
local source = frame.args.ISBN
if source == nil or source == "" then source = frame.args[1] end
if source == nil or source == "" then return "" end
if frame.args.plain then return source:gsub('-', ''):match('%d+X?') end
local data = p.service.ISBN(source)
if data.key == 'ISBNinvalid' then
return string.format(transCode.tcFormat[data.key], source, source)
end
return string.format( transCode.tcFormat[data.key],
data.formated, data.formated)
end
--[[
Literatur|byWikiData|qualifier=<Q...>|position=<S. ...>
generates a cite entry
parameters:
qualifier (mandantory): wikidata entity qualifier string ([qQ][0-9]+) of
an appropriate wikiData item
position (optional): additional addenum for the end of the entry
free text equivalent to position{<free text>} or
page{<number>} column{<number>} position{<text>}
returns: limited literature information string
only one (by priority) of ISBN13, ISBN10, DNB
]]
p.byWikiData = function(frame, qualifier)
if not qualifier then qualifier = frame.args.qualifier end
if not qualifier or qualifier == "" then qualifier = frame.args[1] end
if not qualifier or qualifier == "" then
return 'Fehler: kein Buch![[Kategorie:Wikipedia:Qualitätssicherung Vorlageneinbindung fehlerhaft]]'
end
local position = frame.args.position
local dataSet = p.service.byWikiData(qualifier)
if dataSet == nil then
return 'Fehler: kein Buch![[Kategorie:Wikipedia:Qualitätssicherung Vorlageneinbindung fehlerhaft]]'
end
if position and position ~= "" then
if position:match('{') then
local p = Parser.altParse(position)
for k, v in pairs(p) do dataSet[k] = v end
else
dataSet.position = position
end
end
if dataSet.ID then
local ID = dataSet.ID
dataSet.ID = nil
return '<span id="' .. ID .. '">' .. ID .. ' - <span class="reference-t'
.. 'ext">' .. dataSet:toString() .. '</span></span>'
end
return dataSet:toString()
end
--[[
Literatur|listEntry|Titel=...
complete bibliography entry for further processing in Literatur|list
parameters
see tcGer
returns <ID>{entry}
]]
p.listEntry = function(frame)
local result = p.service.byParamSet(frame:getParent().args)
if not result then result = p.service.byParamSet(frame.args) end
if not result then return nil end
if not result.ID then return nil end
local ID = result.ID
result.ID = nil
return ID .. '{' .. result:toString() .. '}'
end
--[[
Literatur|shortRef |ID=<ID>|position=<reference text>|name=<ref name>
|group=<ref group>
shortened reference entry
parameters:
ID see tcGer; mandantory; links to bibliography entry; no ref name
per default (in contrast to fullRef)
position for what ever to be added to ref
optional; default: "passim.";
"p. 42.", "table 15.", "(take a note on the graphics)." e. g.
name *value - for <ref name="*value">
optional; default: empty (in contrast to fullRef);
have "name=#ID" to use ID as ref name (as in fullRef)
group **value - for <ref group="**value">
optional; default: empty
]]
p.shortRef = function(frame)
local ID = frame.args.ID
if not ID then return '' end
local position = frame.args.position
if not position or position == '' then position = 'passim.' end
local refPar = {name = frame:getParent().args.name}
if not refPar.name then refPar.name = frame.args.name end
refPar.group = frame:getParent().args.group
if not refPar.group then group = frame.args.group end
if refPar.name then
if refPar.name == '#ID' then refPar.name = ID
elseif refPar.name == '' then refPar.name = nil end
end
if refPar.group and refPar.group == '' then refPar.group = nil end
local result = '[[#' .. ID .. '|' .. ID .. ']], ' .. position
result = frame:extensionTag('span', result, {class='reference'})
return frame:extensionTag('ref', result, refPar)
end
--[[
Literatur|fullRef|Titel=...
complete bibliographical reference entry
parameters
see tcGer
in addition:
group see <ref group="...">
returns <ref>entry</ref>
]]
p.fullRef = function(frame)
local result = p.service.byParamSet(frame:getParent().args)
if not result then result = p.service.byParamSet(frame.args) end
if not result then return nil end
if not result.ID then return nil end
local refPar = {name = frame:getParent().args.name}
if not refPar.name then refPar.name = frame.args.name end
if not refPar.name then refPar.name = result.ID end
result.ID = nil
if refPar.name and refPar.name == '' then refPar.name = nil end
refPar.group = frame:getParent().args.group
if not refPar.group then refPar.group = frame.args.group end
if refPar.group and refPar.group == '' then refPar.group = nil end
return frame:extensionTag('ref', result:toString(), refPar)
end
p.pur = function(frame)
local result = p.service.byParamSet(frame:getParent().args)
if not result then result = p.service.byParamSet(frame.args) end
if not result then return nil end
return frame:preprocess(result:toString())
end
--[[
Literatur|anchor|Titel=...
complete bibliography entry with anchor
parameters
see tcGer
returns <span id="<ID>"><ID> - <span class="reference-text"><entry></span></span>
]]
p.anchor = function(frame)
local result = p.service.byParamSet(frame:getParent().args)
if not result then result = p.service.byParamSet(frame.args) end
if not result then return nil end
if not result.ID then return nil end
local ID = result.ID
result.ID = nil
return '<span id="' .. ID .. '">' .. ID
.. ' – <span class="reference-text">' .. result:toString()
.. '</span></span>'
end
--[[
Literatur|list|<bibliography entries>
parameters
1 (unnamed) string containing aligned bibliography entries in this format:
<key1>{<entry1>}<key2>{<entry2>}...
these entries may be white space separated at will. e. g.:
Tom 1811{Antony Tom: ''my first book''. hydraulic press 1811.}
Dick 1912{Bert Dick: ''a book''. another press 1912.}
Harry 2013{Q12345678}
Usage of Wikidata qualifiers not yet implemented
returns: string containing ordered bibliography in html list form
]]
p.list = function(frame)
local args = frame:getParent().args[1]
if not args then args = frame.args[1] end
if not args then return "" end
-- collect data
if args == "" then return "" end
local bList = Parser.altParse(args)
-- process data
function compKeys(a, b)
return a[1]<b[1]
end
local cList = {}
for i, e in ipairs(bList) do
local id = e[2]:match('^%s*[qQ]%d+%s*$')
if id then
local entry = p.service.byWikiData(id)
if entry then
if entry.ID then
if e[1] == '' then e[1] = entry.ID end
entry.ID = nil
end
e[2] = entry:toString()
table.insert(cList, e)
-- else don't insert
end
elseif e[2] ~= '' then table.insert(cList, e)
-- else don't insert
end
end
table.sort(cList, compKeys)
local result = ""
local keyAddendum = 0
for i, k in ipairs(cList) do
if k[1] == '' then
keyAddendum = keyAddendum + 1
result = result .. '* <span id="Lit' .. keyAddendum .. '">'
elseif i > 1 and k[1] == bList[i-1][1] then
keyAddendum = keyAddendum + 1
result = result .. '* <span id="' .. k[1] .. '_' .. keyAddendum .. '">'
.. k[1] .. '_' .. keyAddendum .. ' – '
else
keyAddendum = 0
result = result .. '* <span id="' .. k[1] .. '">' .. k[1] .. ' – '
end
result = result .. '<span class="reference-text">' .. k[2]
.. '</span></span>'
if i < #bList then result = result .. "\n" end
end
-- return data
mw.log(result .. '###')
if result ~= "" then
return result
else return "" end
end
p.List2 = function(frame)
local args = frame:getParent().args[1]
if not args then args = frame.args[1] end
if not args then return "" end
-- collect data
if args == "" then return "" end
local bList = Parser.altParse(args)
-- process data
function compKeys(a, b)
return a[1]<b[1]
end
for i, e in ipairs(bList) do
local id = e[2]:match('^%s*[qQ]%d+%s*$')
if id then
local entry = p.service.byWikiData(id)
if entry then
if entry.ID then
if e[1] == '' then bList[i][1] = entry.ID end
entry.ID = nil
end
bList[i][2] = entry:toString()
end
end
end
table.sort(bList, compKeys)
local result = ""
local keyAddendum = 0
for i, k in ipairs(bList) do
if k[1] == '' then
keyAddendum = keyAddendum + 1
result = result .. '* <span id="Lit' .. keyAddendum .. '">'
elseif i > 1 and k[1] == bList[i-1][1] then
keyAddendum = keyAddendum + 1
result = result .. '* <span id="' .. k[1] .. '_' .. keyAddendum .. '">'
.. k[1] .. '_' .. keyAddendum .. ' – '
else
keyAddendum = 0
result = result .. '* <span id="' .. k[1] .. '">' .. k[1] .. ' – '
end
result = result .. '<span class="reference-text">' .. k[2]
.. '</span></span>'
if i < #bList then result = result .. "\n" end
end
-- return data
if result ~= "" then
return result
else return "" end
end
p.test = function(frame)
local testcase = p[frame.args.func](frame)
local e = frame.args.expected
local i = e:find(testcase, nil, true)
if i then
if testcase == e then return e .. '</td></tr><tr><td>' .. e end
if i > 1 then
return e .. '</td></tr><tr><td style="background-color:#f88;">'
.. e:sub(1, i) .. ' vorweg erwartet!'
elseif #e > #testcase then
return e .. '</td></tr><tr><td style="background-color:#f88;">'
.. e:sub(#testcase) .. ' danach erwartet!'
end
end
i = testcase:find(e, nil, true)
if i then
if i > 1 then
return e .. '</td></tr><tr><td style="background-color:#f88;">'
.. testcase:sub(1, i) .. ' zu viel vorweg!'
elseif #e < #testcase then
return e .. '</td></tr><tr><td style="background-color:#f88;">'
.. testcase:sub(#e) .. ' zu viel danach!'
end
end
local t1 = mw.text.split(testcase, '')
local t2 = mw.text.split(e, '')
mw.log(testcase)
mw.log(e)
for i, v in ipairs(t1) do
if v ~= t2[i] then
return e .. '</td></tr><tr><td style="background-color:#f88;">Fehle'
.. 'r ab Position ' .. i .. ': ' .. v .. '/' .. t2[i]
end
end
if #t1 == #t2 then return e .. '</td></tr><tr><td>' .. e end
end
return p