Modul:Zitation

aus Wikipedia, der freien Enzyklopädie
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dieses Modul ist in über 300.000 Seiten eingebunden.

Jede Veränderung an dieser Seite zwingt die Server zum Neuaufbau aller dieser Seiten.

Die Entwicklung erfolgt in der BETA-Wikipedia und Änderungen werden dort zuerst erprobt, bevor sie hierher übertragen werden. Zwischenzeitliche Bearbeitungen hier werden dabei überschrieben.

--PerfektesChaos 18:07, 15. Mai 2016 (CEST)


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