Zum Inhalt springen

Modul:TransText

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

Modul: Dokumentation

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


local TransText = { suite  = "TransText",
                    serial = "2019-01-22",
                    item   = 0 }
local Config = {
    errBaseInvalid   = { en = "Base code invalid",
                         de = "Ausgangscode ungültig" },
    errBaseMissing   = { en = "Base code missing",
                         de = "Ausgangscode fehlt" },
    errBaseUnknown   = { en = "Base code unknown:",
                         de = "Ausgangscode unbekannt:" },
    errCompInvalid   = { en = "Definition invalid",
                         de = "Definition ungültig" },
    errCompMissing   = { en = "Definition missing",
                         de = "Definition fehlt" },
    errDefMissing    = { en = "Definition module missing",
                         de = "Definitionsmodul fehlt" },
    errTargetMissing = { en = "Target code missing",
                         de = "Zielcode fehlt" },
    errTextCoding    = { en = "Wrong encoding of base text",
                         de = "Schriftzeichen im Ausgangstext falsch" },
    errTextMissing   = { en = "Text missing",
                         de = "Ausgangstext fehlt" },
    errTransCoding   = { en = "Wrong encoding in result text",
                         de = "Schriftzeichen im Ergebnistext falsch" },
    errMissing       = { en = "Missing parameter",
                         de = "Parameter fehlt" },
    errUnkown        = { en = "Unkown parameter:",
                         de = "Parameter unbekannt:" }
               }
local Query = { slang    = "und",
                script   = false,
                template = { ["@"] = "lang",
                             ["#"] = "1",
                             ["*"] = "2" }
              }
local Data



local function Feed( all )
    local r = { }
    local val
    for k, v in pairs( all ) do
        if type( k ) == "number"  or  k:match( "^%d" ) then
            for ik, iv in pairs( all ) do
                if type( iv ) == "table" then
                    val = Feed( iv )
                else
                    val = iv
                end
                table.insert( r, val )
            end -- for ik, iv
            break -- for k, v
        else
            if type( v ) == "table" then
                r[ k ] = Feed( v )
            else
                r[ k ] = v
            end
        end
    end -- for k, v
    return r
end -- Feed()



local function Fellow()
    -- Attach Multilingual/scripting library
    -- Throws error, if library not existing
    local r
    if not TransText.MultiScript then
        local lucky, got = pcall( require,
                                  "Module:Multilingual/scripting" )
        if type( got ) == "table"  and
           type( got.MultiScript ) == "function" then
            TransText.MultiScript = got.MultiScript()
        else
            error( "Library MultiScript unavailable" )
        end
    end
    if type( TransText.MultiScript ) == "table" then
        r = TransText.MultiScript
    end
    return r
end -- Fellow()



local function Fetch( at )
    -- Attempt to load Data
    -- Precondition:
    --    at   -- string, page name
    -- Returns table, or not if invalid
    -- Throws error, if not existing
    local d = mw.loadData( at )
    local r
    if type( d ) == "table"  and
       type( d.data ) == "table" then
        r = Feed( d.data )
    else
        r = { }
    end
    return r
end -- Fetch()



local function Frame()
    -- Fetch current frame
    -- Returns frame
    if not Query.frame then
        Query.frame = mw.getCurrentFrame()
    end
    return Query.frame
end -- Frame()



local function facility()
    -- Fetch current site language
    -- Returns language code
    local r
    if Data then
        r = Data.stdLang
    end
    if not r then
        r = mw.language.getContentLanguage():getCode()
        if Data then
            Data.stdLang = r
        end
    end
    return r
end -- facility()



local function factory( apply )
    -- Localization of messages
    --     apply  -- string, with message key
    -- Returns message text; at least english
    local entry = Config[ apply ]
    local r
    if entry then
        r = entry[ facility() ]
        if not r then
            r = entry.en
        end
    else
        r = string.format( "????.%s.????", apply )
    end
    return r
end -- factory()



local function failure( alert, about )
    -- Format message with class="error"
    --     alert   -- string, with message key
    --     about   -- string, with explanation
    -- Returns message with markup
    local story = factory( alert )
    local err   = mw.html.create( "span" )
                         :addClass( "error" )
    local env = Frame():getParent()
    if env then
        story = string.format( "[[%s]] – %s",
                               env:getTitle(), story )
    end
    if about then
        story = string.format( "%s %s", story, about )
    end
    err:wikitext( story )
    return  tostring( err )
end -- failure()



local function fetch( assigned )
    -- Load data page
    -- Precondition:
    --     assigned   -- string or nil, for sub module
    -- Returns table, if assigned, or not
    -- Throws error, if not existing
    local sub = string.format( "%s/data", Frame():getTitle() )
    local r
    if not Data then
        Data = Fetch( sub )
    end
    if assigned then
        sub = string.format( "%s/%s", sub, assigned )
        r = Fetch( sub )
    end
    return r
end -- fetch()



local function fidelity( assume, accept, after )
    -- Check characters for compatibility with script
    -- Precondition:
    --     assume  -- string, source text
    --     accept  -- string, script specifier
    --     after   -- true, for result text
    -- Returns false, if okay, else string with coding sequence
    local bib = Fellow()
    local r
    if bib then
        local e, cp = bib.isScript( accept, assume )
        if not e then
            local scream
            if after then
                scream = "errTransCoding"
            else
                scream = "errTextCoding"
            end
            r = failure( scream,  bib.showScripts( cp ) )
        end
    end
    return r
end -- fidelity()



local function finish( adjust, about )
    -- Finalize template parameter text
    -- Precondition:
    --     adjust  -- string, with source text
    --     about   -- string or nil, with script or lang code
    -- Returns string, with source text
    local r = adjust:gsub( "{{", "{{" )
                    :gsub( "|",  "|" )
                    :gsub( "}}", "}}" )
    if TransText.MultiScript.isRTL( about ) then
        r = r .. "‎"
    end
    if not mw.isSubsting() then
        r = r:gsub( "&(#?x?[lrm%x]+;)", "&%1" )
    end
    return r
end -- finish()



local function first( a1, a2 )
    -- Compare a1 with a2
    --     a1  -- string, with name
    --     a2  -- string, with name
    -- Returns true if a1 < a2
    local f = function( a )
                  local d = { n = 0, s = "" }
                  local k = a
                  local s = type( k )
                  if s == "string" then
                      if k:match( "^%d+$" ) then
                          d.n = tonumber( k ) - 100
                      else
                          d.s = k
                          if k:match( "^%l+$" ) then
                              d.n = 0.1
                          elseif k:sub( 1, 3 ) == "ISO" then
                              local n, m = k:match( "^ISO(%d+)%-(%d+)$" )
                              if n then
                                  d.n = tonumber( n )
                                        + tonumber( m ) * 0.0001
                              else
                                  n   = k:match( "^ISO(%d+)$" )
                                  d.n = tonumber( n )
                              end
                          else
                              d.n = 1000000
                          end
                      end
                  elseif s == "number" then
                      d.n = k - 100
                  end
                  return d
              end
    local d1 = f( a1 )
    local d2 = f( a2 )
    local r
    if d1.n == d2.n then
        r = ( d1.s < d2.s )
    else
        r = ( d1.n < d2.n )
    end
    return r
end -- first()



local function flat( adjust )
    -- Make string of specification
    -- Precondition:
    --     adjust  -- string, number, table, with specification
    -- Returns string, with specification
    local r = adjust
    local s = type( r )
    if s == "number" then
        r = mw.ustring.char( r )
    elseif s == "string" then
    elseif s == "table" then
        local collection = {}
        for k, v in pairs( r ) do
            s = type( v )
            if s == "number" then
                table.insert( collection, mw.ustring.char( v ) )
            elseif s == "string" then
                table.insert( collection, v )
            else
                table.insert( collection, "/????/" )
            end
        end -- for k, v
        r = table.concat( collection )
    end
    return r
end -- flat()



local function flip( adjust, apply )
    -- Replace by set of string pattern rules
    -- Precondition:
    --     adjust  -- string, with text
    --     apply   -- sequence table, with tupels { seek, set }
    -- Returns string, with text
    local r = adjust
    local seek, set, v
    for i = 1, #apply do
        v          = apply[ i ]
        seek       = flat( v[ 1 ] )
        set        = flat( v[ 2 ] )
        apply[ i ] = { seek, set }
        r          = mw.ustring.gsub( r, seek, set )
    end -- for i
    return r
end -- flip()



local function flipper( apply, append )
    -- Extend table by set of replacement rules
    -- Precondition:
    --     apply   -- sequence table or nil, to be extended
    --     append  -- table, to be appended
    -- Returns sequence table with replacement rules
    local r = apply
    if type( r ) ~= "table" then
        r = { }
    end
    for i = 1, #append do
        v = append[ i ]
        table.insert( r,
                      { flat( v[ 1 ] ),
                        flat( v[ 2 ] ) } )
    end -- for i
    return r
end -- flipper()



local function flush( adjust )
    -- Cleanup for whitespace and invisible characters
    -- Precondition:
    --     adjust  -- string, with source text
    -- Returns string, with source text
    local r = adjust
    local p
    if r:find( "&", 1, true ) then
        r = mw.text.decode( r, true )
    end
    p = mw.ustring.char( 91, 0x200E, 45, 0x200F,
                             0x202A, 45, 0x202E,
                             0x2066, 45, 0x2069, 93 )
    r = mw.ustring.gsub( r, p, "" )
    p = mw.ustring.char( 91, 0x0001, 45, 0x001F,
                             0x00A0,
                             0x2002, 45, 0x200A,
                             0x202F, 93 )    -- NARROW NO-BREAK SPACE
    r = mw.ustring.gsub( r, p, " " )
    return r
end -- flush()



local function focus( achieve )
    -- Merge target specifications
    -- Precondition:
    --     achieve  -- table, with specification
    -- Postcondition:
    --     specification expanded and resolved
    local use = { }
    local s
    if type( Data.use ) ~= "table" then
        Data.use = { }
    end
    if type( achieve.use ) == "string" then
        table.insert( use, achieve.use )
    elseif type( achieve.use ) == "table" then
        for k, v in pairs( achieve.use ) do
            table.insert( use, v )
        end -- for k, v
    end
    achieve.use = false
    for i = 1, #use do
        s = use[ i ]
        if Data.use[ s ] then
            error( "Recursive loop: " .. s )
            break -- for i
        else
            Data.use[ s ] = true
            part = Data.trans[ s ]
            if type( part ) == "table" then
                if part.use then
                    focus( part )
                end
                if type( part.script ) == "string" and
                   type( achieve.script ) ~= "string" then
                    achieve.script = part.script
                end
                if type( part.replace ) == "table" then
                    achieve.replace = flipper( achieve.replace,
                                               part.replace )
                end
            else
                error( "Bad transclusion: " .. s )
            end
        end
    end -- for i
end -- focus()



local function fold()
    -- Merge base specifications
    -- Returns string, with error message, if any
    local use = { }
    local r, part
    Query.task = Data[ Query.seek ]
    if type( Query.task.use ) == "string" then
        table.insert( use, Query.task.use )
    elseif type( Query.task.use ) == "table" then
        for k, v in pairs( Query.task.use ) do
            table.insert( use, v )
        end -- for k, v
    end
    table.insert( use, Query.seek )
    for i = 1, #use do
        part = Data[ use[ i ] ]
        if type( part ) == "table" then
            if type( part.script ) == "string" then
                Query.script = part.script
            end
            if type( part.replace ) == "table" then
                Data.replace = flipper( Data.replace, part.replace )
            end
            if type( part.targets ) == "table" then
                for k, v in pairs( part.targets ) do
                    Data.trans[ k ] = v
                end -- for k, v
            end
        else
            r = failure( "errCompInvalid", v )
            break -- for i
        end
    end -- for i
    return r
end -- fold()



local function foreign( achieve )
    -- Execute transformation
    -- Precondition:
    --     achieve  -- table, with specification
    -- Returns table with components
    --     text    -- string, with transformed text
    --     script  -- string, with transformed text
    --     error   -- string, with problem
    local r = { text = Query.source }
    focus( achieve )
    if type( achieve.replace ) == "table" then
        r.text = flip( r.text, achieve.replace )
    end
    if type( achieve.script ) == "string" then
        r.script = achieve.script
        r.error  = fidelity( r.text, achieve.script, true )
    end
    return r
end -- foreign()



local function forward()
    -- Create template transclusion
    -- Returns string, with wikisyntax text
    local o = { }
    local t = { }
    local e, r, s, x
    if Query.template[ "@" ]:find( "%s", 1, true ) then
        Query.template[ "@" ] = string.format( Query.template[ "@" ],
                                               Query.seek )
    end
    r = string.format( "{{%s", Query.template[ "@" ] )
    e = Query.template[ "#" ]
    if e then
        s = Query.slang
        if Query.script then
            s = string.format( "%s-%s", s, Query.script )
        end
        t[ e ] = s
        table.insert( o, e )
    end
    e = Query.template[ "*" ]
    if e then
        t[ e ] = finish( Query.source,  Query.script or Query.slang )
        table.insert( o, e )
    end
    for k, v in pairs( Query.trans ) do
        if v.text then
            if Query.template[ k ] then
                k = Query.template[ k ]
            end
            t[ k ] = finish( v.text,  v.script or v.slang )
            table.insert( o, k )
            if v.error then
                x = x or ""
                x = string.format( "%s %s", x, v.error )
            end
        end
    end -- for k, v
    table.sort( o, first )
    for i = 1, #o do
        e = o[ i ]
        s = t[ e ]
        if not e:match( "^%d+$" ) then
            s = string.format( "%s=%s", e, s )
        end
        r = string.format( "%s |%s", r, s )
    end -- for i
    r = r .. "}}"
    if x then
        r = r .. x
    end
    return r
end -- forward()



local function furnish()
    -- Execute trans series
    -- Returns string, with text
    local part, r
    if Query.task.shift  and
       Data[ Query.task.shift ] then
        Query.seek = Query.task.shift
        Query.task = Data[ Query.seek ]
    end
    Data.trans = { }
    if type( Query.task.extern ) == "string" then
        Query.task.extern = { Query.task.extern }
    end
    if type( Query.task.extern ) == "table" then
        local lucky
        for k, v in pairs( Query.task.extern ) do
            lucky, part = pcall( fetch, v )
            if type( part ) == "table" then
                for rk, rv in pairs( part ) do
                    Data[ rk ] = rv
                end -- for rk, rv
            else
                r = failure( "errDefMissing", v )
                break -- for k, v
            end
        end -- for k, v
    end
    if not r then
        if type( Query.task ) == "table" then
            r = fold()
        else
            r = failure( "errCompMissing", Query.seek )
        end
    end
    if not r then
        Query.source = flush( Query.source )
        if Data.replace then
            Query.source = flip( Query.source, Data.replace )
        end
        r = fidelity( Query.source, Query.script )
        if not r then
            local s
            for i = 1, #Query.targets do
                s = Query.targets[ i ]
                part = Data.trans[ s ]
                if type( part ) == "table" then
                    Query.trans = Query.trans  or  { }
                    Query.trans[ s ] = foreign( part )
                else
                    if type( s ) == "string"  and  s ~= "" then
                        s = ": " ..s
                    else
                        s = false
                    end
                    r = failure( "errTargetMissing", s )
                    break -- for i
                end
            end -- for i
        end
    end
    if not r  and  Query.trans then
        r = forward()
    end
    return r
end -- furnish()



TransText.fiat = function ( accept, adjust, adapt, alter )
    -- Main entry
    -- Precondition:
    --     accept  -- string, language and/or script specifier
    --     adjust  -- string, source text
    --     adapt   -- table, with required targets
    --     alter   -- string or nil, with JSON template spec
    -- Postcondition:
    --     Returns string
    local r
    if type( accept ) == "string" then
        Query.seek = accept
        if Query.seek:match( "^%l%l%l?%-?" ) then
            Query.slang  = Query.seek:match( "^(%l%l%l?)$" )  or
                           Query.seek:match( "^(%l%l%l?)%-%u%u$" )  or
                           Query.seek:match( "^(%l%l%l?)%-%u%l%l%l$" )
            Query.script = Query.seek:match( "^%l+%-(%u%l%l%l)$" )
            if not Query.script then
                local bib = Fellow()
                if bib then
                    Query.script = bib.getLanguageScript( Query.slang )
                end
            end
        else
            Query.script = Query.seek:match( "^(%u%l%l%l)$" )
        end
        if not ( Query.script or Query.slang ) then
            r = failure( "errBaseInvalid", Query.seek )
        end
        if not r then
            local lucky
            lucky, r = pcall( fetch )
            if lucky then
                if Data[ Query.seek ] then
                    local s
                    Query.task = Data[ Query.seek ]
                    if type( adjust ) == "string" then
                        s = mw.text.trim( adjust )
                        if s == "" then
                            r = failure( "errTextMissing" )
                        else
                            Query.source = s
                        end
                    else
                        r = failure( "errTextMissing" )
                    end
                    if not r then
                        s = type( adapt )
                        if s == "table" then
                            Query.targets = adapt
                        elseif s == "string" then
                            Query.targets = { adapt }
                        end
                        if Query.targets then
                            if alter then
                                lucky, r = pcall( mw.text.jsonDecode,
                                                  alter )
                                if type( r ) == "table" then
                                    Query.template = r
                                end
                            end
                            r = furnish()
                        else
                            r = failure( "errTargetMissing" )
                        end
                    end
                else
                    r = failure( "errBaseUnknown", Query.seek )
                end
            else
                r = failure( "errDefMissing" )
            end
        end
    else
        r = failure( "errBaseMissing" )
    end
    return r
end -- TransText.fiat()



TransText.failsafe = function ( assert )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     assert  -- string, with required version or "wikidata",
    --                or false
    -- Postcondition:
    --     Returns  string with appropriate version, or false
    local since = assert
    local r
    if since == "wikidata" then
        local item = TransText.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local vsn = entity:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string" and
                   vsn.value ~= "" then
                    r = vsn.value
                end
            end
        end
    end
    if not r then
        if not since  or  since <= TransText.serial then
            r = TransText.serial
        else
            r = false
        end
    end
    return r
end -- TransText.failsafe()



-- Export
local p = { }



p.fiat = function ( frame )
    -- Main task
    --     1         -- language or script code of request text
    --     2         -- request text
    --     3         -- code of first transformation
    --     template  -- 1 for template data
    local s = mw.text.trim( frame.args[ 3 ]  or  "" )
    local r
    if s ~= "" then
        local start  = mw.text.trim( frame.args[ 1 ]  or  "" )
        local source = mw.text.trim( frame.args[ 2 ]  or  "" )
        if start ~= ""  and  source ~= "" then
            local syntax = mw.text.trim( frame.args.template  or  "" )
            local trans  = { }
            table.insert( trans, s )
            for k, v in pairs( frame.args ) do
                if type( k ) == "number"  and  k > 3 then
                    s = mw.text.trim( v )
                    if s ~= "" then
                        table.insert( trans, s )
                    end
                end
            end -- for k, v
            Query.frame = frame
            r = TransText.fiat( start, source, trans, syntax )
        end
    end
    return r or ""
end -- p.fiat



p.from = function ( frame )
    -- Available sources
    local r
    return r or ""
end -- p.from



p.forwarding = function ( frame )
    -- Available targets for this source
    local r
    return r or ""
end -- p.forwarding



p.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return TransText.failsafe( since )  or  ""
end -- p.failsafe()



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

return p