Modul:CoordParse
Vorlagenprogrammierung | Diskussionen | Lua | Unterseiten | |||
Modul | Deutsch | English
|
Modul: | Dokumentation |
Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus
Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData:
2022-09-15
local CoordParse = { suite = "CoordParse",
serial = "2022-09-15",
item = 113956219 }
--[==[
Coordinate parsing and validation
* feed
* focus
* fragments
* failsafe
]==]
local Failsafe = CoordParse
local function factory()
-- Create patterns
-- Postcondition:
-- Patterns available
if not CoordParse.re then
CoordParse.re = {
WS = mw.ustring.char( 91, 37, 115,
0xA0,
0x1680,
0x2000, 45, 0x200A,
0x202F,
0x205F,
0x3000,
0x303F, 93 ),
Deg = mw.ustring.char( 91, 0xB0,
0xBA, 93 ),
Min = mw.ustring.char( 91, 0x27,
0x2032,
0x2019, 93 ),
Sec = mw.ustring.char( 91, 0x22,
0x2033,
0x201C,
0x201D, 93 ),
sep = "[,;/]"
}
end
end -- factory()
local function faculty( analyze )
-- Test for boolean interpretation
-- Precondition:
-- analyze -- string or boolean or nil
-- Postcondition:
-- returns boolean
local s = type( analyze )
local r
if s == "string" then
r = mw.text.trim( analyze )
if r == "" or r == "0" or r == "-" then
r = false
elseif r == "1" then
r = true
else
r = r:lower()
if r == "y" or
r == "yes" or
r == "true" or
r == "on" then
r = true
elseif r == "n" or
r == "no" or
r == "false" or
r == "off" then
r = false
else
if r == "falsch" or r == "nein" then
r = false
-- error( "faculty@Expr", 0 )
else
r = true
end
end
end
elseif s == "boolean" then
r = analyze
elseif s == "nil" then
r = false
else
r = true
end
return r
end -- faculty()
local function fair( adjust )
-- Advanced trim
-- Precondition:
-- adjust -- string, to be trimmed, or something else
-- CoordParse.re has been initialized
-- Postcondition:
-- Return trimmed string, or false if empty
local r
if type( adjust ) == "string" then
if not CoordParse.re.TrimL then
CoordParse.re.TrimL = string.format( "^%s+",
CoordParse.re.WS )
end
r = mw.ustring.gsub( adjust, CoordParse.re.TrimL, "" )
if r == "" then
r = false
else
if not CoordParse.re.TrimR then
CoordParse.re.TrimR = string.format( "%s+$",
CoordParse.re.WS )
end
r = mw.ustring.gsub( r, CoordParse.re.TrimR, "" )
end
else
r = adjust
end
return r
end -- fair()
local function fault( apply, about )
-- Error message
-- Precondition:
-- apply -- string, with message ID, or not
-- about -- string, with details, or not
-- Postcondition:
-- Return mw.html object
local r = mw.html.create( "span" )
local s
if apply then
if CoordParse.err then
local std = CoordParse.err[ "err" .. apply ]
if type( std ) == "string" then
std = mw.text.trim( std )
if std ~= "" then
s = std
end
end
end
if not s then
s = string.format( "((%s))", apply )
end
end
if about then
if s then
s = string.format( "%s: %s", s, mw.text.nowiki( about ) )
else
s = about
end
end
if CoordParse.err then
if type( CoordParse.err.Cat ) == "string" and
CoordParse.err.Cat ~= "" then
s = string.format( "%s[[Category:%s]]",
s,
CoordParse.err.Cat:gsub( "%]%]", "" ) )
end
if type( CoordParse.err.errClass ) == "string" and
CoordParse.err.errClass ~= "" then
r:addClass( CoordParse.err.errClass )
end
if type( CoordParse.err.errStyle ) == "string" and
CoordParse.err.errStyle ~= "" then
r:cssText( CoordParse.err.errStyle )
end
end
r:addClass( "error" )
:wikitext( s )
return r
end -- fault()
local function fetch()
-- Retrieve Expr library
-- Postcondition:
-- Return some message string, if failed
local r
if CoordParse.Expr then
if type( CoordParse.Expr ) == "string" then
r = CoordParse.Expr
end
else
local lucky
lucky, CoordParse.Expr = pcall( require, "Module:Expr" )
if type( CoordParse.Expr ) == "table" then
lucky, CoordParse.Expr = pcall( CoordParse.Expr )
if type( CoordParse.Expr ) ~= "table" or
type( CoordParse.Expr.figure ) ~= "function" then
r = "Invalid library 'Expr'"
end
else
r = CoordParse.Expr
end
if r then
r = tostring( fault( r ) )
CoordParse.Expr = r
end
end
return r
end -- fetch()
local function field( apply, align, arglist )
-- Parse compass direction word
-- Precondition:
-- apply -- string, with word, or not
-- align -- true, for latitude
-- arglist -- table, with options
-- Postcondition:
-- Return
-- 1 -- mw.html object, with error message, if failed
-- 2 -- string, with SNWE letter, or not
local r1, r2
if apply and mw.ustring.match( apply, "^%a+$" ) then
local supply = mw.ustring.upper( apply )
local scan
if align then
scan = string.format( "^%s$", arglist.N or "N" )
if mw.ustring.match( supply, scan ) then
r2 = "N"
else
scan = string.format( "^%s$", arglist.S or "S" )
if mw.ustring.match( supply, scan ) then
r2 = "S"
end
end
else
scan = string.format( "^%s$", arglist.E or "E" )
if mw.ustring.match( supply, scan ) then
r2 = "E"
else
scan = string.format( "^%s$", arglist.W or "W" )
if mw.ustring.match( supply, scan ) then
r2 = "W"
end
end
end
if not r2 then
r1 = fault( "Word", apply )
end
end
return r1, r2
end -- field()
local function figure( analyze )
-- Parse string
-- Precondition:
-- analyze - string or number, with figure
-- Expr available
-- Postcondition:
-- Return number or not
local s = type( analyze )
local r
if s == "string" then
if analyze:find( "," ) then
s = "-,"
else
s = "-."
end
r = CoordParse.Expr.figure( analyze, s )
elseif s == "number" then
r = analyze
end
return r
end -- figure()
local function focus( align, apply, arglist )
-- Extract single coordinate from set
-- align -- true, for latitude
-- apply -- string, with set
-- arglist -- table, with options
-- Postcondition:
-- Return single number, or mw.html error message
local f = function ( a )
local re = ( arglist[ a ] or a ) .. "%s*"
return re
end -- f()
local suite = "NSEW"
local begin = { }
local ended = { }
local j, k, lucky, r, s
for i = 1, 4 do
s = f( suite:sub( i, i ) )
j, k = mw.ustring.find( apply, s, 2 )
table.insert( begin, j or false )
table.insert( ended, k or false )
if k and mw.ustring.find( apply, s, k + 1 ) then
r = true
break -- for i
elseif j then
lucky = true
end
end -- for i
if r or
( begin[ 1 ] and begin[ 2 ] ) or
( begin[ 3 ] and begin[ 4 ] ) then
r = fault( "Multi", apply )
else
local j0, j1
s = apply
if lucky then
j0 = begin[ 3 ] or begin[ 4 ]
j1 = begin[ 1 ] or begin[ 2 ]
end
if j0 and j1 then
local k, lead
if j0 < j1 then
k = ended[ 3 ] or ended[ 4 ]
if align then
s = mw.ustring.sub( s, k + 1 )
lead = true
else
s = mw.ustring.sub( s, 1, k )
end
else
k = ended[ 1 ] or ended[ 2 ]
if align then
s = mw.ustring.sub( s, 1, k )
else
s = mw.ustring.sub( s, k + 1 )
lead = true
end
end
if lead then
local sep
s = fair( s )
sep = mw.ustring.sub( s, 1, 1 )
if mw.ustring.match( sep, CoordParse.re.sep ) then
s = mw.ustring.sub( s, 2 )
end
end
elseif ( j0 and not align ) or
( j1 and align ) or
not lucky then
s = apply
else
s = false
end
s = fair( s )
if not s or s == "-" then
r = fault( "Empty", apply )
end
end
if not r then
r = CoordParse.feed( align, s, arglist )
end
return r
end -- focus()
local function fracking( apply, accept )
-- Parse number with unit
-- Precondition:
-- apply -- string, with coordinate remainder
-- accept -- string, with key for pattern of unit
-- -- "Deg"
-- -- "Min"
-- -- "Sec"
-- -- "Min2"
-- Postcondition:
-- Return
-- 1 -- number, if found
-- 2 -- string, with remainder, or not
local s = CoordParse.re[ accept ]
local i, j = mw.ustring.find( apply, s, 2 )
local r1, r2
if i then
s = mw.ustring.sub( apply, 1, i - 1 )
r1 = figure( s )
if r1 then
r2 = fair( mw.ustring.sub( apply, j + 1 ) )
end
end
return r1, r2
end -- fracking()
local function from( apply, align, arglist )
-- Parse string
-- Precondition:
-- apply -- string, with coordinate
-- align -- true, for latitude
-- arglist -- table, with options
-- Postcondition:
-- Return -- mw.html object, with error message, if failed
-- -- number, if success
local g, r, snwe
if apply:find( "/", 1, true ) then
local n
g = mw.text.split( apply, "%s*/%s*" )
n = #g
if n > 4 then
r = fault( "GT4", apply )
else
r, snwe = field( fair( g[ n ] ), align, arglist )
if not r then
if snwe then
g[ n ] = false
n = n - 1
end
end
end
else
local s = fair( apply )
local start, suffix
if s then
start, suffix = mw.ustring.match( s, "^(.*%A)(%a+)$" )
if start then
r, snwe = field( suffix, align, arglist )
s = fair( start )
end
else
r = fault( "Empty", s )
end
if not r then
r = fetch()
end
if not r then
g = { }
start, suffix = fracking( s, "Deg" )
if start then
table.insert( g, start )
s = fair( suffix )
if s then
start, suffix = fracking( s, "Min" )
if start then
table.insert( g, start )
s = fair( suffix )
if s then
start, suffix = fracking( s, "Sec" )
if start then
table.insert( g, start )
else
if not r then
CoordParse.re.Min2 =
CoordParse.re.Min ..
CoordParse.re.Min
end
start, suffix = fracking( s, "Min2" )
if start then
table.insert( g, start )
else
r = fault( "Bad", s )
end
end
s = fair( suffix )
if s then
r = fault( "Bad", s )
end
end
else
r = fault( "Bad", s )
end
end
else
r = fetch()
if not r then
s = figure( s )
if s then
table.insert( g, s )
else
r = fault( "Bad", apply )
end
end
end
end
end
if not r then
if not snwe then
if align then
snwe = "N"
else
snwe = "E"
end
end
for i = #g + 1, 3 do
table.insert( g, false )
end -- for i
if g == 3 then
table.insert( g, snwe )
else
g[ 4 ] = snwe
end
r = CoordParse.fragments( align, g, arglist )
end
return r
end -- from()
CoordParse.feed = function ( align, adjust, arglist )
-- Parse single string
-- Precondition:
-- align -- true, for latitude
-- adjust -- string, to be parsed
-- arglist -- table, with options
-- Postcondition:
-- Return single number, or mw.html error message
local r, stuff
if type( arglist ) == "table" then
CoordParse.err = arglist
if type( adjust ) == "string" then
factory()
stuff = adjust
if stuff:find( "<", 1, true ) then
stuff = stuff:gsub( "<[^>]*>", "" )
end
if stuff:find( "&.+;" ) then
stuff = mw.text.decode( stuff, true )
end
r = from( stuff, align, arglist )
end
end
if not stuff then
r = fault( "Empty" )
end
return r
end -- CoordParse.feed()
CoordParse.focus = function ( align, all, arglist )
-- Parse coordinate set
-- align -- true, for latitude
-- all -- string, with set
-- arglist -- table, with options
-- Postcondition:
-- Return single number, or mw.html error message
local r
if type( arglist ) == "table" then
CoordParse.err = arglist
if type( all ) == "string" then
factory()
r = all
if r:find( "&.+;" ) then
r = mw.text.decode( r, true )
end
r = fair( r )
if r then
r = focus( align, r, arglist )
end
end
end
if not r then
r = fault( "Empty" )
end
return r
end -- CoordParse.focus()
CoordParse.fragments = function ( align, array, arglist )
-- Parse component set
-- Precondition:
-- align -- true, for latitude
-- array -- sequence table, with components
-- [ 1 ] -- string or number, with degrees
-- [ 2 ] -- string or number, with minutes, or not
-- [ 3 ] -- string or number, with seconds, or not
-- [ 4 ] -- string or not, with direction
-- arglist -- table, with options
-- Postcondition:
-- Return single number, or mw.html error message
local r
if type( array ) == "table" and type( arglist ) == "table" then
local g = { }
local max, min, s, v
factory()
for i = 1, #array do
v = array[ i ]
s = type( v )
if s == "string" then
v = fair( v )
if v and i < 4 then
max = i
if not min and v:find( "[%.,]%d" ) then
min = i
end
end
elseif s == "number" then
if i < 4 then
max = i
if not min and v ~= math.floor( v ) then
min = i
end
end
else
v = false
end
table.insert( g, v )
end -- for i
for i = #g + 1, 4 do
table.insert( g, false )
end -- for i
if g[ 1 ] then
if g[ 3 ] and not g[ 2 ] then
r = fault( "MinX" )
elseif min and min < max then
r = fault( "SepEl", tostring( g[ min ] ) )
else
r = fetch()
if not r then
for i = 1, max do
v = g[ i ]
if type( v ) == "string" then
v = figure( v )
if v then
g[ i ] = v
else
r = fault( "Num", v )
break -- for i
end
end
end -- for i
end
if not r and max > 1 then
v = g[ 2 ]
if v < 0 then
r = fault( "Mlt0", tostring( v ) )
elseif v >= 60 then
r = fault( "Mgt60", tostring( v ) )
elseif max > 2 then
v = g[ 3 ]
if v < 0 then
r = fault( "Slt0", tostring( v ) )
elseif v >= 60 then
r = fault( "Sgt60", tostring( v ) )
end
end
end
if not r then
v = g[ 1 ]
if align then
if v < -90 then
r = fault( "DegLT", tostring( v ) )
elseif v > 90 then
r = fault( "DegGT", tostring( v ) )
end
else
if v <= -180 then
r = fault( "DegLT", tostring( v ) )
elseif v > 180 then
if v < 360 then
g[ 1 ] = v - 360
g[ 4 ] = "W"
else
r = fault( "DegGT", tostring( v ) )
end
end
end
if not r then
r = g[ 1 ]
if g[ 2 ] then
r = r + g[ 2 ] * 0.0166666666666667
if g[ 3 ] then
r = r + g[ 3 ] * 0.0002777777777777778
end
end
if g[ 4 ] then
if r > 0 then
if g[ 4 ] == "S" or
g[ 4 ] == "W" then
r = -1 * r
end
elseif r < 0 and
g[ 4 ] ~= "S" and
g[ 4 ] ~= "W" then
r = fault( "Minus" )
end
end
end
end
end
else
r = fault( "DegX" )
end
end
return r
end -- CoordParse.fragments()
Failsafe.failsafe = function ( atleast )
-- Retrieve versioning and check for compliance
-- Precondition:
-- atleast -- string, with required version
-- or wikidata|item|~|@ or false
-- Postcondition:
-- returns string -- with queried version/item, also if problem
-- false -- if appropriate
-- 2020-08-17
local since = atleast
local last = ( since == "~" )
local linked = ( since == "@" )
local link = ( since == "item" )
local r
if last or link or linked or since == "wikidata" then
local item = Failsafe.item
since = false
if type( item ) == "number" and item > 0 then
local suited = string.format( "Q%d", item )
if link then
r = suited
else
local entity = mw.wikibase.getEntity( suited )
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
elseif linked then
if mw.title.getCurrentTitle().prefixedText
== mw.wikibase.getSitelink( suited ) then
r = false
else
r = suited
end
else
r = vsn.value
end
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()
-- Export
local p = {}
function p.feed( frame )
return tostring( CoordParse.feed( faculty( frame.args.latitude ),
frame.args[ 1 ],
frame.args ) )
end
function p.focus( frame )
return tostring( CoordParse.focus( faculty( frame.args.latitude ),
frame.args[ 1 ],
frame.args ) )
end
function p.fragments( frame )
local latitude = faculty( frame.args.latitude )
local parts = { }
for i = 1, 4 do
table.insert( parts, frame.args[ i ] )
end -- for i
return tostring( CoordParse.fragments( latitude,
parts,
frame.args ) )
end
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
setmetatable( p, { __call = function ( func, ... )
setmetatable( p, nil );
return Failsafe;
end } );
return p