Modul:Zitation

Aus 2020wiki
Version vom 17. August 2022, 21:39 Uhr von Holger>Leyo (zu unspezifisch ausgelöste Fehlermeldung beseitigt: es sollte nur der Linktext analysiert werden, nicht der Link an sich (siehe Vorlage Diskussion:Literatur#Unsinnige Fehlermeldung bei Klammern in Dateinamen))
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Zur Navigation springen Zur Suche springen

Die Dokumentation für dieses Modul kann unter Modul:Zitation/Doku erstellt werden

local Serial = "2021-05-25"
--[=[
Zitation
]=]



Zitation = Zitation  or  { extern = false }
Zitation.serial = Serial
-- local globals
local Selbst      = "Modul:Zitation"
local FehlerTypen = { Intern    = { s = "Interner Fehler",
                                    k = "Intern" },
                      Entfernen = { s = "Veraltet, bitte entfernen" },
                      Format    = { s = "Parameterformat" },
                      Konflikt  = { s = "Parameterkonflikt",
                                    k = "Parameter" },
                      Modul     = { s = "Modul-Seite fehlt",
                                    k = "Intern" },
                      Name      = { s = "Schreibweise falsch",
                                    k = "Name" },
                      Pflicht   = { s = "Pflichtparameter fehlt",
                                    k = "Parameter" },
                      Problem   = { s = "Parameterproblem" },
                      Abruf     = { s = "Abrufdatum mangelhaft",
                                    k = "Abruf" },
                      Vorlage   = { s = "Vorlagen-Seite fehlt",
                                    k = "Intern" },
                      Wert      = { s = "Ungültig",
                                    k = "Parameter" }
                    }
local KategorieBeginn = "Wikipedia:Vorlagenfehler"
local Kategorien      = { Intern     = { s = "/Interner Fehler" },
                          Name       = { s = "/Parameterfehler" },
                          Parameter  = { s = "/Parameterfehler" },
                          Abruf      = { s = "/Abrufdatum" },
                          arXiv      = { s = "Parameter:arXiv" },
                          bibcode    = { s = "Parameter:bibcode" },
                          Datum      = { s = "Parameter:Datum" },
                          DNB        = { s = "Parameter:DNB" },
                          DOI        = { s = "Parameter:DOI" },
                          ISBN       = { s = "Parameter:ISBN" },
                          ISSN       = { s = "Parameter:ISSN" },
                          JSTOR      = { s = "Parameter:JSTOR" },
                          LCCN       = { s = "Parameter:LCCN" },
                          OCLC       = { s = "Parameter:OCLC" },
                          PMID       = { s = "Parameter:PMID" },
                          Sprachcode = { s = "Parameter:Sprachcode" },
                          URN        = { s = "Parameter:URN" },
                          ZDB        = { s = "Parameter:ZDB" }
                        }
local DocTypes =
          { CSV          = "[[CSV (Dateiformat)|CSV]]",
            DJVU         = "[[DjVu]]",
            EPUB         = "[[EPUB]]",
            FLASH        = "[[Adobe Flash|Flash]]",
            GZIP         = "[[gzip]]",
            MIDI         = "[[Musical Instrument Digital Interface]]",
            MP3          = "MP3",
            MPEG         = "[[Moving Picture Experts Group|MPEG]]",
            MPEG4        = "[[MPEG-4]]",
            MSEXCEL      = "[[Microsoft Excel|MS Excel]]",
            MSPOWERPOINT = "[[Microsoft PowerPoint|MS PowerPoint]]",
            MSWORD       = "[[Microsoft Word|MS Word]]",
            PDF          = "PDF",
            POSTSCRIPT   = "[[PostScript]]",
            RAR          = "[[RAR (Dateiformat)|RAR]]",
            REAL         = "[[RealPlayer]]",
            RTF          = "[[Rich Text Format]]",
            WINDOWSMV    = "[[Windows Media Video|WMV]]",
            ZIP          = "[[ZIP-Dateiformat|ZIP]]" }
local URLunwanted = {
                  ["//arxiv%.org/abs/"]                      = "arXiv",
                  ["//adsabs%.harvard%.edu/"]                = "bibcode",
                  ["//portal%.dnb%.de/opac.+query=%d+X?$"]   = "DNB",
                  ["//doi%.org/10%."]                        = "DOI",
                  ["//dx%.doi%.org/10%."]                    = "DOI",
                  ["jstor%.org/pss/"]                        = "JSTOR",
                  ["jstor%.org/stable/"]                     = "JSTOR",
                  ["worldcat%.org/oclc"]                     = "OCLC",
                  ["www%.worldcat%.org/oclc"]                = "OCLC",
                  ["ncbi%.nlm%.nih%.gov/pmc/articles/pmc%d"] = "PMC",
                  ["ncbi%.nlm%.nih%.gov/pubmed/%d"]          = "PMID",
                  ["//nbn%-resolving%.de/urn:"]              = "URN",
                  ["//urn%.nb%.no/urn:nbn:"]                 = "URN",
                  ["//urn%.kb%.se/resolve%?urn="]            = "URN" }
local Fehler    = false
local Fun       = { }
local KBytesMax = 100
local Resultat
local Silent = false
local Spacer


-- Allgemeine Hilfsfunktionen ===========================================



local function faced( ahead, after )
    -- Zwei Elemente durch schmalen Abstand verbinden
    -- Parameter:
    --     ahead  -- string, mit erstem Element
    --     after  -- string, mit zweitem Element
    -- Rückgabewert: string
    local e
    if not Spacer then
        e = mw.html.create( "span" )
                   :css( { display = "inline-block",
                           width   = ".2em" } )
                   :wikitext( " " )
        Spacer = tostring( e )
    end
    return string.format( "%s%s%s", ahead, Spacer, after )
end -- faced()



local function facet( ahead, after )
    -- Zwei Elemente durch schmalen umbruchgeschützten Abstand verbinden
    -- Parameter:
    --     ahead  -- string, mit erstem Element
    --     after  -- string, mit zweitem Element
    -- Rückgabewert: string
    local e = mw.html.create( "span" )
                     :css( "white-space", "nowrap" )
                     :wikitext( faced( ahead, after ) )
    return tostring( e )
end -- facet()



local function fading()
    -- Ausblende-Modus feststellen
    -- Rückgabewert: true, wenn im Ansicht-Modus (kein Bearbeitungsmodus)
    if not Silent then
        if not Zitation.frame then
            Zitation.frame = mw.getCurrentFrame()
        end
        Silent = Zitation.frame:preprocess( "{{REVISIONID}}" )
    end
    return ( Silent ~= "" )
end -- fading()



local function fair( apply )
    -- Turn any whitespace, even by HTML entity, into single ASCII space
    -- Parameter:
    --     apply   -- string, with some text
    -- Rückgabewert: string
    local r = apply
    if r:find( "&", 1, true ) then
        r = r:gsub( " ", " " )
             :gsub( " ", " " )
        if r:find( "&#", 1, true ) then
            r = r:gsub( " ", " " )
                 :gsub( " ", " " )
            if r:find( "&#x", 1, true ) then
                r = r:gsub( "&#x0*[aA]0;", " " )
                     :gsub( "&#x0*202[fF];", " " )
            end
        end
    end
    if r:len() > mw.ustring.len( r ) then
        if not Zitation.patWhSp then
            Zitation.patWhSp = mw.ustring.char( 91, 0xA0,
                                                    0x1680,
                                                    0x2000, 45, 0x200A,
                                                    0x202F,
                                                    0x205F,
                                                93 )
        end
        r = mw.ustring.gsub( r, Zitation.patWhSp, " " )
    end
    return r:gsub( "%s%s+", " " )
end -- fair()



local function faraway( assign, alien )
    -- Sprache zuweisen
    -- Parameter:
    --     assign  -- mw.html-Element
    --     alien   -- string mit Sprachcode, oder nil
    if alien  and  alien ~= "de" then
        assign:addClass( "lang" )
              :attr( { dir  = "auto",
                       lang = alien } )
    end
end -- faraway()



local function feed( area, access, about )
    -- Zugriff auf Parameterkomponente
    -- Parameter:
    --     area    -- string, mit Name der Parametergruppe
    --     access  -- string, mit Name der Komponente, oder nil
    --     about   -- true, for returning original parameter name
    -- Rückgabewert: Parameterwert, oder nil
    local e, r
    if not Zitation.o then
        Zitation.o = { }
    end
    e = Zitation.o[ area ]
    if e then
        if access then
            r = e[ access ]
            if type( r ) == "table" then
                if about then
                    r = r.s or "???????"
                else
                    r = r.v
                end
            end
        else
            r = e
        end
    end
    return r
end -- feed()



local function fehler( art, anzeige )
    -- Ein Fehler ist aufgetreten
    -- Parameter:
    --     art      -- string mit Schlüsselwort zum Typ
    --     anzeige  -- string mit Einzelheiten, oder nil
    local t
    if not Fehler then
        Fehler = FehlerTypen
    end
    t = Fehler[ art ]
    if t then
        if anzeige then
            local s = mw.text.nowiki( anzeige )
            if t.e then
                t.e = string.format( "%s; %s", t.e, s )
            else
                t.e = s
            end
        end
        if t.k then
            local wk = Kategorien[ t.k ]
            if wk then
                wk.e = true
            else
                Fehler.Intern.e     = "Wartungskat " .. wk
                Kategorien.Intern.e = true
            end
        end
    else
        Fehler.Intern.e     = string.format( "fehler(%s) %s",
                                             art, anzeige )
        Kategorien.Intern.e = true
    end
end -- fehler()



local function fehlerliste()
    -- Auflistung aller Fehlermeldungen und Kategorien
    -- Rückgabewert: string mit formatiertem Ergebnis
    local r = ""
    local s
    local t = mw.title.getCurrentTitle()
    if Fehler then
        local sep = ""
        for k, v in pairs( Fehler ) do
             if v.e then
                if v.s then
                    s = v.s .. ":"
                else
                    s = ""
                end
                s = string.format( "*** %s %s", s, v.e )
                if not v.k  and  fading() then
                    local e = mw.html.create( "span" )
                    e:wikitext( s )
                     :addClass( "Zitationsfehler Zitationswartung" )
                     :css( "display", "none" )
                    s = tostring( e )
                end
                r = string.format( "%s%s%s", r, sep, s )
                sep = " "
            end
        end -- for k, v
    end
    if t.namespace == 0    or
       ( t.namespace == 4   and
         t.text:sub( 1, 4 )  ==  "Lua/" ) then
        Selbst = feed( "leise", "Vorlage" )  or  Selbst
        for k, v in pairs( Kategorien ) do
            if v.e then
                if v.s:sub( 1, 1 ) == "/" then
                    s = Selbst
                else
                    s = ""
                end
                r = string.format( "%s[[Kategorie:%s/%s%s]]",
                                   r, KategorieBeginn, s, v.s )
            end
        end -- for k, v
    end
    return r
end -- fehlerliste()



local function fein( abtrennung, anhang )
    -- Ergänze Resultat um fertig formatierten Block
    -- Parameter:
    --     abtrennung  -- string mit vorangestelltem Separator
    --     anhang      -- string mit Textelement
    if anhang then
        Resultat = string.format( "%s%s%s",
                                  Resultat, abtrennung, anhang )
    end
end -- fein()



local function figure( a )
    -- Ist das eine Zahl aus arabischen oder römischen Ziffern?
   return a:match( "^[0-9./%-:IVX]+$" )   or
          a:match( "^C*[LXVI]+$" )   or
          not mw.ustring.find( a, "%a" )
end -- figure()



local function figures( a )
    -- Formatierte Aufzählung von Zahlen
    local i = true
    local r = a
    local j, k, s, suffix
    while i do
        i, j, suffix = r:match( "^(%d+)%-(%d+)(.*)$" )
        if i then
            s = ""
        else
            s, i, j, suffix = r:match( "^(.*[^0-9]?)(%d+)%-(%d+)(.*)$" )
        end
        if i then
            if tonumber( i ) < tonumber( j ) then
                k = 8211
            else
                k = 45
            end
            r = string.format( "%s%s&#%d;%s%s",
                               s, i, k, j, suffix )
        end
    end -- while
    return fair( r ):gsub( "(%d)%s*(ff?)%.?", "%1 %2." )
                    :gsub( " ", "&#160;" )
                    :gsub( "(%d+)&#160;(ff?%.)", faced )
end -- figures()



local function findbar( assigned, about )
    -- Unauffindbare Namen (Personen) melden
    -- Parameter:
    --     assigned  -- string; zwei lateinische Buchstaben oder ein CJK
    --     about     -- string mit Parametername
    if not  mw.ustring.find( assigned, "%a.*%a" ) then
        local Text = Zitation.fetch( "Text" )
        if not Text.containsCJK then
            fehler( "Wert",  string.format( "'%s' zu kurz", about ) )
        end
    end
end -- findbar()



local function fire( art )
    -- Melde Kategorie an
    -- Parameter:
    --     art  -- string mit Schlagwort zum Typ
    local t = Kategorien[ art ]
    if t then
        t.e  =  true
    else
        fehler( "Intern",  "Kategorie:" .. art )
    end
end -- fire()



local function flat( adjust, anzahl, ascii )
    -- Komma-separierte Aufzählung begrenzen
    -- Parameter:
    --     adjust  -- string, mit Aufzählung
    --     anzahl  -- number, mit Anzahl erlaubter Elemente
    --     ascii   -- true, für ASCII-Auslassung
    -- Rückgabewert: string mit gleichem oder gekürztem Ergebnis
    local i = 1
    local n = 0
    local r = adjust
    while i do
        i = r:find( ",", i, true )
        if i then
            n = n + 1
            if n == anzahl then
                r = r:sub( 1, i )
                if ascii then
                    r = r .. " ..."
                else
                    r = r .. "&#160;&#8230;"   -- nbsp hellip
                end
                break -- while
            end
            i = i + 1
        end
    end    -- while i
    return r
end -- flat()



local function foreign( area, access )
    -- Sprachcodes zuweisen
    -- Parameter:
    --     area    -- string, mit Name der Parametergruppe
    --     access  -- string, mit Name der Komponente
    local r = feed( area, access )
    if r == "Undetermined" then
        fehler( "Wert", "Sprache" )
        fire( "Sprachcode" )
    elseif r then
        local Multilingual = Zitation.fetch( "Multilingual" )
        local s            = Multilingual.format( r, "-",
                                                  false, false, false,
                                                  Zitation.frame,
                                                  "[, ]", " " )
        local parts        = mw.text.split( s or "", " " )
        local lapsus
        for i = 1, #parts do
            if not Multilingual.getName( parts[ i ] ) then
                lapsus = true
                break -- for i
            end
        end -- for i
        if lapsus then
            fehler( "Wert",  "Sprachcode=" .. r )
            fire( "Sprachcode" )
            s = r:lower():match( "^(%l%l%l?)-" )
            if s then
                Zitation.fill( area, access, s )
            end
        elseif s ~= r then
            local say = string.format( "%s: '%s' statt '%s' verwenden",
                                       "Sprachcode", s, r )
            r = s
            Zitation.fill( area, access, r )
            fehler( "Format", say )
        end
    end
    return r
end -- foreign()



local function framedTemplate( access, args )
    -- Vorlage einbinden
    -- Parameter:
    --     access  -- Name der Vorlage
    --     args    -- table mit Parameterliste
    -- Rückgabewert: string mit expandierter Vorlage
    if not Zitation.frame then
        Zitation.frame = mw.getCurrentFrame()
    end
    return Zitation.frame:expandTemplate{ title = access, args = args }
end -- framedTemplate()



local function future( ask )
    -- Liegt Datum in der Zukunft?
    -- Parameter:
    --     ask  -- table oder string, mit Datum
    -- Rückgabewert: true, wenn ask ungültig oder in der Zukunft
    local r = true
    local DateTime, datum
    if not Zitation.heute then
        DateTime       = Zitation.fetch( "DateTime" )
        Zitation.heute = DateTime()
    end
    if type( ask ) == "string" then
        DateTime = Zitation.fetch( "DateTime" )
        datum    = DateTime( ask )
    else
        datum = ask
    end
    if type( datum ) == "table" then
        r = ( Zitation.heute < datum )
        if r then
            local karenz = Zitation.heute:future( "90 days" )
            if karenz > datum then
                r = false
            end
        end
    end
    return r
end -- future()



-- Spezielle Werte ======================================================



local function Abrufdatum( abruf )
    -- Gib behauptetes Abrufdatum zurück
    -- Parameter:
    --     abruf  -- table oder string, mit Datum
    local o = abruf
    local r
    if type( o ) == "string" then
        local DateTime = Zitation.fetch( "DateTime" )
        o = DateTime( o )
    end
    if type( o ) == "table" then
        if not future( o ) then
            local s = o:format( "ISO" )
            r = o:format( "T._Monat JJJJ", "de" )
            if not o.dom or not o.month then
                fehler( "Abruf", "Abrufdatum soll taggenau sein" )
            elseif abruf ~= s then
                fehler( "Format",
                        string.format( "'%s'=%s soll sein: %s",
                                       feed( "www", "Abruf", true ),
                                       abruf,
                                       s ) )
                fire( "Datum" )
            end
        end
    end
    if r then
        local pub = feed( "bas", "Datum" )
        if pub then
            if type( pub ) == "string" then
                local DateTime = Zitation.fetch( "DateTime" )
                pub = DateTime( pub )
                if type( pub ) == "table" then
                    Zitation.fill( "bas", "Datum", pub )
                end
            end
            if type( pub ) == "table"  and  o < pub then
                fehler( "Abruf", "Abrufdatum vor Publikationsdatum" )
            end
        end
    else
        r = abruf
        fehler( "Wert",
                string.format( "'%s'=%s",
                               feed( "www", "Abruf", true ),
                               abruf ) )
        fire( "Datum" )
    end
    return "abgerufen am " .. r
end -- Abrufdatum()



local function Herausgeber( apply, above, ahead )
    -- Analysiere Herausgeber und formatiere ihn, mit Klammerzusatz
    -- Parameter:
    --     access  -- string mit Herausgeber
    --     above   -- true: innerhalb Klammerebene
    --     ahead   -- true: vorangestellt statt Klammerzusatz
    -- Rückgabewerte:  -- string
    local pat  = "^([^%(%[]+)[%(%[]((%w+)%.?)[%)%]](.*)$"
    local scan = apply
    local seek = "|hg|hsg|hrsg|hrsgg|herausgeber|ed|eds|editor|editors|"
    local story = ""
    local r, s, start, sub, suffix
    while true do
        start, sub, s, suffix = mw.ustring.match( scan, pat )
        if s then
            if seek:find( string.format( "|%s|", s:lower() ) ) then
                story = story .. mw.text.trim( start )
            elseif suffix:sub( 1, 1 ) == "|"   and
                sub:sub( 1, -1 ) == ")" then
                story = string.format( "%s%s%s",
                                       story, start, sub )
            else
                story = string.format( "%s%s[%s]",
                                       story, start, s )
            end
            scan = suffix
        else
            break -- while
        end
    end -- while
    r = story .. scan
    sub, suffix = mw.ustring.match( r, "^(%w+%.?)%s*(.+)$" )
    if sub == "Hg." then
        -- (Verwechslungsgefahr bei abgekürztem Vornamen)
        r = mw.text.trim( suffix )
    elseif sub then
        seek = "|hrsg|hrsgg|herausgegeben|"
        if seek:find( string.format( "|%s|", sub:lower() ) ) then
            r = mw.text.trim( suffix )
            sub, suffix = mw.ustring.match( r, "^(vo?n?.?)%s*(.+)$" )
            if sub == "von"  or  sub == "v." then
                r = mw.text.trim( suffix )
            end
        end
    end
    if r ~= apply  or  r:match( "%)$" ) then
        fehler( "Wert", "Herausgeber mit problematischem Zusatz" )
    end
    findbar( r, "Hrsg" )
    if ahead then
        r = "Hrsg.: " .. r
    else
        if above then
            r = r .. " [Hrsg.]"
        else
            r = r .. " (Hrsg.)"
        end
    end
    return r
end -- Herausgeber()



local function Ortsname( analyse, area, access )
    -- Analysiere einen Ortsnamen
    -- Parameter:
    --     analyse  -- string, mit Ortsname
    --     area     -- string, mit Name der Parametergruppe
    --     access   -- string, mit Name der Komponente
    local s = analyse:gsub( "&#%x+;", "" )
    if s:find( "%d%d%d%d" ) then
        fehler( "Wert",
                string.format( "'%s' %s (%s)",
                               feed( area, access, true ),
                               "mit verdächtiger Ziffernfolge",
                               "Jahreszahl, Postleitzahl" ) )
    end
end -- Ortsname()



Fun.arXiv = function ( access )
    -- Analysiere arXiv-ID und gib sie als formatierten Link zurück
    local arXiv   = Zitation.fetch( "arXiv", "Vorlage:arXiv" )
    local details = arXiv.fair( access )
    if not details.legal then
        fehler( "Wert", "'arXiv'" )
        fire( "arXiv" )
    end
    arXiv.features( { showArticle = "arXiv" } )
    return arXiv.format( details )
end -- Fun.arXiv()



Fun.bibcode = function ( access )
    -- Analysiere bibcode-ID und gib sie als formatierten Link zurück
    local bibcode = Zitation.fetch( "bibcode", "Vorlage:bibcode" )
    local r       = bibcode.format{ access }
    if not r:find( "//", 1, true ) then
        fehler( "Wert",  "'bibcode' =" .. access )
        fire( "bibcode" )
    end
    return r
end -- Fun.bibcode()



Fun.DOI = function ( access )
    -- Analysiere DOI und gib sie als formatierten Link zurück
    local URIutil = Zitation.fetch( "URIutil" )
    local r       = URIutil.linkDOI( access )
    local s       = "Digital Object Identifier"
    if r then
        r = string.format( "[[%s|doi]]:%s", s, r )
    else
        r = string.format( "[[%s|DOI]]:%s%s",
                           s,
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
        fehler( "Wert", "'DOI'" )
        fire( "DOI" )
    end
    return r
end -- Fun.DOI()



Fun.DNB = function ( access )
    -- Analysiere DNB und gib sie formatiert zurück
    local URIutil = Zitation.fetch( "URIutil" )
    local r
    if URIutil.isDNBvalid( access ) then
        r = URIutil.linkDNBopac( access, false, true, true )
    else
        local s = "Deutsche Nationalbibliothek"
        fehler( "Wert", "'DNB'" )
        fire( "DNB" )
        r = string.format( "[[%s|DNB]] %s%s",
                           s,
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
    end
    return r
end -- Fun.DNB()



Fun.ISSN = function ( access, allow )
    -- Analysiere ISSN und gib sie formatiert zurück
    --     allow    -- true: permit invalid check digit
    local URIutil = Zitation.fetch( "URIutil" )
    if allow or URIutil.isISSNvalid( access ) then
        r = URIutil.linkISSN( access, allow, true, true )
    else
        local s = "International Standard Serial Number"
        fehler( "Wert", "'ISSN'" )
        fire( "ISSN" )
        r = string.format( "[[%s|ISSN]]&#160;%s%s",
                           s,
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
    end
    return r
end -- Fun.ISSN()



Fun.ISSNfalsch = function ( access )
    -- Analysiere formal falsche ISSN und gib sie formatiert zurück
    return Fun.ISSN( access, true )
end -- Fun.ISSNfalsch()



Fun.JSTOR = function ( access )
    -- Analysiere JSTOR (stable) und gib sie formatiert zurück
    -- i: Volume
    local JSTOR = Zitation.fetch( "JSTOR" )
    local r, URIutil
    if access:find( "/", 1, true ) then
        URIutil = Zitation.fetch( "URIutil" )
    end
    r = JSTOR.feasible( access, "stable", URIutil )
    if r then
        r = JSTOR.format( r, "stable", false, URIutil )
    else
        fehler( "Wert", "'JSTOR'" )
        fire( "JSTOR" )
        r = string.format( "JSTOR:%s%s",
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
    end
    return r
end -- Fun.JSTOR()



Fun.LCCN = function ( access )
    -- Analysiere LCCN und gib sie formatiert zurück
    local URIutil = Zitation.fetch( "URIutil" )
    local see = "Library of Congress Control Number"
    local r = string.format( "[[%s|LCCN]]&#160;", see )
    if URIutil.isLCCN( access ) then
        r = r .. URIutil.linkLCCN( access, "-" )
    else
        fehler( "Wert", "'LCCN'" )
        fire( "LCCN" )
        r = string.format( "%s%s%s",
                           r,
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
    end
    return r
end -- Fun.LCCN()



Fun.Lizenznummer = function ( access )
    -- Gib DDR-Lizenznummer formatiert zurück
    return "[[Lizenznummer]] " .. access
end -- Fun.Lizenznummer()



Fun.OCLC = function ( access )
    -- Analysiere OCLC und gib sie formatiert zurück
    local r
    if access:match( "^[1-9][0-9]*$" ) then
        r = framedTemplate( "OCLC", { access } )
    else
        fehler( "Wert", "'OCLC'" )
        fire( "OCLC" )
        r = string.format( "OCLC %s", access )
    end
    return r
end -- Fun.OCLC()



Fun.PMC = function ( access )
    -- Analysiere PMC und gib sie formatiert zurück
    local start, s = access:match( "^(%a%a%a)%s*(%d+)$" )
    local r
    if start and start:upper() == "PMC" then
        fehler( "Wert", "'PMC' vor Nummer unerwünscht" )
    else
        s = access
    end
    if s:match( "^[1-9][0-9]*$" ) then
        r = framedTemplate( "PMC", { s } )
    else
        fehler( "Wert", "'PMC'" )
        fire( "PMID" )    -- Ja, PMID-Experte betreut auch PMC
        r = string.format( "PMC %s", access )
    end
    return r
end -- Fun.PMC()



Fun.PMID = function ( access )
    -- Analysiere PMID und gib sie formatiert zurück
    if not access:match( "^[1-9][0-9]*$" ) then
        fehler( "Wert", "'PMID'" )
        fire( "PMID" )
    end
    return string.format( "PMID %s", access )
end -- Fun.PMID()



Fun.URN = function ( access )
    -- Analysiere URN und gib sie formatiert zurück
    local URIutil = Zitation.fetch( "URIutil" )
    local r = URIutil.uriURN( "urn:" .. access )
    if r:find( "class=\"[^\"]*error", 1, true )  or
       not r:find( "//", 1, true ) then
        fehler( "Wert",  "'URN'=" .. access )
        fire( "URN" )
    end
    return r
end -- Fun.URN()



Fun.ZDB = function ( access )
    -- Analysiere ZDB und gib sie formatiert zurück
    local URIutil = Zitation.fetch( "URIutil" )
    if URIutil.isDNBvalid( access )  or
       URIutil.isDNBvalid( access:gsub( "-", "" ) ) then
        r = framedTemplate( "ZDB", { access:upper() } )
    else
        local s = "Zeitschriftendatenbank"
        fehler( "Wert", "'ZDB'" )
        fire( "ZDB" )
        r = string.format( "[[%s|ZDB]] %s%s",
                           s,
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
    end
    return r
end -- Fun.ZDB()



local function doiCheck( analyse )
    -- Prüfe Wikitext auf zulässige DOI-WikiLinks
    -- Parameter:
    --     analyse  -- string (downcased)
    local s = analyse
    local f = function()
                  local skip, s = s:match( "^(.*%[%[%s*doi:)(.+)$" )
                  if s then
                      local seek
                      seek, s = s:match( "^(.*)%]%](.*)$" )
                      if seek then
                          local URIutil = Zitation.fetch( "URIutil" )
                          if URIutil.mayURI( seek ) then
                              fehler( "Wert", "doi:DOI ungültig" )
                              seek = false
                          end
                      else
                          fehler( "Wert", "doi: nicht geschlossen" )
                      end
                      if not seek then
                          fire( "DOI" )
                          s = false
                      end
                  end
                  return s
              end
    while f() do
    end -- while
end -- doiCheck()



local function redundanz( analyse )
    -- Prüfe Angabe auf Redundanz mit anderen Parametern
    -- Parameter:
    --     analyse  -- string (downcased)
    for k, v in pairs( URLunwanted ) do
        if analyse:match( k ) then
            if ( v == "DNB"  or   v == "LCCN"  or   v == "OCLC" )  and
               ( feed( "id", "ISBN" )  or
                 feed( "id", "ISBNfalsch" )  or
                 feed( "id", "ISBNdefekt" ) ) then
                local s = string.format( "%s %s-URL redundant",
                                         "ISBN vorhanden", v )
                fehler( "Konflikt", s )
            else
                local s = string.format( "%s '%s=' %s",
                                         "Statt URL sollte etwas wie",
                                         v,
                                         "angegeben werden" )
                fehler( "Konflikt", s )
            end
        end
    end -- for k, v
    if analyse:find( "title=\"ctx_ver=Z39.88-2004", 1, true ) then
        fehler( "Konflikt", "Verschachtelte Zitationsvorlagen" )
    end
end -- redundanz()



local function bandNummer( area )
    -- Formatiere Angaben von Band und/oder Nummer
    -- Parameter:
    --     area  -- string, mit Name der Parametergruppe print/serie
    -- Rückgabewert: string mit Inhalt, oder nil
    local sB = feed( area, "Band" )
    local sN = feed( area, "Nummer" )
    local r
    if sB then
        local lead
        sB = fair( sB ):gsub( "&#%x+;", " " )
                       :gsub( "%s%s+", " " )
        if sB:sub( 1, 5 ) == "Band " then
            sB   = sB:sub( 6 )
            lead = true
        elseif sB:sub( 1, 3 ) == "Bd." then
            sB   = mw.text.trim( sB:sub( 4 ) )
            lead = true
        end
        if figure( sB ) then
            r = facet( "Band", sB )
        else
            if lead then
                sB = "Band " .. sB
            end
            r = sB:gsub( "(Band) (%d+)", facet )
        end
    end
    if sN then
        local lead
        sN = fair( sN ):gsub( "&#%x+;", " " )
                       :gsub( "%s%s+", " " )
        if sN:sub( 1, 7 ) == "Nummer " then
            sN   = sN:sub( 8 )
            lead = true
        elseif sN:sub( 1, 3 ) == "Nr." then
            sN   = mw.text.trim( sN:sub( 4 ) )
            lead = true
        end
        if figure( sN ) then
            sN = facet( "Nr.", sN )
        else
            if lead then
                sN = "Nr. " .. sN
            end
            sN = sN:gsub( "(Nr%.) (%d+)", facet )
        end
        if r then
            r = string.format( "%s, %s", r, sN )
        else
            r = sN
        end
    end
    return r
end -- bandNummer()



local function Kapitel( a )
    -- Formatiere Kapitelangabe
    local r = a
    if a:match( "^%d+$" ) then
        r = facet( "Kap.", a )
    end
    return r
end -- Kapitel()



local function ArtikelNr( aN, aS )
    -- Analysiere ArtikelNr
    if aS then
        fehler( "Konflikt", "Seitenzahl redundant wenn ArtikelNr" )
    end
    if not aN:match( "^%d+$" ) then
        fehler( "Wert", "'ArtikelNr'=" .. aN )
    end
end -- ArtikelNr()



local function Seiten( a )
    -- Analysiere Seitenzahl und formatiere
    local s, seiten = a:match( "^(%w+%.?)%s*(.+)$" )
    local r
    if s then
        local seek = "|s.|ss.|seite|seiten|page|pages|p.|pp.|"
        if seek:find( string.format( "|%s|", s:lower() ) ) then
            fehler( "Wert", "Seitenzahl mit unnötigem Zusatz" )
            seiten = mw.text.trim( seiten )
        else
            seiten = a
        end
    else
        seiten = a
    end
    if #seiten > 50 then
        -- URL? Google-Buch?
        local shrink = string.format( "[%%-0-9,;/ f%%.%s]",
                                      mw.ustring.char( 8211 ) )
        s = mw.ustring.gsub( seiten, shrink, "" )
        if #s > 10 then
            fehler( "Wert", "Seitenzahl unerklärlich lang" )
            r = a
        end
    end
    if not r then
        r = facet( "S.",  figures( seiten ) ):gsub( "%.</span>$",
                                                    "</span>." )
    end
    return r
end -- Seiten()



local function Spalten( a )
    -- Analysiere Spaltenangabe und formatiere
    local s, sp = a:match( "^(%w+%.?)%s*(.+)$" )
    if s then
        local seek = "|sp.|spalte|spalten|"
        if seek:find( string.format( "|%s|", s:lower() ) ) then
            fehler( "Wert", "Spaltenangabe mit unnötigem Zusatz" )
            sp = mw.text.trim( sp )
        else
            sp = a
        end
    else
        sp = a
    end
    return  facet( "Sp.", figures( sp ) ):gsub( "%.</span>$",
                                                "</span>." )
end -- Spalten()



local function Werktitel( a, amend, alien, abschluss )
    -- Formatiere einen Werktitel, das Sammelwerk, ggf. Reihe
    -- Parameter:
    --     a          -- string
    --     amend      -- string mit Ergänzung, oder nil
    --     alien      -- string mit Sprachcode, oder nil
    --     abschluss  -- true: Satzendezeichen sicherstellen
    -- Rückgabewert: string mit formatiertem Werktitel
    local Text  = Zitation.fetch( "Text" )
    local cite = mw.html.create( "cite" )
    local sep
    local r
    cite:css( "font-style", "italic" )
        :wikitext( Text.uprightNonlatin( a ) )
    faraway( cite, alien )
    r = tostring( cite )
    if amend then
        local s
        if Text.sentenceTerminated( a ) then
            sep = ""
        else
            sep = "."
        end
        r = string.format( "%s%s %s", r, sep, amend )
    end
    if abschluss  and
       ( ( not amend  and  not Text.sentenceTerminated( a ) )
         or   ( amend  and  not Text.sentenceTerminated( amend ) ) ) then
        r = r .. "."
    end
    return r
end -- Werktitel()



-- Einzelblöcke der Darstellung =========================================



local function resourceMeta( anfang )
    -- Ergänze Resultat um für einen Online-Abruf wichtigen Informationen
    --     anfang  -- boolean
    --                * true   -- runde Klammern
    --                            um .Format und .KBytes gesetzt
    --                * false  -- eckige Klammern
    --                            um .Format und .KBytes und .Abruf
    -- Rückgabewert: string mit führendem " " und Inhalt, oder ""
    local r = ""
    if feed( "www" ) then
        local sURL     = feed( "www", "URL" )
        local sWeblink = feed( "www", "Weblink" )
        local some     = ( sURL or sWeblink )
        local sAbruf   = feed( "www", "Abruf" )
        local sFormat  = feed( "www", "Format" )
        local sKBytes  = feed( "www", "KBytes" )
        if some then
            local sep = ""
            some = some:lower() .. " "
            if sFormat then
                local s = sFormat:upper()
                if s ~= "HTML" then
                    if s:find( "PDF%-?" ) then
                        r = "pdf"
                    else
                        r = sFormat
                    end
                end
            elseif some:find( "%Wpdf%W" ) then
                r = "pdf"
            end
            if r:lower() == "pdf"  and
               sWeblink  and
               some:find( " .*pdf" ) then
                r = ""
            end
            if r == "" then
                sKBytes = false
            else
                local docTypes = mw.text.split( r:upper(), " " )
                local s
                r   = ""
                sep = ""
                for i = 1, #docTypes do
                    s   = docTypes[ i ]
                    r   = string.format( "%s%s%s",
                                         r,  sep,  DocTypes[ s ] or s )
                    sep = " "
                end -- for i
                sep = "; "
            end
            if sKBytes then
                local scan  = "^(%d+[,%.]?%d*)%s*(%a*)$"
                local large = sKBytes:find( "[sic!]", 3, true )
                local lot, size, suffix
                sKBytes = fair( sKBytes )
                if large then
                    sKBytes = sKBytes:gsub( "%s*%[sic!%]%s*$", "" )
                end
                size, suffix = sKBytes:match( scan )
                if size then
                    local n
                    size = size:gsub( "%.", "" )
                    if size:find( "," ) then
                        n    = tonumber( size:gsub( ",", "." ), 10 )
                        size = string.format( "%1.1d", n )
                    else
                        n = tonumber( size )
                    end
                    if suffix then
                        if suffix == "" then
                            suffix = false
                        else
                            suffix = suffix:lower()
                            if suffix:match( "^mi?b%l*$" ) then
                                n      = n * 1000
                                suffix = false
                            elseif suffix:match( "^ki?b%l*$" ) then
                                suffix = false
                            end
                        end
                    end
                    if suffix then
                        fehler( "Wert", "Byte-Einheit nicht erkannt" )
                    else
                        if n > 1000 then
                            n      = math.ceil( n * 0.01 ) * 0.1
                            size   = string.format( "%.1f", n )
                            suffix = "MB"
                            lot    = ( n > KBytesMax )
                        else
                            suffix = "kB"
                        end
                    end
                    sKBytes = facet( size:gsub( "%.", "," ), suffix )
                    if lot then
                        sKBytes = string.format( "'''%s'''", sKBytes )
                        if not large then
                            fehler( "Wert", "zu viele MegaBytes" )
                        end
                    end
                else
                    fehler( "Wert", "KiloByte-Zahl nicht erkannt" )
                end
                r = string.format( "%s%s%s", r, sep, sKBytes )
                if large and not lot then
                    fehler( "Wert", "Fehlplatziertes [sic!]" )
                end
                sep = "; "
            end
            if not anfang and sAbruf and sWeblink then
                r = string.format( "%s%s%s",
                                   r, sep, Abrufdatum( sAbruf ) )
            end
            if r ~= "" then
                if anfang then
                    sep = " (%s)"
                else
                    sep = " &#91;%s&#93;"
                end
                r = string.format( sep, r )
            end
            redundanz( some )
        elseif sFormat or sKBytes or sAbruf then
            local s = feed( "bas", "Titel" )
            if not s  or  not s:find( "//" ) then
                fehler( "Problem",
                        "Dateiformat/Größe/Abruf nur wenn Weblink" )
            end
        end
    end
    return r
end -- resourceMeta()



local function autorHrsg()
    -- Ergänze Resultat um Personen zu Beginn der Zitation
    local sAutor = feed( "bas", "Autor" )
    local sHrsg  = feed( "bas", "Hrsg" )
    local sTyp   = feed( "leise", "Typ" )
    local lead
    if sTyp  and  sTyp ~= "wl" then
        fehler( "Wert",
                "'Typ' zurzeit nur 'Typ=wl' (Werkliste) unterstützt" )
        r = "wl"
    end
    if sHrsg then
        lead = ( not feed( "bas", "Werk" ) )
        findbar( sHrsg, "Hrsg" )
    end
    if sAutor or lead then
        local list = false
        if sAutor then
            sAutor = sAutor:gsub( "^#", "&#35;" ) -- nick, hashtag
                           :gsub( "^%*", "&#42;" )
            if sTyp ~= "wl" then
                fein( "", sAutor )
                list = true
            end
            if type( sAutor ) == "table" then
                sAutor = Zitation.citePerson( sAutor, false )
            end
            findbar( sAutor, "Autor" )
            if sHrsg  and  not feed( "bas", "Titel" ) then
                fehler( "Konflikt",
                        "Gleichzeitig 'Autor' und 'Hrsg' nur wenn auch 'Titel'" )
            end
        else
            fein( "",  Herausgeber( sHrsg ) )
            list = true
        end
        if list then
            fein( "&#58; ", "" )    -- " news:" wäre eine Wiki-URL
        end
    end
end -- autorHrsg()



local function erstAusgabe()
    -- Erstausgabe
    -- Rückgabewert: string
    local sJahr   = feed( "ed1", "Jahr" )
    local sOrt    = feed( "ed1", "Ort" )
    local sVerlag = feed( "ed1", "Verlag" )
    local r       = "Erstausgabe: "
    local sep
    if sVerlag then
        r   = r .. sVerlag
        sep = ","
        if feed( "bas", "Verlag" ) == sVerlag then
            fehler( "Konflikt",
                    string.format( "'%s' hat gleichen Wert wie '%s'",
                                   feed( "ed1", "Verlag", true ),
                                   feed( "bas", "Verlag", true ) ) )
        end
    else
        sep = ""
    end
    if sOrt then
        r = string.format( "%s%s %s", r, sep, sOrt )
        Ortsname( sOrt, "ed1", "Ort" )
        sep = " "
        -- bas.Verlag und ed1.Verlag dürfen in derselben Stadt sein
    end
    if sJahr then
        local jahr
        if sJahr:match( "^%d%d%d%d$" ) then
            if not future( sJahr ) then
                jahr = tonumber( sJahr )
            end
        end
        if jahr then
            local datum = feed( "bas", "Datum" )
            r = string.format( "%s%s %s", r, sep, sJahr )
            if type( datum ) == "table"   and
               datum.year   and   datum.year < jahr then
                fehler( "Konflikt",
                        string.format( "'%s' nach Neuausgabe",
                                       feed( "ed1", "Jahr", true ) ) )
            end
        else
            fehler( "Wert",
                    string.format( "'%s'=%s",
                                   feed( "ed1", "Jahr", true ),
                                   sJahr ) )
            fire( "Datum" )
        end
    end
    return r
end -- erstAusgabe()



local function originalPublikation()
    -- Originalpublikation; zulässige Parameterlogik noch unklar
    -- Rückgabewert: string mit Inhalt, oder false
    local r
    if feed( "orig" ) then
        local sTitel      = feed( "orig", "Titel" )
        local sTranslator = feed( "orig", "Translator" )
        if sTitel then
            local sprache = foreign( "orig", "Sprache" )
            local sOrt    = feed( "orig", "Ort" )
            local sJahr   = feed( "orig", "Jahr" )
            r = Werktitel( sTitel, false, sprache, true )
            if sOrt then
                r = string.format( "%s %s", r, sOrt )
                Ortsname( sOrt, "orig", "Ort" )
            end
            if sJahr then
                r = string.format( "%s %s", r, sJahr )
                if sJahr:match( "^%d%d%d%d$" ) then
                    if future( sJahr ) then
                        fehler( "Wert", "Originaljahr in der Zukunft" )
                    else
                        local jahr = tonumber( sJahr )
                        local datum = feed( "bas", "Datum" )
                        if type( datum ) == "table"   and
                           datum.year   and   datum.year < jahr then
                            fehler( "Konflikt",
                                    "Originaljahr nach Neuausgabe" )
                        end

                    end
                else
                    fehler( "Wert", "Originaljahr ist keine Jahreszahl" )
                end
            end
            if ( sOrt or sJahr )  and
               ( sJahr or not sOrt:match( "%.$" ) ) then
                r = r .. "."
            end
            if sprache then
                local Multilingual = Zitation.fetch( "Multilingual" )
                local s = Multilingual.format( sprache, "de", "m",
                                               false, false,
                                               Zitation.frame,
                                               " ", ", " )
                if s then
                    sprache = s
                elseif sprache:match( "^%l%l%l?$" ) then
                    fehler( "Wert",
                            "Unbekannter Sprachcode=" .. sprache )
                    fire( "Sprachcode" )
                    sprache = string.format( "<code>%s</code>", sprache )
--              elseif sprache:match( "^%l.+[ ,;/]" ) then
--                  fehler( "Wert",
--                          "Original-Sprachcode seltsam: " .. sprache )
                end
            else
                sprache = "Originaltitel"
            end
            r = string.format( "%s: %s", sprache, r )
            if sTranslator then
                r = string.format( "%s Übersetzt von %s",
                                   r, sTranslator )
            end
        elseif not sTranslator then
            fehler( "Pflicht", "Originaltitel fehlt" )
        end
    end
    return r
end -- originalPublikation()



local function titel()
    -- Ergänze Resultat um Titel
    local sTitel = feed( "bas", "Titel" )
    local sWerk  = feed( "bas", "Werk" )
    if sTitel then
        local stop = ""
        local sReihe    = feed( "serie", "Reihe" )
        local sTitelErg = feed( "bas",   "TitelErg" )
        local sURL      = feed( "www",   "URL" )
        local s = Werktitel( sTitel,
                             false,
                             feed( "bas", "Sprache" ),
                             sWerk  or  not sReihe  or  sTitelErg )
        local Text
        if sURL then
            local URLutil = Zitation.fetch( "URLutil" )
            if URLutil.isResourceURL( sURL ) then
                s    = s:gsub( "%[", "&#91;" )
                        :gsub( "%]", "&#93;" )
                sURL = sURL:gsub( "%[", "%5B" )
                           :gsub( "%]", "%5D" )
                s    = string.format( "[%s %s]%s",
                                      sURL, s, resourceMeta( true ) )
            else
                fehler( "Wert", "URL" )
            end
        end
        if sTitelErg then
            Text = Zitation.fetch( "Text" )
            if ( sWerk  or  not sReihe )   and
               not Text.sentenceTerminated( sTitelErg ) then
                stop = "."
            end
            s = string.format( "%s %s%s", s, sTitelErg, stop )
        end
        fein( "", s )
        if not sWerk then
            local sAutor = feed( "bas", "Autor" )
            local sHrsg  = feed( "bas", "Hrsg" )
            if sAutor and sHrsg then
                Text = Zitation.fetch( "Text" )
                if stop == "" then
                    local strip = Resultat:gsub( "</cite>$", "" )
                    if not Text.sentenceTerminated( strip ) then
                        stop = "."
                    end
                elseif sTitelErg then
                    stop = ""
                end
                stop = stop .. " "
                s    = Herausgeber( sHrsg, false, true )
                if not ( sReihe  or
                         Text.sentenceTerminated( s ) ) then
                    s = s .. "."
                end
                fein( stop, s )
            end
        end
    else
        if sWerk then
            fehler( "Pflicht", "Kein 'Titel'" )
        else
            fehler( "Pflicht", "Weder 'Titel' noch sonstiges Werk" )
        end
    end
end -- titel()



local function werk()
    -- Ergänze Resultat um Sammelwerk usw.
    local sWerk = feed( "bas", "Werk" )
    if sWerk then
        local start = " In: "
        local sHrsg = feed( "bas", "Hrsg" )
        local s     = Werktitel( sWerk,
                                 feed( "bas", "WerkErg" ),
                                 feed( "bas", "Sprache" ),
                                 not feed( "serie", "Reihe" ) )
        if sHrsg then
            s = string.format( "%s: %s",  Herausgeber( sHrsg ),  s )
        end
        if Resultat == "" then
            start = "In: "
        end
        fein( start, s )
    end
end -- werk()



local function auflage()
    -- Ergänze Resultat um Auflage
    local sAuflage = feed( "print", "Auflage" )
    if sAuflage then
        local s    = sAuflage:gsub( "Aufla?g?e?%.?$", "" )
        local lang = s:find( "d", 2, true )
        if lang then
            local sFR = mw.ustring.char( 233, 100, 46 )
            s = mw.ustring.gsub( s, "[éÉ]d%.?$", sFR )
            s = mw.ustring.gsub( s, "[éÉ]dition", sFR )
        end
        if s:match( "^%d+$" ) then
            s = s .. "."
        end
        if not ( s:find( "Aufl", 1, true )    or
                 ( lang   and
                   ( s:find( "ed.", 1, true )  or
                     mw.ustring.find( s, "éd.", 1, true ) ) ) ) then
            s = s .. " Auflage"
        end
        if not s:match( "%.$" ) then
            s = s .. "."
        end
        fein( " ", s )
    end
end -- auflage()



local function reihe()
    -- Ergänze Resultat um Angaben zur Reihe
    if feed( "serie" ) then
        local sReihe = feed( "serie", "Reihe" )
        if sReihe then
            local sBN   = bandNummer( "serie" )
            local sHrsg = feed( "serie", "Hrsg" )
            local s     = Werktitel( sReihe,
                                     false,
                                     feed( "bas", "Sprache" ),
                                     sBN )
            if sHrsg then
                s = string.format( "%s: %s",
                                   Herausgeber( sHrsg, true ),  s )
            end
            if sBN then
                s = string.format( "%s %s", s, sBN )
            end
            fein( " ",  string.format( "(=&#160;%s).", s ) )
        else
            local scream
            local flop = function ( au )
                             if feed( "serie", au ) then
                                 local s = feed( "serie", au, true )
                                 if scream then
                                     scream = scream .. ", "
                                 else
                                     scream = ""
                                 end
                                 scream = string.format( "%s'%s'",
                                                         scream, s )
                             end
                         end -- flop()
            flop( "Hrsg" )
            flop( "Band" )
            flop( "Nummer" )
            if scream then
                -- Muss in dieser Konstellation eigentlich immer sein
                fehler( "Pflicht",
                        scream .. " nur wenn Reihe angegeben" )
            end
        end
    end
end -- reihe()



local function bibliografischeAngaben()
    -- Ermittle die Aufzählung bibliografische Angaben
    -- Rückgabewert: string mit Inhalt, oder false
    local r       = ""
    local sep     = ""
    local datum   = feed( "bas", "Datum" )
    local sVerlag = feed( "bas", "Verlag" )
    local s, sOrt
    if feed( "print" ) then
        sOrt = feed( "print", "Ort" )
        if sOrt  and
           ( feed( "id", "ISSN" )  or  feed( "id", "ZDB" ) ) then
            sOrt = false
        end
        s = bandNummer( "print" )
        if s then
            r = s
            if s:find( "%.$" ) then
                sep = " "
            elseif sVerlag or sOrt then
                sep = ". "
            else
                sep = ", "
            end
        end
    end
    if sVerlag then
        r = string.format( "%s%s%s", r, sep, sVerlag )
        sep = ", "
    end
    if sOrt then
        s = sOrt:gsub( "[,/; ]+$", "" )
        r = string.format( "%s%s%s", r, sep, s )
        if s ~= sOrt then
            Zitation.fill( "print", "Ort", s )
        end
        Ortsname( sOrt, "print", "Ort" )
        sep = ", "
    end
    if datum then
        local o = datum
        if type( datum ) == "string" then
            local DateTime = Zitation.fetch( "DateTime" )
            o = DateTime( datum )
        end
        if type( o ) == "table"  and  o.year then
            Zitation.fill( "bas", "Datum", o )
            if future( o )   or
               ( o.zone  and  not o.hour ) then
                o = false
            elseif feed( "id", "ISBN" ) then
                s = tostring( o.year )
            elseif feed( "www", "URL" ) then
                s = o:format( "T._Monat JJJJ hh:mm:ss Zone" )
            else
                s = o:format( "T._Monat JJJJ" )
            end
        else
            o = false
        end
        if not o then
            if type( datum ) == "string" then
            --  s = "Datum: " .. datum    -- LEGACY
                s = datum
            else
                s = "Datum??"
            end
            fehler( "Wert", s )
            fire( "Datum" )
        end
        if sOrt then
            sep = " "
        end
        r = string.format( "%s%s%s", r, sep, s )
        sep = ", "
    end
    if feed( "id" ) then
        local lazy        = false    -- gültige ISBN bekannt
        local sID         = feed( "id", "ID" )
        local sISBN       = feed( "id", "ISBN" )
        local sISBNfalsch = feed( "id", "ISBNfalsch" )
        local sISBNdefekt = feed( "id", "ISBNdefekt" )
        local fiddler     =
                  function( at )
                      local s = feed( "id", at )
                      if s then
                          if lazy  and
                             not  ( at == "ISSN"  or
                                    at == "ISSNfalsch" ) then
                              s = "redundant, da ISBN gegeben"
                              s = string.format( "'%s' %s",
                                                 feed( "id", at, true ),
                                                 s )
                              fehler( "Konflikt", s )
                          else
                              s   = Fun[ at ]( s )
                              r   = string.format( "%s%s%s",
                                                   r, sep, s )
                              sep = ", "
                          end
                      end
                  end
        if sISBN and sISBNfalsch then
            s = string.format( "'%s' und '%s' %s",
                               feed( "id", "ISBN", true ),
                               feed( "id", "ISBNfalsch", true ),
                               "nicht gleichzeitig angeben" )
            fehler( "Konflikt", s )
        elseif sISBN or sISBNfalsch then
            local mode
            if sISBNfalsch then
                mode = -1
            end
            s, lazy = Zitation.ISBN( sISBN or sISBNfalsch,  mode )
            r = string.format( "%s%s%s", r, sep, s )
            sep = ", "
        end
        if sISBNdefekt then
            s = Zitation.ISBN( sISBNdefekt, #sISBNdefekt )
            r = string.format( "%s%s%s", r, sep, s )
            sep = ", "
        end
        fiddler( "ISSN" )
        fiddler( "ISSNfalsch" )
        fiddler( "DNB" )
        fiddler( "LCCN" )
        fiddler( "Lizenznummer" )
        fiddler( "OCLC" )
        fiddler( "ZDB" )
        if sID then
            r = string.format( "%s%s%s", r, sep, sID )
            sep = ", "
            redundanz( sID )
        end
    end
    if feed( "fragment" ) then
        local sArtikelNr  = feed( "fragment", "ArtikelNr" )
        local sFundstelle = feed( "fragment", "Fundstelle" )
        local sKapitel    = feed( "fragment", "Kapitel" )
        local sSeiten     = feed( "fragment", "Seiten" )
        local sSpalten    = feed( "fragment", "Spalten" )
        if sKapitel then
            r = string.format( "%s%s%s", r, sep, Kapitel( sKapitel ) )
            sep = ", "
        end
        if sSeiten then
            r = string.format( "%s%s%s", r, sep, Seiten( sSeiten ) )
            sep = ", "
        end
        if sSpalten then
            r = string.format( "%s%s%s", r, sep, Spalten( sSpalten ) )
            sep = ", "
        end
        if sArtikelNr then
            ArtikelNr( sArtikelNr, sSeiten )
            r = string.format( "%s%s%s", r, sep, sArtikelNr )
            sep = ", "
        end
        if sFundstelle then
            r = string.format( "%s%s<span style='%s'>%s</span>",
                               r,
                               sep, "white-space:nowrap", sFundstelle )
            sep = ", "
        end
    end
    if feed( "id" ) then
        local docCodes   = { "DOI",
                             "PMID",
                             "PMC",
                             "arXiv",
                             "bibcode",
                             "JSTOR",
                             "URN" }
        local sign
        for i = 1, #docCodes do
            s    = docCodes[ i ]
            sign = feed( "id", s )
            if sign then
                r   = string.format( "%s%s%s", r, sep, Fun[ s ]( sign ) )
                sep = ", "
            end
        end -- for i
    end
    if r == "" then
        r = false
    end
    return r
end -- bibliografischeAngaben()



local function klammerInhalt()
    -- Inhalt der Klammer am Ende der bibliografischen Angaben
    -- Rückgabewert: string mit Inhalt, oder false
    local r          = ""
    local sep        = ""
    local sKommentar = feed( "bas",   "Kommentar" )
    local sOrig      = originalPublikation()
    local sprache    = feed( "bas",   "Sprache" )
    local sUmfang    = feed( "print", "Umfang" )
    if sprache  and  sprache ~= "de" then
        local Multilingual = Zitation.fetch( "Multilingual" )
        sprache = Multilingual.format( sprache, "de", "m",
                                       false, false,
                                       Zitation.frame,
                                       " ", ", " )
        r       = string.format( "%s%s%s", r, sep, sprache )
        sep     = ", "
    end
    if sUmfang then
        if not mw.ustring.find( sUmfang, "%a" ) then
            -- "14, 234"
            sUmfang = string.format( "%s&#160;S.", sUmfang )
        end
        r   = string.format( "%s%s%s", r, sep, sUmfang )
        sep = ", "
    end
    local sanitize = feed( "www", "Weblink" )
    if sanitize and sanitize:find( "InternetArchiveBot", 1, true ) then
        -- 2018-12-04
        mw.addWarning( "<b>Unerlaubte Bot-Aktion</b><br /> " ..
                       sanitize )
        fein( "", "[[Kategorie:Wikipedia:InternetArchiveBot-Fehler]]")
        Zitation.o.www = false
-- Wiederholt ZR-widrig den Titel der Website u. a.  171587445/183321268
    end
    if feed( "www" ) then
        local sWeblink = feed( "www", "Weblink" )
        if sWeblink then
            local WLink = Zitation.fetch( "WLink" )
            local show  = WLink.getWeblink( sWeblink )
            local spec  = resourceMeta( false )
            show = show:gsub( " www%d*%.", " " )
            r    = string.format( "%s%s%s%s",
                                  r, sep, show, spec )
            if spec == "" then
                sep = " &#8211; "
            else
                sep = " "
            end
            if sWeblink:find( "</cite>", 1, true )   or
               sWeblink:find( "class=\"cite\"", 1, true ) then
                fehler( "Konflikt",
                        "Zitationsvorlage rekursiv eingebunden" )
                fire( "Parameter" )
                -- Langfristig wirkungslos; greift nur vorübergehend
                -- wenn IQ noch reine Vorlage,
                -- oder cite als reine Vorlage.
                -- Nach vollständiger Umsetzung
                -- sperrt sich aber das System,
                -- weil Modul:Zitation
                -- dann rekursiv aufgerufen werden würde.
            end
        elseif feed( "www", "URL" ) then
            local sAbruf = feed( "www", "Abruf" )
            if sAbruf then
                r   = string.format( "%s%s%s",
                                     r, sep, Abrufdatum( sAbruf ) )
                sep = ", "
            end
        else
            local s = feed( "bas", "Titel" )
            if not s  or  not s:find( "//", 1, true ) then
                fehler( "Problem",
                        "Dateiformat/Größe/Abruf nur bei externem Link" )
            end
        end
    end
    if sOrig then
        r = string.format( "%s%s%s", r, sep, sOrig )
        if feed( "orig", "Translator" ) then
            sep = ", "
        else
            sep = " "
        end
    end
    if feed( "ed1" ) then
        r   = string.format( "%s%s%s", r, sep, erstAusgabe() )
        sep = ", "
    end
    if sKommentar then
        r = string.format( "%s%s%s", r, sep, sKommentar )
        sKommentar = mw.ustring.lower( sKommentar )
        redundanz( sKommentar )
        if sKommentar:find( "[[%s*doi:", 1, true ) then
           doiCheck( sKommentar )
        end
    end
    if r == "" then
        r = false
    end
    return r
end -- klammerInhalt()



local function endBlock()
    -- Ergänze Resultat um bibliografische Angaben, Klammer sowie Zitat.
    -- Komma-getrennte Aufzählung, die mit Punkt abgeschlossen wird,
    -- oder Zitat wird nachgestellt.
    local sZitat = feed( "bas", "Zitat" )
    local sep    = ""
    local s      = bibliografischeAngaben()
    if s then
        fein( " ", s )
        if not s:match( "%.$" ) then
            sep = "."
        end
    end
    s = klammerInhalt()
    if s then
        if Resultat:find( "%)$" ) then
            -- Irgendwas zuvor endet auf eine runde Klammer.
            sep = " &#8211; ("
        else
            sep = " ("
        end
        fein( sep,  s .. ")" )
        sep = "."
    end
    if sep ~= "" then
        -- Aufzählung enthält zumindest ein Element
        if sZitat then
            fein( ":", "" )
        elseif not Resultat:match( "%.$" ) then
            fein( ".", "" )
        end
    end
    if sZitat then
        local sprache = feed( "bas", "Sprache" )
        local Text    = Zitation.fetch( "Text" )
        local story   = Text.quoteUnquoted( sZitat, sprache )
        if sprache  and  sprache ~= "de" then
            local q = mw.html.create( "span" )
            faraway( q, sprache )
            q:wikitext( story )
            story = tostring( q )
        end
        fein( " ", story )
    end
end -- endBlock()



local function coins()
    -- Ergänze Resultat um COinS, wenn gewünscht
    local COinS = feed( "coins" )
    if COinS then
        local std = "book"
        local pars
        if type( COinS ) == "table" then
            pars = COinS
        elseif feed( "bas" ) then
            local datum    = feed( "bas",      "Datum" )
            local sAutor   = feed( "bas",      "Autor" )
            local sKapitel = feed( "fragment", "Kapitel" )
            local sTitel   = feed( "bas",      "Titel" )
            local sWerk    = feed( "bas",      "Werk" )
            local stick
            pars = { }
            if sWerk then
                if feed( "print",    "Nummer" )  or
                   feed( "id",       "ISSN" )  or
                   feed( "id",       "ISSNfalsch" )  or
                   feed( "id",       "ZDB" )  or
                   feed( "fragment", "ArtikelNr" ) then
                    pars.genre  = "journal"
                    pars.jtitle = sWerk
                    std         = "journal"
                else
                    pars.genre  = "book"
                    pars.btitle = sWerk
                end
                pars.atitle = sTitel
            elseif sKapitel then
                pars.genre  = "bookitem"
                pars.btitle = sTitel
                pars.atitle = sKapitel
            else
                pars.genre  = "book"
                pars.btitle = sTitel
            end
            if type( datum ) == "table" then
                pars.date = datum:format( "ISO" )
            end
            if sAutor then
                if type( sAutor ) == "table" then
                    stick = Zitation.citePerson( sAutor, true )
                else
                    pars.au = flat( sAutor, 3, true )
                end
            end
            pars.pub   = feed( "bas",      "Verlag" )
            pars.pages = feed( "fragment", "Seiten" )
            if feed( "id" ) then
                pars.isbn = feed( "id", "ISBN" )  or
                            feed( "id", "ISBNfalsch" )  or
                            feed( "id", "ISBNdefekt" )
                pars.issn = feed( "id", "ISSN" )  or
                            feed( "id", "ISSNfalsch" )
                pars.oclc = feed( "id", "OCLC" )
                pars.doi  = feed( "id", "DOI" )
                pars.pmc  = feed( "id", "PMC" )
                pars.pmid = feed( "id", "PMID" )
            end
            if feed( "print" ) then
                pars.edition = feed( "print", "Auflage" )
                pars.issue   = feed( "print", "Nummer" )
                pars.place   = feed( "print", "Ort" )
                pars.volume  = feed( "print", "Band" )
            end
            pars.series = feed( "serie", "Reihe" )
        end
        if pars then
            fein( "",  Zitation.COinS( pars, false, stick ),  std )
        end
    end
end -- coins()



-- Exportierte Funktionen ===============================================



Zitation.failure = function ( alert, always )
    -- Ausgabe von Fehlern mit class=error
    -- Parameter:
    --     alert   -- string, mit Fehlerliste, oder nil
    --     always  -- do not hide: boolean, or nil
    -- Rückgabewert: string, ggf. mit Fehlermeldung
    local r
    if alert then
        local TemplUtl = Zitation.fetch( "TemplUtl" )
        local light    = feed( "leise", "leiser" )  and  not always
        local self     = feed( "leise", "Vorlage" )  or  Selbst
        r = string.format( "'''Fehler in [[%s]]''' &#8211; %s",
                           self, alert )
        r = TemplUtl.failure( r,  not light, false, Zitation.frame )
    else
        r = ""
    end
    return r
end -- Zitation.failure()



Zitation.fault = function ( a, always, auxilary )
    -- Formatiere Fehler als teils ausgeblendet
    -- Parameter:
    --     a          -- string, mit Fehlermeldung
    --     always    -- true, wenn nicht zu unterdrücken
    --     auxilary  -- string oder nil, mit tooltip
    -- Rückgabewert:
    --     string, ggf. mit umschließendem HTML-Element
    local r
    if not always  and
       feed( "leise", "leiser" )  and
       mw.site.server == "//de.wikipedia.org" then
        if fading() then
            local e = mw.html.create( "span" )
            if auxilary then
                e:attr( { title  = auxilary } )
            end
            e:addClass( "Zitationsfehler Zitationswartung" )
             :css( "display", "none" )
             :wikitext( a )
            r = tostring( e )
        end
    end
    return r or a
end -- Zitation.fault()



Zitation.fetch = function ( assigned, acquire )
    -- Binde Modul ein
    -- Parameter:
    --     assigned  -- string mit Name
    --                  "arXiv"
    --                  "bibcode"
    --                  "DateTime"
    --                  "JSTOR"
    --                  "Multilingual"
    --                  "TemplUtl"
    --                  "Text"
    --                  "URIutil"
    --                  "URLutil"
    --                  "WLink"
    --     acquire   -- string mit abweichendem Modulnamen, oder false
    -- Rückgabewert: table des Moduls
    -- error: Modul nicht gefunden
    local r
    if Zitation.extern then
        r = Zitation.extern[ assigned ]
    else
        Zitation.extern = { }
    end
    if not r then
        local s = assigned
        local lucky, g
        if acquire then
            s = acquire
        end
        lucky, g = pcall( require, "Module:" .. s )
        if type( g ) == "table" then
            r = g[ assigned ]()
            Zitation.extern[ assigned ] = r
        else
            fehler( "Modul", g )
            error( string.format( "Zitation.fetch(%s) %s", s, g ) )
        end
    end
    return r
end -- Zitation.fetch()



Zitation.figure = function ( adjust )
    -- Bilde Zahlenwert
    -- Parameter:
    --     adjust  -- Wert beliebigen Typs
    -- Rückgabewert:
    --     Numerischer Wert, notfalls 0
    local r
    local s = type( adjust )
    if s == "string" then
        r = tonumber( adjust ) or 0
    elseif s == "number" then
        r = adjust
    else
        r = 0
    end
    return r
end -- Zitation.figure()



Zitation.fill = function ( area, access, assign, alias )
    -- Parameterkomponente zuweisen
    -- Parameter:
    --     area    -- string, mit Name der Parametergruppe
    --     access  -- string, mit Name der Komponente
    --     assign  -- Parameterwert
    --     alias   -- string, mit Name des Benutzerparameters, oder nil
    if not Zitation.o then
        Zitation.o = { }
    end
    if type( Zitation.o[ area ] ) ~= "table" then
        Zitation.o[ area ] = { }
    end
    Zitation.o[ area ][ access ] = { s = alias    or
                                         string.format( "%s.%s",
                                                        area, access ),
                                     v = assign }
end -- Zitation.fill()



Zitation.filler = function ( args, assign )
    -- Parameterkomponenten zuweisen
    -- Parameter:
    --     args    -- Zfilter.object,
    --                mit Zuweisungen nach Vorlagenparametername
    --     assign  -- table, mit Transformation in neutrales Datenmodell
    local g, r, value
    if not Zitation.o then
        Zitation.o = { }
    end
    for k, v in pairs( assign ) do
        value = args{ k }
        if value then
            g = v[ 1 ]
            if not Zitation.o[ g ] then
                Zitation.o[ g ] = { }
            end
            Zitation.o[ g ][ v[ 2 ] ] = value
        end
    end -- for k, v
end -- Zitation.filler()



Zitation.filter = function ( args, allowed )
    -- Analysiere Argumentenliste und gleiche mit erlaubten Namen ab
    -- Parameter:
    --     args     -- table, mit aktuellen Werten
    --     allowed  -- table, mit erlaubten Namen, zugewiesen:
    --                        true   -- Nur diese Namensvariante bekannt
    --                        table  -- Namensvariationen
    --                                  Jeder Wert:
    --                                  true   -- unerwünscht, Meldung
    --                                  table  -- Details
    --                                  table  -- low=true: Keine Meldung
    -- Rückgabewerte:
    --     table, mit gefilterten Werten, nach Parametername
    --            Zfilter object
    --            Jede Komponente:
    --                 index-Zugriff:
    --                     string, mit Parameterwert, kein leerer string
    --                 get-Zugriff:
    --                     table, mit
    --                            s=Orginal-Parametername
    --                            v=Parameterwert, nicht leer
    local signatur = "__Zfilter"
    local meta     = { }
    local r        = { [ signatur ] = { } }
    local discard  = false
    local doubled  = false
    local un       = false
    local d, lapsus
    meta.__call     = function ( self, arglist )
                          -- Antwort auf:  Tabelle{ ... }
                          return self[ signatur ][ arglist[ 1 ] ]
                      end
    meta.__index    = function ( self, access )
                          -- Antwort auf:  ... = Tabelle[x]
                          local e = self[ signatur ][ access ]
                          if type( e ) == "table" then
                              e = e.v
                          end
                          return e
                      end
    meta.__newindex = function ( self, access, assign )
                       -- Antwort auf:  Tabelle[x] = ...
                          local put = assign
                          if assign  and
                             ( type( assign ) ~= "table"   or
                               not assign.v ) then
                              put = { s=access, v=assign }
                          end
                          self[ signatur ][ access ] = put
                          return
                      end
    setmetatable( r, meta )
    for s, v in pairs( args ) do
        d = allowed[ s ]
        if d then
            lapsus = false
            if type( d ) == "table" then
                for dk, dv in pairs( d ) do
                    if args[ dk ] then
                        if not doubled then
                            doubled = { }
                        end
                        if not doubled[ dk ] then
                            doubled[ tostring( s ) ] = dk
                        end
                    end
                end
            else
                d = false
            end
        elseif type( s ) == "string" then
            if d == false then
                if not discard then
                    discard = { }
                end
                table.insert( discard, s )
            else
                lapsus = true
            end
        else
            lapsus = true
            if v then
                if mw.text.trim( v ) == "" then
                    fehler( "Format", "Pipe '|' zu viel" )
                end
                v = false
            end
            s = tostring( s )
        end
        if lapsus then
            if not un then
                un = { }
            end
            un[ s ] = true
        end
        if v == "" then
            v = false
        end
        if v then
            r[ s ] = v
            if v:find( "'''", 1, true )   and
               type( s ) == "string" then
                fehler( "Wert",
                        string.format( "'%s' mit Wikisyntax", s ) )
                r[ s ] = mw.text.encode( r[ s ] )
            end
        end
    end -- for s, v
    if un then
        local down      = { }
        local scream    = false
        local undesired = false
        local unknown   = false
        local light, s, sa
        for k, v in pairs( allowed ) do
            s = mw.ustring.lower( k )
            down[ s ] = { standard=k }
            if type( v ) == "table" then
                for ka, va in pairs( v ) do
                    sa = mw.ustring.lower( ka )
                    if type( va ) == "table" then
                        va.standard = k
                    else
                        va = { standard=k }
                    end
                    down[ sa ] = va
                end
            end
        end -- for k, v
        for k, v in pairs( un ) do
            if type( k ) == "string" then
                s = mw.ustring.lower( k )
                d = down[ s ]
            else
                d = false
            end
            if d then
                if type( d ) == "table" then
                    light = d.low
                    s     = d.standard
                else
                    light = false
                end
                if r[ s ] then
                    if not doubled then
                        doubled = { }
                    end
                    if not doubled[ s ] then
                        doubled[ k ] = s
                    end
                else
                    r[ s ] = r{ k }
                    r[ k ] = nil
                    if not light then
                        if not undesired then
                            undesired = { }
                        end
                        undesired[ k ] = s
                    end
                end
            else
                if not unknown then
                    unknown = { }
                end
                unknown[ k ] = true
            end
        end -- for k, v
        if unknown then
            down = { }
            for k, v in pairs( allowed ) do
                if type( v ) == "table" then
                    sa = mw.ustring.lower( k )
                    for ka, va in pairs( v ) do
                        sa = mw.ustring.lower( ka )
                        if type( va ) == "table" then
                            va.standard = k
                        else
                            va = { standard=k }
                        end
                        down[ sa ] = va
                    end
                end
            end -- for k, v
            for k, v in pairs( unknown ) do
                if type( k ) == "string" then
                    s = mw.ustring.lower( k )
                    d = down[ s ]
                    if d then
                        if type( d ) == "table" then
                            light = d.low
                        else
                            light = false
                        end
                        s = d.standard
                        if r[ s ] then
                            if not doubled then
                                doubled = { }
                            end
                            doubled[ k ] = s
                        else
                            r[ s ] = r{ k }
                            r[ k ] = nil
                            if not light then
                                if not undesired then
                                    undesired = { }
                                end
                                undesired[ k ] = d.standard
                            end
                        end
                    else
                        if scream then
                            scream = scream .. ", "
                        else
                            scream = "Unbekannte Parameter: "
                        end
                        scream = scream .. k
                    end
                end
            end -- for k, v
            fehler( "Konflikt", scream )
            scream = false
        end
        if undesired then
            for k, v in pairs( undesired ) do
                if scream then
                    scream = scream .. ", "
                else
                    scream = ""
                end
                scream = string.format( "%s '%s' ist '%s'",
                                        scream, k, v )
            end -- for k, v
            fehler( "Name", scream )
            scream = false
        end
    end
    if doubled then
        for k, v in pairs( doubled ) do
            if scream then
                scream = scream .. ","
            else
                scream = "Parameterwerte gedoppelt: "
            end
            scream = string.format( "%s '%s' ./. '%s'",
                                    scream, k, v )
        end -- for k, v
        fehler( "Konflikt", scream )
        scream = false
    end
    if discard then
        for k, v in pairs( discard ) do
            fehler( "Entfernen",  v .. "=" )
        end -- for k, v
    end
    return r
end -- Zitation.filter()



Zitation.format = function ()
    -- Generiere Zitation
    -- Rückgabewert:
    --     1  -- string mit Vorlagenresultat
    --     2  -- string mit Fehlermeldung(en) und -kategorien, oder false
    local s
    Resultat = ""
    foreign( "bas", "Sprache" )
    autorHrsg()
    titel()       -- Schließt mit Punkt etc.
    werk()        -- Schließt mit Punkt etc.
    reihe()
    auflage()     -- Schließt mit Punkt
    endBlock()    -- Schließt mit Punkt
    coins()
    s = fehlerliste()
    if s == "" then
        s = false
    end
    return Resultat, s
end -- Zitation.format()



Zitation.COinS = function ( args, assign, already )
    -- Create string with COinS <span>
    -- Parameter:
    --     args     -- table, with COinS components
    --     assign   -- optional string, with ID
    --     already  -- optional string, with preformatted &sequence
    -- Returns HTML element string
    local Text  = Zitation.fetch( "Text" )
    local WLink = Zitation.fetch( "WLink" )
    local rft   = { }
    local site  = mw.site.server:gsub( "^%l*:?//", "" )
    local s, sub, v
    if assign then
        sub = assign
    else
        if args.genre then
            sub = args.genre
        else
            sub = "book"
        end
    end
    if args.isbn then
        args.isbn = args.isbn:gsub( "-", "" ):upper()
    end
    s = string.format( "%s%s%s",
                       "ctx_ver=Z39.88-2004",
                       "&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3A",
                       sub )
    if not args.genre then
        s = s .. "&rft.genre=book"
    end
    if type( assign ) == "string" then
        sub = assign
    else
        sub = mw.title.getCurrentTitle().fullText
    end
    s = string.format( "%s&rfr_id=info:sid/%s:%s",
                       s,
                       site,
                       mw.uri.encode( sub ) )
    if already then
        s = s .. already
    end
    for k, v in pairs( args ) do
       table.insert( rft, k )
    end -- for k, v
    table.sort( rft )
    for i = 1, #rft do
        sub = rft[ i ]
        v   = args[ sub ]
        if type( v ) == "table" then
            if type( v.tostring ) == "function" then
                v = v.tostring()
            end
        end
        if type( v ) == "string" then
            v = mw.text.killMarkers( v )    -- <math>
            v = mw.uri.encode( WLink.getPlain( Text.getPlain( v ) ) )
                             :gsub( "%%E2%%80%%93", "-" )
            s = string.format( "%s&rft.%s=%s", s, sub, v )
        end
    end -- for i
    s = string.format( "<span class=\"Z3988\" title=\"%s\" %s>%s</span>",
                       s,
                       "style='display:none'",
                       "&#160;" )
    return s
end -- Zitation.COinS()



Zitation.ISBN = function ( access, accept, alert )
    -- Create string with formatted ISBN
    -- Parameter:
    --     access   -- string, with presumable ISBN
    --     accept   -- optional number, whether invalid data is permitted
    --                  0, nil   -- require valid ISBN
    --                 -1        -- ignore invalid check digit
    --                 other     -- other, e.g. number of digits
    --     alert    -- optional string, with maintenance category title
    -- Returns:
    --     1  -- string, for display
    --     2  -- true, if conditions not matched
    local URIutil = Zitation.fetch( "URIutil" )
    local mode  = accept or 0
    local isbn, lapsus, legal, lethal, r
    if mode == -1 then
        legal, isbn = URIutil.isISBN( access )
        if legal then
            if URIutil.isISBNvalid( access ) then
                fehler( "Wert", "'ISBN' ist nicht formal falsch" )
            else
                lapsus = true
            end
        end
    elseif mode == 0 then
        legal, isbn = URIutil.isISBNvalid( access )
    else
        legal, isbn  = URIutil.isISBN( access )
        if isbn == -1 then
            lapsus = true
            legal  = true
            lethal = true
        end
    end
    if legal then
        if lethal then
            r = URIutil.linkISBN( access, true, true, true, alert )
        else
            r = "ISBN " .. URIutil.formatISBN( access, isbn )
        end
        if lapsus then
            local plus = mw.html.create( "small" )
            local show, story
            if lethal then
                show  = "defekt"
                story = "WP:ISBNdefekt"
            else
                show  = "formal falsch"
                story = "WP:ISBNformalFalsch"
            end
            if story then
                show = string.format( "[[%s|%s]]", story, show )
            end
            show = string.format( "(%s)", show )
            plus:addClass( "ISBN-bad-code" )
                :css( "white-space", "nowrap" )
                :wikitext( show )
            r = string.format( "%s&#160;%s", r, tostring( plus ) )
        end
    else
        r = string.format( "ISBN %s%s",
                           access,
                           Zitation.fault( "(?!)", true, "ungültig" ) )
        fehler( "Wert", "'ISBN'" )
        fire( "ISBN" )
    end
    return r, legal
end -- Zitation.ISBN()



-- Export ===============================================================

local p = { }

p.Endpunkt = function ( frame )
    -- LEGACY für Vorlage:Internetquelle
    local r = ""
    local s = frame.args.titel
    if s then
        local Text = Zitation.fetch( "Text" )
        if Text.sentenceTerminated( s ) then
            r = ""
        else
            r = "."
        end
    end
    return r
end -- p.Endpunkt


p.TitelFormat = function ( frame )
    -- LEGACY für Vorlage:Internetquelle
    local r = ""
    local s = frame.args.titel
    if s then
        local Text = Zitation.fetch( "Text" )
        if Text.sentenceTerminated( s ) then
            r = s
        else
            r = s .. "."
        end
        r = string.format( "<i>%s</i>", r )
    end
    return r
end -- p.TitelFormat


p.COinS_Template = function ( frame )
    local l, r = pcall( Zitation.COinS, frame:getParent().args )
    return r
end -- p.COinS_Template


p.ISBN = function ( frame )
    local mode = frame.args[ 2 ]
    if mode then
        mode = tonumber( mode )
    end
    if frame.args.template then
        Selbst = frame.args.template
    end
    local l, r = pcall( Zitation.ISBN,
                        frame.args[ 1 ],
                        mode,
                        frame.args.link )
    return r
end -- p.ISBN


function p.failsafe()
    return Zitation.serial
end


p.Zitation = function ()
    return Zitation
end -- p.Zitation

return p