Modul:TransText
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-10-30",
item = 0,
globals = { ISO15924 = 71584769 } }
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 Failsafe = TransText
local GlobalMod = TransText
local Query = { slang = "und",
script = false,
template = { ["@"] = "lang",
["#"] = "1",
["*"] = "2" }
}
local Data
local foreignModule = function ( access, advanced, append, alt, alert )
-- Fetch global module
-- Precondition:
-- access -- string, with name of base module
-- advanced -- true, for require(); else mw.loadData()
-- append -- string, with subpage part, if any; or false
-- alt -- number, of wikidata item of root; or false
-- alert -- true, for throwing error on data problem
-- Postcondition:
-- Returns whatever, probably table
-- 2019-10-29
local storage = access
local finer = function ()
if append then
storage = string.format( "%s/%s",
storage,
append )
end
end
local fun, lucky, r, suited
if advanced then
fun = require
else
fun = mw.loadData
end
GlobalMod.globalModules = GlobalMod.globalModules or { }
suited = GlobalMod.globalModules[ access ]
if not suited then
finer()
lucky, r = pcall( fun, "Module:" .. storage )
end
if not lucky then
if not suited and
type( alt ) == "number" and
alt > 0 then
suited = string.format( "Q%d", alt )
suited = mw.wikibase.getSitelink( suited )
GlobalMod.globalModules[ access ] = suited or true
end
if type( suited ) == "string" then
storage = suited
finer()
lucky, r = pcall( fun, storage )
end
if not lucky and alert then
error( "Missing or invalid page: " .. storage, 0 )
end
end
return r
end -- foreignModule()
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 ISO15924 library
-- Throws error, if library not existing
local r
if not TransText.ISO15924 then
local got = foreignModule( "ISO15924",
true,
false,
TransText.globals.ISO15924,
true )
if type( got ) == "table" and
type( got.ISO15924 ) == "function" then
TransText.ISO15924 = got.ISO15924()
end
if type( TransText.ISO15924 ) == "table" then
r = TransText.ISO15924
else
error( "Library ISO15924 unavailable" )
end
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( attempt, accept, after )
-- Check characters for compatibility with script
-- Precondition:
-- attempt -- 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, attempt )
if e then
r = false
else
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 about and TransText.ISO15924.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
local v
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 )
r = mw.ustring.gsub( r, p, " " )
return r
end -- flush()
local function focus( achieve, all )
-- Merge target specifications
-- Precondition:
-- achieve -- table, with specification
-- all -- true: include replacements
-- 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, all )
end
if type( part.script ) == "string" and
type( achieve.script ) ~= "string" then
achieve.script = part.script
end
if all and 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( above, all, at )
-- Merge base specifications
-- Precondition:
-- above -- table, with task
-- all -- true: include replacements
-- at -- string or nil, with top entry
-- Returns string, with error message, if any
local use = { }
local r, part
if type( above.use ) == "string" then
table.insert( use, above.use )
elseif type( above.use ) == "table" then
for k, v in pairs( above.use ) do
table.insert( use, v )
end -- for k, v
end
above.use = false
if at then
table.insert( use, at )
end
for i = 1, #use do
part = Data[ use[ i ] ]
if type( part ) == "table" then
if part.use then
r = fold( part, all )
end
if type( part.script ) == "string" then
Query.script = part.script
end
if all and 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, true )
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+$" ) or
s:find( "=", 1, true ) 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 fresh( accept, all )
-- Build environment
-- Precondition:
-- accept -- string, language and/or script specifier
-- all -- true: include replacements
-- Postcondition:
-- Returns string, if error
local r
if accept ~= "" and 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
Data.trans = { }
Query.task = Data[ Query.seek ]
if Query.task.shift and
Data[ Query.task.shift ] then
Query.seek = Query.task.shift
Query.task = Data[ Query.seek ]
end
if type( Query.task.extern ) == "string" then
Query.task.extern = { Query.task.extern }
end
if type( Query.task.extern ) == "table" then
local lucky, part
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
r = fold( Query.task, all, Query.seek )
end
else
r = failure( "errBaseUnknown", Query.seek )
end
else
r = failure( "errDefMissing" )
end
end
else
r = failure( "errBaseMissing" )
end
return r
end -- fresh()
local function furnish()
-- Execute trans series
-- Returns string, with text
local part, r
if type( Query.task ) == "table" 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
else
r = failure( "errCompMissing", Query.seek )
end
if not r and Query.trans then
r = forward()
end
return r
end -- furnish()
Failsafe.failsafe = function ( atleast )
-- Retrieve versioning and check for compliance
-- Precondition:
-- atleast -- string, with required version or "wikidata" or "~"
-- or false
-- Postcondition:
-- Returns string -- with queried version, also if problem
-- false -- if appropriate
-- 2019-10-15
local last = ( atleast == "~" )
local since = atleast
local r
if last or since == "wikidata" then
local item = Failsafe.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 seek = Failsafe.serialProperty or "P348"
local vsn = entity:formatPropertyValues( seek )
if type( vsn ) == "table" and
type( vsn.value ) == "string" and
vsn.value ~= "" then
if last and vsn.value == Failsafe.serial then
r = false
else
r = vsn.value
end
end
end
end
end
if type( r ) == "nil" then
if not since or since <= Failsafe.serial then
r = Failsafe.serial
else
r = false
end
end
return r
end -- Failsafe.failsafe()
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 = fresh( accept, true )
if not r then
local s
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
local lucky
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
end
return r
end -- TransText.fiat()
TransText.forwarding = function ( accept )
-- Retrieve available targets for this source
-- Precondition:
-- accept -- string, language and/or script specifier
-- Postcondition:
-- Returns table with appropriate keys,
-- or false,
-- or string with error message
local r = fresh( accept, false )
if not r and Data.trans then
r = { }
for k, v in pairs( Data.trans ) do
table.insert( r, k )
end -- for k, v
table.sort( r, first )
for i = #r - 1, 1, -1 do
if r[ i + 1 ] == r[ i ] then
table.remove( r, i + 1 )
end
end -- for i
end
return r
end -- TransText.forwarding()
-- 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.forwarding = function ( frame )
-- Available targets for this source
local s = mw.text.trim( frame.args[ 1 ] or "" )
local r = TransText.forwarding( s )
if type( r ) == "table" then
if frame.args.code == "1" then
local e
for i = 1, #r do
s = r[ i ]
e = mw.html.create( "code" )
:wikitext( s )
if s:find( "-", 1, true ) then
e:css( "white-space", "nowrap" )
end
r[ i ] = tostring( e )
end -- for i
end
r = table.concat( r, " " )
else
r = ""
end
return r
end -- p.forwarding
p.from = function ( frame )
-- Available sources
local r
return r or ""
end -- p.from
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 Failsafe.failsafe( since ) or ""
end -- p.failsafe()
p.TransText = function ()
return TransText
end -- p.TransText
return p