mirror of
https://github.com/gnosygnu/xowa.git
synced 2026-03-02 03:49:30 +00:00
Res: Add resources from xowa_app_windows_64_v4.5.26.1810
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
---
|
||||
-- An implementation of the lua 5.2 bit32 library, in pure Lua
|
||||
|
||||
-- Note that in Lua, "x % n" is defined such that will always return a number
|
||||
-- between 0 and n-1 for positive n. We take advantage of that a lot here.
|
||||
|
||||
local bit32 = {}
|
||||
|
||||
local function checkint( name, argidx, x, level )
|
||||
local n = tonumber( x )
|
||||
if not n then
|
||||
error( string.format(
|
||||
"bad argument #%d to '%s' (number expected, got %s)",
|
||||
argidx, name, type( x )
|
||||
), level + 1 )
|
||||
end
|
||||
return math.floor( n )
|
||||
end
|
||||
|
||||
local function checkint32( name, argidx, x, level )
|
||||
local n = tonumber( x )
|
||||
if not n then
|
||||
error( string.format(
|
||||
"bad argument #%d to '%s' (number expected, got %s)",
|
||||
argidx, name, type( x )
|
||||
), level + 1 )
|
||||
end
|
||||
return math.floor( n ) % 0x100000000
|
||||
end
|
||||
|
||||
|
||||
function bit32.bnot( x )
|
||||
x = checkint32( 'bnot', 1, x, 2 )
|
||||
|
||||
-- In two's complement, -x = not(x) + 1
|
||||
-- So not(x) = -x - 1
|
||||
return ( -x - 1 ) % 0x100000000
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Logic tables for and/or/xor. We do pairs of bits here as a tradeoff between
|
||||
-- table space and speed. If you change the number of bits, also change the
|
||||
-- constants 2 and 4 in comb() below, and the initial value in bit32.band and
|
||||
-- bit32.btest
|
||||
local logic_and = {
|
||||
[0] = { [0] = 0, 0, 0, 0},
|
||||
[1] = { [0] = 0, 1, 0, 1},
|
||||
[2] = { [0] = 0, 0, 2, 2},
|
||||
[3] = { [0] = 0, 1, 2, 3},
|
||||
}
|
||||
local logic_or = {
|
||||
[0] = { [0] = 0, 1, 2, 3},
|
||||
[1] = { [0] = 1, 1, 3, 3},
|
||||
[2] = { [0] = 2, 3, 2, 3},
|
||||
[3] = { [0] = 3, 3, 3, 3},
|
||||
}
|
||||
local logic_xor = {
|
||||
[0] = { [0] = 0, 1, 2, 3},
|
||||
[1] = { [0] = 1, 0, 3, 2},
|
||||
[2] = { [0] = 2, 3, 0, 1},
|
||||
[3] = { [0] = 3, 2, 1, 0},
|
||||
}
|
||||
|
||||
---
|
||||
-- @param name string Function name
|
||||
-- @param args table Function args
|
||||
-- @param nargs number Arg count
|
||||
-- @param s number Start value, 0-3
|
||||
-- @param t table Logic table
|
||||
-- @return number result
|
||||
local function comb( name, args, nargs, s, t )
|
||||
for i = 1, nargs do
|
||||
args[i] = checkint32( name, i, args[i], 3 )
|
||||
end
|
||||
|
||||
local pow = 1
|
||||
local ret = 0
|
||||
for b = 0, 31, 2 do
|
||||
local c = s
|
||||
for i = 1, nargs do
|
||||
c = t[c][args[i] % 4]
|
||||
args[i] = math.floor( args[i] / 4 )
|
||||
end
|
||||
ret = ret + c * pow
|
||||
pow = pow * 4
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function bit32.band( ... )
|
||||
return comb( 'band', { ... }, select( '#', ... ), 3, logic_and )
|
||||
end
|
||||
|
||||
function bit32.bor( ... )
|
||||
return comb( 'bor', { ... }, select( '#', ... ), 0, logic_or )
|
||||
end
|
||||
|
||||
function bit32.bxor( ... )
|
||||
return comb( 'bxor', { ... }, select( '#', ... ), 0, logic_xor )
|
||||
end
|
||||
|
||||
function bit32.btest( ... )
|
||||
return comb( 'btest', { ... }, select( '#', ... ), 3, logic_and ) ~= 0
|
||||
end
|
||||
|
||||
|
||||
function bit32.extract( n, field, width )
|
||||
n = checkint32( 'extract', 1, n, 2 )
|
||||
field = checkint( 'extract', 2, field, 2 )
|
||||
width = checkint( 'extract', 3, width or 1, 2 )
|
||||
if field < 0 then
|
||||
error( "bad argument #2 to 'extract' (field cannot be negative)", 2 )
|
||||
end
|
||||
if width <= 0 then
|
||||
error( "bad argument #3 to 'extract' (width must be positive)", 2 )
|
||||
end
|
||||
if field + width > 32 then
|
||||
error( 'trying to access non-existent bits', 2 )
|
||||
end
|
||||
|
||||
return math.floor( n / 2^field ) % 2^width
|
||||
end
|
||||
|
||||
function bit32.replace( n, v, field, width )
|
||||
n = checkint32( 'replace', 1, n, 2 )
|
||||
v = checkint32( 'replace', 2, v, 2 )
|
||||
field = checkint( 'replace', 3, field, 2 )
|
||||
width = checkint( 'replace', 4, width or 1, 2 )
|
||||
if field < 0 then
|
||||
error( "bad argument #3 to 'replace' (field cannot be negative)", 2 )
|
||||
end
|
||||
if width <= 0 then
|
||||
error( "bad argument #4 to 'replace' (width must be positive)", 2 )
|
||||
end
|
||||
if field + width > 32 then
|
||||
error( 'trying to access non-existent bits', 2 )
|
||||
end
|
||||
|
||||
local f = 2^field
|
||||
local w = 2^width
|
||||
local fw = f * w
|
||||
return ( n % f ) + ( v % w ) * f + math.floor( n / fw ) * fw
|
||||
end
|
||||
|
||||
|
||||
-- For the shifting functions, anything over 32 is the same as 32
|
||||
-- and limiting to 32 prevents overflow/underflow
|
||||
local function checkdisp( name, x )
|
||||
x = checkint( name, 2, x, 3 )
|
||||
return math.min( math.max( -32, x ), 32 )
|
||||
end
|
||||
|
||||
function bit32.lshift( x, disp )
|
||||
x = checkint32( 'lshift', 1, x, 2 )
|
||||
disp = checkdisp( 'lshift', disp )
|
||||
|
||||
return math.floor( x * 2^disp ) % 0x100000000
|
||||
end
|
||||
|
||||
function bit32.rshift( x, disp )
|
||||
x = checkint32( 'rshift', 1, x, 2 )
|
||||
disp = checkdisp( 'rshift', disp )
|
||||
|
||||
return math.floor( x / 2^disp ) % 0x100000000
|
||||
end
|
||||
|
||||
function bit32.arshift( x, disp )
|
||||
x = checkint32( 'arshift', 1, x, 2 )
|
||||
disp = checkdisp( 'arshift', disp )
|
||||
|
||||
if disp <= 0 then
|
||||
-- Non-positive displacement == left shift
|
||||
-- (since exponent is non-negative, the multipication can never result
|
||||
-- in a fractional part)
|
||||
return ( x * 2^-disp ) % 0x100000000
|
||||
elseif x < 0x80000000 then
|
||||
-- High bit is 0 == right shift
|
||||
-- (since exponent is positive, the division will never increase x)
|
||||
return math.floor( x / 2^disp )
|
||||
elseif disp > 31 then
|
||||
-- Shifting off all bits
|
||||
return 0xffffffff
|
||||
else
|
||||
-- 0x100000000 - 2 ^ ( 32 - disp ) creates a number with the high disp
|
||||
-- bits set. So shift right then add that number.
|
||||
return math.floor( x / 2^disp ) + ( 0x100000000 - 2 ^ ( 32 - disp ) )
|
||||
end
|
||||
end
|
||||
|
||||
-- For the rotation functions, disp works mod 32.
|
||||
-- Note that lrotate( x, disp ) == rrotate( x, -disp ).
|
||||
function bit32.lrotate( x, disp )
|
||||
x = checkint32( 'lrotate', 1, x, 2 )
|
||||
disp = checkint( 'lrotate', 2, disp, 2 ) % 32
|
||||
|
||||
local x = x * 2^disp
|
||||
return ( x % 0x100000000 ) + math.floor( x / 0x100000000 )
|
||||
end
|
||||
|
||||
function bit32.rrotate( x, disp )
|
||||
x = checkint32( 'rrotate', 1, x, 2 )
|
||||
disp = -checkint( 'rrotate', 2, disp, 2 ) % 32
|
||||
|
||||
local x = x * 2^disp
|
||||
return ( x % 0x100000000 ) + math.floor( x / 0x100000000 )
|
||||
end
|
||||
|
||||
return bit32
|
||||
@@ -0,0 +1,144 @@
|
||||
--[[
|
||||
compat_env - see README for details.
|
||||
(c) 2012 David Manura. Licensed under Lua 5.1/5.2 terms (MIT license).
|
||||
--]]
|
||||
|
||||
local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.2.20120406'}
|
||||
|
||||
local function check_chunk_type(s, mode)
|
||||
local nmode = mode or 'bt'
|
||||
local is_binary = s and #s > 0 and s:byte(1) == 27
|
||||
if is_binary and not nmode:match'b' then
|
||||
return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode)
|
||||
elseif not is_binary and not nmode:match't' then
|
||||
return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local IS_52_LOAD = pcall(load, '')
|
||||
if IS_52_LOAD then
|
||||
M.load = _G.load
|
||||
M.loadfile = _G.loadfile
|
||||
else
|
||||
-- 5.2 style `load` implemented in 5.1
|
||||
function M.load(ld, source, mode, env)
|
||||
local f
|
||||
if type(ld) == 'string' then
|
||||
local s = ld
|
||||
local ok, err = check_chunk_type(s, mode)
|
||||
if not ok then return ok, err end
|
||||
local err; f, err = loadstring(s, source)
|
||||
if not f then return f, err end
|
||||
elseif type(ld) == 'function' then
|
||||
local ld2 = ld
|
||||
if (mode or 'bt') ~= 'bt' then
|
||||
local first = ld()
|
||||
local ok, err = check_chunk_type(first, mode)
|
||||
if not ok then return ok, err end
|
||||
ld2 = function()
|
||||
if first then
|
||||
local chunk=first; first=nil; return chunk
|
||||
else return ld() end
|
||||
end
|
||||
end
|
||||
local err; f, err = load(ld2, source); if not f then return f, err end
|
||||
else
|
||||
error(("bad argument #1 to 'load' (function expected, got %s)")
|
||||
:format(type(ld)), 2)
|
||||
end
|
||||
if env then setfenv(f, env) end
|
||||
return f
|
||||
end
|
||||
|
||||
-- 5.2 style `loadfile` implemented in 5.1
|
||||
function M.loadfile(filename, mode, env)
|
||||
if (mode or 'bt') ~= 'bt' then
|
||||
local ioerr
|
||||
local fh, err = io.open(filename, 'rb'); if not fh then return fh,err end
|
||||
local function ld()
|
||||
local chunk; chunk,ioerr = fh:read(4096); return chunk
|
||||
end
|
||||
local f, err = M.load(ld, filename and '@'..filename, mode, env)
|
||||
fh:close()
|
||||
if not f then return f, err end
|
||||
if ioerr then return nil, ioerr end
|
||||
return f
|
||||
else
|
||||
local f, err = loadfile(filename); if not f then return f, err end
|
||||
if env then setfenv(f, env) end
|
||||
return f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _G.setfenv then -- Lua 5.1
|
||||
M.setfenv = _G.setfenv
|
||||
M.getfenv = _G.getfenv
|
||||
else -- >= Lua 5.2
|
||||
-- helper function for `getfenv`/`setfenv`
|
||||
local function envlookup(f)
|
||||
local name, val
|
||||
local up = 0
|
||||
local unknown
|
||||
repeat
|
||||
up=up+1; name, val = debug.getupvalue(f, up)
|
||||
if name == '' then unknown = true end
|
||||
until name == '_ENV' or name == nil
|
||||
if name ~= '_ENV' then
|
||||
up = nil
|
||||
if unknown then
|
||||
error("upvalues not readable in Lua 5.2 when debug info missing", 3)
|
||||
end
|
||||
end
|
||||
return (name == '_ENV') and up, val, unknown
|
||||
end
|
||||
|
||||
-- helper function for `getfenv`/`setfenv`
|
||||
local function envhelper(f, name)
|
||||
if type(f) == 'number' then
|
||||
if f < 0 then
|
||||
error(("bad argument #1 to '%s' (level must be non-negative)")
|
||||
:format(name), 3)
|
||||
elseif f < 1 then
|
||||
error("thread environments unsupported in Lua 5.2", 3) --[*]
|
||||
end
|
||||
f = debug.getinfo(f+2, 'f').func
|
||||
elseif type(f) ~= 'function' then
|
||||
error(("bad argument #1 to '%s' (number expected, got %s)")
|
||||
:format(type(name, f)), 2)
|
||||
end
|
||||
return f
|
||||
end
|
||||
-- [*] might simulate with table keyed by coroutine.running()
|
||||
|
||||
-- 5.1 style `setfenv` implemented in 5.2
|
||||
function M.setfenv(f, t)
|
||||
local f = envhelper(f, 'setfenv')
|
||||
local up, val, unknown = envlookup(f)
|
||||
if up then
|
||||
debug.upvaluejoin(f, up, function() return up end, 1) --unique upval[*]
|
||||
debug.setupvalue(f, up, t)
|
||||
else
|
||||
local what = debug.getinfo(f, 'S').what
|
||||
if what ~= 'Lua' and what ~= 'main' then -- not Lua func
|
||||
error("'setfenv' cannot change environment of given object", 2)
|
||||
end -- else ignore no _ENV upvalue (warning: incompatible with 5.1)
|
||||
end
|
||||
return f -- invariant: original f ~= 0
|
||||
end
|
||||
-- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html
|
||||
|
||||
-- 5.1 style `getfenv` implemented in 5.2
|
||||
function M.getfenv(f)
|
||||
if f == 0 or f == nil then return _G end -- simulated behavior
|
||||
local f = envhelper(f, 'setfenv')
|
||||
local up, val = envlookup(f)
|
||||
if not up then return _G end -- simulated behavior [**]
|
||||
return val
|
||||
end
|
||||
-- [**] possible reasons: no _ENV upvalue, C function
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,71 @@
|
||||
local libraryUtil = {}
|
||||
|
||||
function libraryUtil.checkType( name, argIdx, arg, expectType, nilOk )
|
||||
if arg == nil and nilOk then
|
||||
return
|
||||
end
|
||||
if type( arg ) ~= expectType then
|
||||
local msg = string.format( "bad argument #%d to '%s' (%s expected, got %s)",
|
||||
argIdx, name, expectType, type( arg )
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
end
|
||||
|
||||
function libraryUtil.checkTypeMulti( name, argIdx, arg, expectTypes )
|
||||
local argType = type( arg )
|
||||
for _, expectType in ipairs( expectTypes ) do
|
||||
if argType == expectType then
|
||||
return
|
||||
end
|
||||
end
|
||||
local n = #expectTypes
|
||||
local typeList
|
||||
if n > 1 then
|
||||
typeList = table.concat( expectTypes, ', ', 1, n - 1 ) .. ' or ' .. expectTypes[n]
|
||||
else
|
||||
typeList = expectTypes[1]
|
||||
end
|
||||
local msg = string.format( "bad argument #%d to '%s' (%s expected, got %s)",
|
||||
argIdx,
|
||||
name,
|
||||
typeList,
|
||||
type( arg )
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
|
||||
function libraryUtil.checkTypeForIndex( index, value, expectType )
|
||||
if type( value ) ~= expectType then
|
||||
local msg = string.format( "value for index '%s' must be %s, %s given",
|
||||
index, expectType, type( value )
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
end
|
||||
|
||||
function libraryUtil.checkTypeForNamedArg( name, argName, arg, expectType, nilOk )
|
||||
if arg == nil and nilOk then
|
||||
return
|
||||
end
|
||||
if type( arg ) ~= expectType then
|
||||
local msg = string.format( "bad named argument %s to '%s' (%s expected, got %s)",
|
||||
argName, name, expectType, type( arg )
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
end
|
||||
|
||||
function libraryUtil.makeCheckSelfFunction( libraryName, varName, selfObj, selfObjDesc )
|
||||
return function ( self, method )
|
||||
if self ~= selfObj then
|
||||
error( string.format(
|
||||
"%s: invalid %s. Did you call %s with a dot instead of a colon, i.e. " ..
|
||||
"%s.%s() instead of %s:%s()?",
|
||||
libraryName, selfObjDesc, method, varName, method, varName, method
|
||||
), 3 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return libraryUtil
|
||||
@@ -0,0 +1,264 @@
|
||||
--[[---------------
|
||||
LuaBit v0.4
|
||||
-------------------
|
||||
a bitwise operation lib for lua.
|
||||
|
||||
http://luaforge.net/projects/bit/
|
||||
|
||||
How to use:
|
||||
-------------------
|
||||
bit.bnot(n) -- bitwise not (~n)
|
||||
bit.band(m, n) -- bitwise and (m & n)
|
||||
bit.bor(m, n) -- bitwise or (m | n)
|
||||
bit.bxor(m, n) -- bitwise xor (m ^ n)
|
||||
bit.brshift(n, bits) -- right shift (n >> bits)
|
||||
bit.blshift(n, bits) -- left shift (n << bits)
|
||||
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
|
||||
|
||||
Please note that bit.brshift and bit.blshift only support number within
|
||||
32 bits.
|
||||
|
||||
2 utility functions are provided too:
|
||||
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
|
||||
-- high bits first
|
||||
bit.tonumb(bit_tbl) -- convert a bit table into a number
|
||||
-------------------
|
||||
|
||||
Under the MIT license.
|
||||
|
||||
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
|
||||
|
||||
2013-02-20: Brad Jorsch: Fix to not try messing with globals, doesn't work in Scribunto
|
||||
--]]---------------
|
||||
|
||||
do
|
||||
|
||||
------------------------
|
||||
-- bit lib implementions
|
||||
|
||||
local function check_int(n)
|
||||
-- checking not float
|
||||
if(n - math.floor(n) > 0) then
|
||||
error("trying to use bitwise operation on non-integer!")
|
||||
end
|
||||
end
|
||||
|
||||
local function to_bits(n)
|
||||
check_int(n)
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
return to_bits(bit.bnot(math.abs(n)) + 1)
|
||||
end
|
||||
-- to bits table
|
||||
local tbl = {}
|
||||
local cnt = 1
|
||||
while (n > 0) do
|
||||
local last = math.mod(n,2)
|
||||
if(last == 1) then
|
||||
tbl[cnt] = 1
|
||||
else
|
||||
tbl[cnt] = 0
|
||||
end
|
||||
n = (n-last)/2
|
||||
cnt = cnt + 1
|
||||
end
|
||||
|
||||
return tbl
|
||||
end
|
||||
|
||||
local function tbl_to_number(tbl)
|
||||
local n = table.getn(tbl)
|
||||
|
||||
local rslt = 0
|
||||
local power = 1
|
||||
for i = 1, n do
|
||||
rslt = rslt + tbl[i]*power
|
||||
power = power*2
|
||||
end
|
||||
|
||||
return rslt
|
||||
end
|
||||
|
||||
local function expand(tbl_m, tbl_n)
|
||||
local big = {}
|
||||
local small = {}
|
||||
if(table.getn(tbl_m) > table.getn(tbl_n)) then
|
||||
big = tbl_m
|
||||
small = tbl_n
|
||||
else
|
||||
big = tbl_n
|
||||
small = tbl_m
|
||||
end
|
||||
-- expand small
|
||||
for i = table.getn(small) + 1, table.getn(big) do
|
||||
small[i] = 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function bit_or(m, n)
|
||||
local tbl_m = to_bits(m)
|
||||
local tbl_n = to_bits(n)
|
||||
expand(tbl_m, tbl_n)
|
||||
|
||||
local tbl = {}
|
||||
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
|
||||
for i = 1, rslt do
|
||||
if(tbl_m[i]== 0 and tbl_n[i] == 0) then
|
||||
tbl[i] = 0
|
||||
else
|
||||
tbl[i] = 1
|
||||
end
|
||||
end
|
||||
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_and(m, n)
|
||||
local tbl_m = to_bits(m)
|
||||
local tbl_n = to_bits(n)
|
||||
expand(tbl_m, tbl_n)
|
||||
|
||||
local tbl = {}
|
||||
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
|
||||
for i = 1, rslt do
|
||||
if(tbl_m[i]== 0 or tbl_n[i] == 0) then
|
||||
tbl[i] = 0
|
||||
else
|
||||
tbl[i] = 1
|
||||
end
|
||||
end
|
||||
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_not(n)
|
||||
|
||||
local tbl = to_bits(n)
|
||||
local size = math.max(table.getn(tbl), 32)
|
||||
for i = 1, size do
|
||||
if(tbl[i] == 1) then
|
||||
tbl[i] = 0
|
||||
else
|
||||
tbl[i] = 1
|
||||
end
|
||||
end
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_xor(m, n)
|
||||
local tbl_m = to_bits(m)
|
||||
local tbl_n = to_bits(n)
|
||||
expand(tbl_m, tbl_n)
|
||||
|
||||
local tbl = {}
|
||||
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
|
||||
for i = 1, rslt do
|
||||
if(tbl_m[i] ~= tbl_n[i]) then
|
||||
tbl[i] = 1
|
||||
else
|
||||
tbl[i] = 0
|
||||
end
|
||||
end
|
||||
|
||||
--table.foreach(tbl, print)
|
||||
|
||||
return tbl_to_number(tbl)
|
||||
end
|
||||
|
||||
local function bit_rshift(n, bits)
|
||||
check_int(n)
|
||||
|
||||
local high_bit = 0
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit_not(math.abs(n)) + 1
|
||||
high_bit = 2147483648 -- 0x80000000
|
||||
end
|
||||
|
||||
for i=1, bits do
|
||||
n = n/2
|
||||
n = bit_or(math.floor(n), high_bit)
|
||||
end
|
||||
return math.floor(n)
|
||||
end
|
||||
|
||||
-- logic rightshift assures zero filling shift
|
||||
local function bit_logic_rshift(n, bits)
|
||||
check_int(n)
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit_not(math.abs(n)) + 1
|
||||
end
|
||||
for i=1, bits do
|
||||
n = n/2
|
||||
end
|
||||
return math.floor(n)
|
||||
end
|
||||
|
||||
local function bit_lshift(n, bits)
|
||||
check_int(n)
|
||||
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit_not(math.abs(n)) + 1
|
||||
end
|
||||
|
||||
for i=1, bits do
|
||||
n = n*2
|
||||
end
|
||||
return bit_and(n, 4294967295) -- 0xFFFFFFFF
|
||||
end
|
||||
|
||||
local function bit_xor2(m, n)
|
||||
local rhs = bit_or(bit_not(m), bit_not(n))
|
||||
local lhs = bit_or(m, n)
|
||||
local rslt = bit_and(lhs, rhs)
|
||||
return rslt
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- bit lib interface
|
||||
|
||||
local bit = {
|
||||
-- bit operations
|
||||
bnot = bit_not,
|
||||
band = bit_and,
|
||||
bor = bit_or,
|
||||
bxor = bit_xor,
|
||||
brshift = bit_rshift,
|
||||
blshift = bit_lshift,
|
||||
bxor2 = bit_xor2,
|
||||
blogic_rshift = bit_logic_rshift,
|
||||
|
||||
-- utility func
|
||||
tobits = to_bits,
|
||||
tonumb = tbl_to_number,
|
||||
}
|
||||
|
||||
return bit
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
for i = 1, 100 do
|
||||
for j = 1, 100 do
|
||||
if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then
|
||||
error("bit.xor failed.")
|
||||
end
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
--[[---------------
|
||||
Hex v0.4
|
||||
-------------------
|
||||
Hex conversion lib for lua.
|
||||
|
||||
How to use:
|
||||
hex.to_hex(n) -- convert a number to a hex string
|
||||
hex.to_dec(hex) -- convert a hex string(prefix with '0x' or '0X') to number
|
||||
|
||||
Part of LuaBit(http://luaforge.net/projects/bit/).
|
||||
|
||||
Under the MIT license.
|
||||
|
||||
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
|
||||
|
||||
2013-02-20: Brad Jorsch: Fix to not try messing with globals, doesn't work in Scribunto
|
||||
--]]---------------
|
||||
|
||||
local bit = require 'bit'
|
||||
|
||||
do
|
||||
|
||||
local function to_hex(n)
|
||||
if(type(n) ~= "number") then
|
||||
error("non-number type passed in.")
|
||||
end
|
||||
|
||||
-- checking not float
|
||||
if(n - math.floor(n) > 0) then
|
||||
error("trying to apply bitwise operation on non-integer!")
|
||||
end
|
||||
|
||||
if(n < 0) then
|
||||
-- negative
|
||||
n = bit.tobits(bit.bnot(math.abs(n)) + 1)
|
||||
n = bit.tonumb(n)
|
||||
end
|
||||
|
||||
hex_tbl = {'A', 'B', 'C', 'D', 'E', 'F'}
|
||||
hex_str = ""
|
||||
|
||||
while(n ~= 0) do
|
||||
last = math.mod(n, 16)
|
||||
if(last < 10) then
|
||||
hex_str = tostring(last) .. hex_str
|
||||
else
|
||||
hex_str = hex_tbl[last-10+1] .. hex_str
|
||||
end
|
||||
n = math.floor(n/16)
|
||||
end
|
||||
if(hex_str == "") then
|
||||
hex_str = "0"
|
||||
end
|
||||
return "0x" .. hex_str
|
||||
end
|
||||
|
||||
local function to_dec(hex)
|
||||
if(type(hex) ~= "string") then
|
||||
error("non-string type passed in.")
|
||||
end
|
||||
|
||||
head = string.sub(hex, 1, 2)
|
||||
|
||||
if( head ~= "0x" and head ~= "0X") then
|
||||
error("wrong hex format, should lead by 0x or 0X.")
|
||||
end
|
||||
|
||||
v = tonumber(string.sub(hex, 3), 16)
|
||||
|
||||
return v;
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- hex lib interface
|
||||
local hex = {
|
||||
to_dec = to_dec,
|
||||
to_hex = to_hex,
|
||||
}
|
||||
|
||||
return hex
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
-- test
|
||||
d = 4341688
|
||||
h = to_hex(d)
|
||||
print(h)
|
||||
print(to_dec(h))
|
||||
|
||||
|
||||
for i = 1, 100000 do
|
||||
h = hex.to_hex(i)
|
||||
d = hex.to_dec(h)
|
||||
if(d ~= i) then
|
||||
error("failed " .. i .. ", " .. h)
|
||||
end
|
||||
end
|
||||
--]]
|
||||
@@ -0,0 +1,143 @@
|
||||
LuaBit
|
||||
------
|
||||
LuaBit is a bitwise operation lib completely written in Lua. It's
|
||||
written in the belief that Lua is self-contained.
|
||||
|
||||
The supported operations are: not, and, or, xor, right shift, logic
|
||||
right shift and left shift.
|
||||
|
||||
Several utilities are designed to leverage the power of bit operation:
|
||||
1. hex: a dec <-> hex number converter
|
||||
2. utf8: convert utf8 string to ucs2
|
||||
3. noki: convert nokia pc suite backuped SMS file to .txt
|
||||
|
||||
Under the MIT license.
|
||||
|
||||
Visit http://luaforge.net/projects/bit/ to get latest version.
|
||||
|
||||
Status
|
||||
------
|
||||
Now LuaBit is in v0.4.
|
||||
Release date: Mar 18, 2007
|
||||
|
||||
Content
|
||||
-------
|
||||
3 files are there for LuaBit:
|
||||
1) bit.lua
|
||||
is the bitwise operation lib, all operations are implemented here.
|
||||
|
||||
2) hex.lua
|
||||
is a helper lib for ease of using hex numbers with bitwise
|
||||
operation.
|
||||
|
||||
3) noki.lua
|
||||
a utility(based on bit and hex) to convert Nokia PC Suite backuped
|
||||
SMS to a unicode .txt file, which is more accessible than the
|
||||
original .nfb or .nfc file.
|
||||
|
||||
4) utf8.lua
|
||||
convert utf8 string to ucs2 string
|
||||
|
||||
How to use
|
||||
----------
|
||||
Bit
|
||||
---
|
||||
Just require 'bit' in your project and the bit lib will be
|
||||
available:
|
||||
bit.bnot(n) -- bitwise not (~n)
|
||||
bit.band(m, n) -- bitwise and (m & n)
|
||||
bit.bor(m, n) -- bitwise or (m | n)
|
||||
bit.bxor(m, n) -- bitwise xor (m ^ n)
|
||||
bit.brshift(n, bits) -- right shift (n >> bits)
|
||||
bit.blshift(n, bits) -- left shift (n << bits)
|
||||
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
|
||||
|
||||
Please note that bit.brshift and bit.blshift only support number within
|
||||
32 bits.
|
||||
|
||||
2 utility functions are provided too:
|
||||
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
|
||||
-- high bits first
|
||||
bit.tonumb(bit_tbl) -- convert a bit table into a number
|
||||
|
||||
Hex
|
||||
---
|
||||
For ease of using hex numbers, a utility hex lib is also included in
|
||||
LuaBit. You can require 'hex' to use them:
|
||||
hex.to_hex(n) -- convert a number to a hex string
|
||||
hex.to_dec(hex) -- convert a hex string(prefix with '0x' or '0X') to number
|
||||
|
||||
With hex, you can write code like:
|
||||
bit.band(258, hex.to_dec('0xFF'))
|
||||
to get the lower 8 bits of 258, that's 2.
|
||||
|
||||
Noki
|
||||
----
|
||||
require 'noki', to save your sms to .txt file:
|
||||
noki.save_sms('nokia.nfb', 'sms.txt')
|
||||
and you can view the output sms.txt in notepad or other editor which
|
||||
support unicode.
|
||||
|
||||
Utf8
|
||||
----
|
||||
require 'utf8', to convert a utf8 string:
|
||||
ucs2_string = utf8.utf_to_uni(utf8_string)
|
||||
|
||||
History
|
||||
-------
|
||||
v0.4
|
||||
* utf8 to ucs2 converter(utf8.lua).
|
||||
* clean up for compatible with Lua5.1 and 5.0.
|
||||
* add 'How to use' section for bit.lua and hex.lua.
|
||||
|
||||
v0.3
|
||||
* noki added as an application of bit.
|
||||
* typo correction.
|
||||
|
||||
v0.2
|
||||
* add logic right shift(>>>) support: bit.blogic_rshift.
|
||||
* add 2 utility functions: bit.tobits and bit.tonumb.
|
||||
* update hex.to_hex(in hex.lua) to support negative number.
|
||||
|
||||
v0.1
|
||||
LuaBit is written when I do my own game project(Fio at http://fio.edithis.info).
|
||||
When loading resources, I have to do some bit operation. And I do not
|
||||
like the embedded way of bit operation. So I decide to implement those
|
||||
ops in lua. And that's LuaBit. It's not as fast as the embedded one, but
|
||||
it works. And Lua is self-contained :-)
|
||||
|
||||
To-Do List
|
||||
---------
|
||||
v0.1
|
||||
It'll be useful if LuaBit support those bitwise op like:
|
||||
bit.band(258, '0xFF')
|
||||
ease to type and use. This will be supported in next release.
|
||||
|
||||
v0.2
|
||||
I decide to delay this feature to later version for it'll mess up the
|
||||
interface of LuaBit.
|
||||
|
||||
v0.3
|
||||
May more utility functions add to Noki - phonebook might be a nice candidate.
|
||||
|
||||
v0.4
|
||||
There's no UCS2 -> UTF8 convertion now, this feature may add in next release
|
||||
or when the project need.
|
||||
|
||||
Noki'll be be exluded from LuaBit in next release; I decide to let Noki grow
|
||||
into a powerful tool to support more Nokia PC Suite backup format(.nfb,
|
||||
.nfc and .nbu).
|
||||
|
||||
Trial Noki demo at http://nokisms.googlepages.com/(in Chinese)
|
||||
|
||||
Known issues
|
||||
------------
|
||||
LuaBit doesn't play very well with negative number. The return value of the
|
||||
bitwise operations might change to positive when applied on negative numbers
|
||||
though the bit sequence is correct. So if you want do some arithmetic with
|
||||
the result of bit operation, be careful.
|
||||
|
||||
Feedback
|
||||
--------
|
||||
Please send your comments, bugs, patches or change request to
|
||||
hanzhao(abrash_han@hotmail.com).
|
||||
@@ -0,0 +1,422 @@
|
||||
--[[
|
||||
A module for building complex HTML from Lua using a
|
||||
fluent interface.
|
||||
|
||||
Originally written on the English Wikipedia by
|
||||
Toohool and Mr. Stradivarius.
|
||||
|
||||
Code released under the GPL v2+ as per:
|
||||
https://en.wikipedia.org/w/index.php?diff=next&oldid=581399786
|
||||
https://en.wikipedia.org/w/index.php?diff=next&oldid=581403025
|
||||
|
||||
@license GNU GPL v2+
|
||||
@author Marius Hoch < hoo@online.de >
|
||||
]]
|
||||
|
||||
local HtmlBuilder = {}
|
||||
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local checkTypeMulti = util.checkTypeMulti
|
||||
|
||||
local metatable = {}
|
||||
local methodtable = {}
|
||||
|
||||
local selfClosingTags = {
|
||||
area = true,
|
||||
base = true,
|
||||
br = true,
|
||||
col = true,
|
||||
command = true,
|
||||
embed = true,
|
||||
hr = true,
|
||||
img = true,
|
||||
input = true,
|
||||
keygen = true,
|
||||
link = true,
|
||||
meta = true,
|
||||
param = true,
|
||||
source = true,
|
||||
track = true,
|
||||
wbr = true,
|
||||
}
|
||||
|
||||
local htmlencodeMap = {
|
||||
['>'] = '>',
|
||||
['<'] = '<',
|
||||
['&'] = '&',
|
||||
['"'] = '"',
|
||||
}
|
||||
|
||||
metatable.__index = methodtable
|
||||
|
||||
metatable.__tostring = function( t )
|
||||
local ret = {}
|
||||
t:_build( ret )
|
||||
return table.concat( ret )
|
||||
end
|
||||
|
||||
-- Get an attribute table (name, value) and its index
|
||||
--
|
||||
-- @param name
|
||||
local function getAttr( t, name )
|
||||
for i, attr in ipairs( t.attributes ) do
|
||||
if attr.name == name then
|
||||
return attr, i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Is this a valid attribute name?
|
||||
--
|
||||
-- @param s
|
||||
local function isValidAttributeName( s )
|
||||
-- Good estimate: http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
|
||||
return s:match( '^[a-zA-Z_:][a-zA-Z0-9_.:-]*$' )
|
||||
end
|
||||
|
||||
-- Is this a valid tag name?
|
||||
--
|
||||
-- @param s
|
||||
local function isValidTag( s )
|
||||
return s:match( '^[a-zA-Z0-9]+$' )
|
||||
end
|
||||
|
||||
-- Escape a value, for use in HTML
|
||||
--
|
||||
-- @param s
|
||||
local function htmlEncode( s )
|
||||
-- The parentheses ensure that there is only one return value
|
||||
return ( string.gsub( s, '[<>&"]', htmlencodeMap ) )
|
||||
end
|
||||
|
||||
local function cssEncode( s )
|
||||
-- XXX: I'm not sure this character set is complete.
|
||||
-- bug #68011: allow delete character (\127)
|
||||
return mw.ustring.gsub( s, '[^\32-\57\60-\127]', function ( m )
|
||||
return string.format( '\\%X ', mw.ustring.codepoint( m ) )
|
||||
end )
|
||||
end
|
||||
|
||||
-- Create a builder object. This is a separate function so that we can show the
|
||||
-- correct error levels in both HtmlBuilder.create and metatable.tag.
|
||||
--
|
||||
-- @param tagName
|
||||
-- @param args
|
||||
local function createBuilder( tagName, args )
|
||||
if tagName ~= nil and tagName ~= '' and not isValidTag( tagName ) then
|
||||
error( string.format( "invalid tag name '%s'", tagName ), 3 )
|
||||
end
|
||||
|
||||
args = args or {}
|
||||
local builder = {}
|
||||
setmetatable( builder, metatable )
|
||||
builder.nodes = {}
|
||||
builder.attributes = {}
|
||||
builder.styles = {}
|
||||
|
||||
if tagName ~= '' then
|
||||
builder.tagName = tagName
|
||||
end
|
||||
|
||||
builder.parent = args.parent
|
||||
builder.selfClosing = selfClosingTags[tagName] or args.selfClosing or false
|
||||
return builder
|
||||
end
|
||||
|
||||
-- Append a builder to the current node. This is separate from methodtable.node
|
||||
-- so that we can show the correct error level in both methodtable.node and
|
||||
-- methodtable.wikitext.
|
||||
--
|
||||
-- @param builder
|
||||
local function appendBuilder( t, builder )
|
||||
if t.selfClosing then
|
||||
error( "self-closing tags can't have child nodes", 3 )
|
||||
end
|
||||
|
||||
if builder then
|
||||
table.insert( t.nodes, builder )
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
methodtable._build = function( t, ret )
|
||||
if t.tagName then
|
||||
table.insert( ret, '<' .. t.tagName )
|
||||
for i, attr in ipairs( t.attributes ) do
|
||||
table.insert(
|
||||
ret,
|
||||
-- Note: Attribute names have already been validated
|
||||
' ' .. attr.name .. '="' .. htmlEncode( attr.val ) .. '"'
|
||||
)
|
||||
end
|
||||
if #t.styles > 0 then
|
||||
table.insert( ret, ' style="' )
|
||||
local css = {}
|
||||
for i, prop in ipairs( t.styles ) do
|
||||
if type( prop ) ~= 'table' then -- added with cssText()
|
||||
table.insert( css, htmlEncode( prop ) )
|
||||
else -- added with css()
|
||||
table.insert(
|
||||
css,
|
||||
htmlEncode( cssEncode( prop.name ) .. ':' .. cssEncode( prop.val ) )
|
||||
)
|
||||
end
|
||||
end
|
||||
table.insert( ret, table.concat( css, ';' ) )
|
||||
table.insert( ret, '"' )
|
||||
end
|
||||
if t.selfClosing then
|
||||
table.insert( ret, ' />' )
|
||||
return
|
||||
end
|
||||
table.insert( ret, '>' )
|
||||
end
|
||||
for i, node in ipairs( t.nodes ) do
|
||||
if node then
|
||||
if type( node ) == 'table' then
|
||||
node:_build( ret )
|
||||
else
|
||||
table.insert( ret, tostring( node ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
if t.tagName then
|
||||
table.insert( ret, '</' .. t.tagName .. '>' )
|
||||
end
|
||||
end
|
||||
|
||||
-- Append a builder to the current node
|
||||
--
|
||||
-- @param builder
|
||||
methodtable.node = function( t, builder )
|
||||
return appendBuilder( t, builder )
|
||||
end
|
||||
|
||||
-- Appends some markup to the node. This will be treated as wikitext.
|
||||
methodtable.wikitext = function( t, ... )
|
||||
local vals = {...}
|
||||
for i = 1, #vals do
|
||||
checkTypeMulti( 'wikitext', i, vals[i], { 'string', 'number' } )
|
||||
appendBuilder( t, vals[i] )
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Appends a newline character to the node.
|
||||
methodtable.newline = function( t )
|
||||
t:wikitext( '\n' )
|
||||
return t
|
||||
end
|
||||
|
||||
-- Appends a new child node to the builder, and returns an HtmlBuilder instance
|
||||
-- representing that new node.
|
||||
--
|
||||
-- @param tagName
|
||||
-- @param args
|
||||
methodtable.tag = function( t, tagName, args )
|
||||
checkType( 'tag', 1, tagName, 'string' )
|
||||
checkType( 'tag', 2, args, 'table', true )
|
||||
args = args or {}
|
||||
|
||||
args.parent = t
|
||||
local builder = createBuilder( tagName, args )
|
||||
t:node( builder )
|
||||
return builder
|
||||
end
|
||||
|
||||
-- Get the value of an html attribute
|
||||
--
|
||||
-- @param name
|
||||
methodtable.getAttr = function( t, name )
|
||||
checkType( 'getAttr', 1, name, 'string' )
|
||||
|
||||
local attr = getAttr( t, name )
|
||||
if attr then
|
||||
return attr.val
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Set an HTML attribute on the node.
|
||||
--
|
||||
-- @param name Attribute to set, alternative table of name-value pairs
|
||||
-- @param val Value of the attribute. Nil causes the attribute to be unset
|
||||
methodtable.attr = function( t, name, val )
|
||||
if type( name ) == 'table' then
|
||||
if val ~= nil then
|
||||
error(
|
||||
"bad argument #2 to 'attr' " ..
|
||||
'(if argument #1 is a table, argument #2 must be left empty)',
|
||||
2
|
||||
)
|
||||
end
|
||||
|
||||
local callForTable = function()
|
||||
for attrName, attrValue in pairs( name ) do
|
||||
t:attr( attrName, attrValue )
|
||||
end
|
||||
end
|
||||
|
||||
if not pcall( callForTable ) then
|
||||
error(
|
||||
"bad argument #1 to 'attr' " ..
|
||||
'(table keys must be strings, and values must be strings or numbers)',
|
||||
2
|
||||
)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
checkType( 'attr', 1, name, 'string' )
|
||||
checkTypeMulti( 'attr', 2, val, { 'string', 'number', 'nil' } )
|
||||
|
||||
-- if caller sets the style attribute explicitly, then replace all styles
|
||||
-- previously added with css() and cssText()
|
||||
if name == 'style' then
|
||||
t.styles = { val }
|
||||
return t
|
||||
end
|
||||
|
||||
if not isValidAttributeName( name ) then
|
||||
error( string.format(
|
||||
"bad argument #1 to 'attr' (invalid attribute name '%s')",
|
||||
name
|
||||
), 2 )
|
||||
end
|
||||
|
||||
local attr, i = getAttr( t, name )
|
||||
if attr then
|
||||
if val ~= nil then
|
||||
attr.val = val
|
||||
else
|
||||
table.remove( t.attributes, i )
|
||||
end
|
||||
elseif val ~= nil then
|
||||
table.insert( t.attributes, { name = name, val = val } )
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- Adds a class name to the node's class attribute. Spaces will be
|
||||
-- automatically added to delimit each added class name.
|
||||
--
|
||||
-- @param class
|
||||
methodtable.addClass = function( t, class )
|
||||
checkTypeMulti( 'addClass', 1, class, { 'string', 'number', 'nil' } )
|
||||
|
||||
if class == nil then
|
||||
return t
|
||||
end
|
||||
|
||||
local attr = getAttr( t, 'class' )
|
||||
if attr then
|
||||
attr.val = attr.val .. ' ' .. class
|
||||
else
|
||||
t:attr( 'class', class )
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- Set a CSS property to be added to the node's style attribute.
|
||||
--
|
||||
-- @param name CSS attribute to set, alternative table of name-value pairs
|
||||
-- @param val The value to set. Nil causes it to be unset
|
||||
methodtable.css = function( t, name, val )
|
||||
if type( name ) == 'table' then
|
||||
if val ~= nil then
|
||||
error(
|
||||
"bad argument #2 to 'css' " ..
|
||||
'(if argument #1 is a table, argument #2 must be left empty)',
|
||||
2
|
||||
)
|
||||
end
|
||||
|
||||
local callForTable = function()
|
||||
for attrName, attrValue in pairs( name ) do
|
||||
t:css( attrName, attrValue )
|
||||
end
|
||||
end
|
||||
|
||||
if not pcall( callForTable ) then
|
||||
error(
|
||||
"bad argument #1 to 'css' " ..
|
||||
'(table keys and values must be strings or numbers)',
|
||||
2
|
||||
)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
checkTypeMulti( 'css', 1, name, { 'string', 'number' } )
|
||||
checkTypeMulti( 'css', 2, val, { 'string', 'number', 'nil' } )
|
||||
|
||||
for i, prop in ipairs( t.styles ) do
|
||||
if prop.name == name then
|
||||
if val ~= nil then
|
||||
prop.val = val
|
||||
else
|
||||
table.remove( t.styles, i )
|
||||
end
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
if val ~= nil then
|
||||
table.insert( t.styles, { name = name, val = val } )
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- Add some raw CSS to the node's style attribute. This is typically used
|
||||
-- when a template allows some CSS to be passed in as a parameter
|
||||
--
|
||||
-- @param css
|
||||
methodtable.cssText = function( t, css )
|
||||
checkTypeMulti( 'cssText', 1, css, { 'string', 'number', 'nil' } )
|
||||
if css ~= nil then
|
||||
table.insert( t.styles, css )
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Returns the parent node under which the current node was created. Like
|
||||
-- jQuery.end, this is a convenience function to allow the construction of
|
||||
-- several child nodes to be chained together into a single statement.
|
||||
methodtable.done = function( t )
|
||||
return t.parent or t
|
||||
end
|
||||
|
||||
-- Like .done(), but traverses all the way to the root node of the tree and
|
||||
-- returns it.
|
||||
methodtable.allDone = function( t )
|
||||
while t.parent do
|
||||
t = t.parent
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Create a new instance
|
||||
--
|
||||
-- @param tagName
|
||||
-- @param args
|
||||
function HtmlBuilder.create( tagName, args )
|
||||
checkType( 'mw.html.create', 1, tagName, 'string', true )
|
||||
checkType( 'mw.html.create', 2, args, 'table', true )
|
||||
return createBuilder( tagName, args )
|
||||
end
|
||||
|
||||
mw_interface = nil
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.html = HtmlBuilder
|
||||
|
||||
package.loaded['mw.html'] = HtmlBuilder
|
||||
|
||||
return HtmlBuilder
|
||||
@@ -0,0 +1,197 @@
|
||||
local language = {}
|
||||
local php
|
||||
local util = require 'libraryUtil'
|
||||
local init_site_for_lang_needed = true -- xowa
|
||||
|
||||
function language.setupInterface()
|
||||
-- Boilerplate
|
||||
language.setupInterface = nil
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.language = language
|
||||
mw.getContentLanguage = language.getContentLanguage
|
||||
mw.getLanguage = mw.language.new
|
||||
|
||||
local lang = mw.getContentLanguage();
|
||||
|
||||
-- Extend ustring
|
||||
if mw.ustring then
|
||||
mw.ustring.upper = function ( s )
|
||||
return lang:uc( s )
|
||||
end
|
||||
mw.ustring.lower = function ( s )
|
||||
return lang:lc( s )
|
||||
end
|
||||
string.uupper = mw.ustring.upper
|
||||
string.ulower = mw.ustring.lower
|
||||
end
|
||||
|
||||
package.loaded['mw.language'] = language
|
||||
end
|
||||
|
||||
-- xowa:bgn
|
||||
function language.notify_lang_changed()
|
||||
init_site_for_lang_needed = true
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
function language.isSupportedLanguage( code )
|
||||
return php.isSupportedLanguage( code )
|
||||
end
|
||||
|
||||
function language.isKnownLanguageTag( code )
|
||||
return php.isKnownLanguageTag( code )
|
||||
end
|
||||
|
||||
function language.isValidCode( code )
|
||||
return php.isValidCode( code )
|
||||
end
|
||||
|
||||
function language.isValidBuiltInCode( code )
|
||||
return php.isValidBuiltInCode( code )
|
||||
end
|
||||
|
||||
function language.fetchLanguageName( code, inLanguage )
|
||||
return php.fetchLanguageName( code, inLanguage )
|
||||
end
|
||||
|
||||
function language.fetchLanguageNames( inLanguage, include )
|
||||
return php.fetchLanguageNames( inLanguage, include )
|
||||
end
|
||||
|
||||
function language.getFallbacksFor( code )
|
||||
return php.getFallbacksFor( code )
|
||||
end
|
||||
|
||||
function language.new( code )
|
||||
if code == nil then
|
||||
error( "too few arguments to mw.language.new()", 2 )
|
||||
end
|
||||
|
||||
local lang = { code = code }
|
||||
|
||||
local checkSelf = util.makeCheckSelfFunction( 'mw.language', 'lang', lang, 'language object' )
|
||||
|
||||
local wrappers = {
|
||||
lcfirst = 1,
|
||||
ucfirst = 1,
|
||||
lc = 1,
|
||||
uc = 1,
|
||||
caseFold = 1,
|
||||
formatNum = 1,
|
||||
formatDate = 1,
|
||||
formatDuration = 1,
|
||||
getDurationIntervals = 1,
|
||||
convertPlural = 2,
|
||||
convertGrammar = 2,
|
||||
gender = 2,
|
||||
}
|
||||
|
||||
for name, numArgs in pairs( wrappers ) do
|
||||
lang[name] = function ( self, ... )
|
||||
checkSelf( self, name )
|
||||
if select( '#', ... ) < numArgs then
|
||||
error( "too few arguments to mw.language:" .. name, 2 )
|
||||
end
|
||||
return php[name]( self.code, ... )
|
||||
end
|
||||
end
|
||||
|
||||
-- This one could use caching
|
||||
function lang:isRTL()
|
||||
checkSelf( self, 'isRTL' )
|
||||
local rtl = php.isRTL( self.code )
|
||||
self.isRTL = function ()
|
||||
return rtl
|
||||
end
|
||||
return rtl
|
||||
end
|
||||
|
||||
-- Fix semantics
|
||||
function lang:parseFormattedNumber( ... )
|
||||
checkSelf( self, 'parseFormattedNumber' )
|
||||
if select( '#', ... ) < 1 then
|
||||
error( "too few arguments to mw.language:parseFormattedNumber", 2 )
|
||||
end
|
||||
return tonumber( php.parseFormattedNumber( self.code, ... ) )
|
||||
end
|
||||
|
||||
-- Alias
|
||||
lang.plural = lang.convertPlural
|
||||
|
||||
-- Parser function compat
|
||||
function lang:grammar( case, word )
|
||||
checkSelf( self, name )
|
||||
return self:convertGrammar( word, case )
|
||||
end
|
||||
|
||||
-- Other functions
|
||||
function lang:getCode()
|
||||
checkSelf( self, 'getCode' )
|
||||
return self.code
|
||||
end
|
||||
|
||||
function lang:getDir()
|
||||
checkSelf( self, 'getDir' )
|
||||
return self:isRTL() and 'rtl' or 'ltr'
|
||||
end
|
||||
|
||||
function lang:getDirMark( opposite )
|
||||
checkSelf( self, 'getDirMark' )
|
||||
local b = self:isRTL()
|
||||
if opposite then
|
||||
b = not b
|
||||
end
|
||||
return b and '\226\128\143' or '\226\128\142'
|
||||
end
|
||||
|
||||
function lang:getDirMarkEntity( opposite )
|
||||
checkSelf( self, 'getDirMarkEntity' )
|
||||
local b = self:isRTL()
|
||||
if opposite then
|
||||
b = not b
|
||||
end
|
||||
return b and '‏' or '‎'
|
||||
end
|
||||
|
||||
function lang:getArrow( direction )
|
||||
checkSelf( self, 'getArrow' )
|
||||
direction = direction or 'forwards'
|
||||
util.checkType( 'getArrow', 1, direction, 'string' )
|
||||
if direction == 'forwards' then
|
||||
return self:isRTL() and '←' or '→'
|
||||
elseif direction == 'backwards' then
|
||||
return self:isRTL() and '→' or '←'
|
||||
elseif direction == 'left' then
|
||||
return '←'
|
||||
elseif direction == 'right' then
|
||||
return '→'
|
||||
elseif direction == 'up' then
|
||||
return '↑'
|
||||
elseif direction == 'down' then
|
||||
return '↓'
|
||||
end
|
||||
end
|
||||
|
||||
function lang:getFallbackLanguages()
|
||||
checkSelf( self, 'getFallbackLanguages' )
|
||||
return language.getFallbacksFor( self.code )
|
||||
end
|
||||
|
||||
return lang
|
||||
end
|
||||
|
||||
local contLangCode
|
||||
|
||||
function language.getContentLanguage()
|
||||
if init_site_for_lang_needed then -- xowa; was contLangCode == nil
|
||||
contLangCode = php.getContLangCode()
|
||||
init_site_for_lang_needed = false -- xowa
|
||||
end
|
||||
return language.new( contLangCode )
|
||||
end
|
||||
|
||||
return language
|
||||
816
res/bin/any/xowa/xtns/Scribunto/engines/LuaCommon/lualib/mw.lua
Normal file
816
res/bin/any/xowa/xtns/Scribunto/engines/LuaCommon/lualib/mw.lua
Normal file
@@ -0,0 +1,816 @@
|
||||
mw = mw or {}
|
||||
|
||||
local packageCache
|
||||
local packageModuleFunc
|
||||
local php
|
||||
local allowEnvFuncs = false
|
||||
local logBuffer = ''
|
||||
local currentFrame
|
||||
local loadedData = {}
|
||||
local executeFunctionDepth = 0
|
||||
|
||||
----[[
|
||||
local dump_table_count = 0;
|
||||
function mw.dump_table(o)
|
||||
if dump_table_count == 10 then
|
||||
return "overflow";
|
||||
end
|
||||
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
dump_table_count = dump_table_count + 1;
|
||||
s = s .. '['..k..'] = ' .. mw.dump_table(v) .. ','
|
||||
dump_table_count = dump_table_count - 1;
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
local diagnostic_preprocess_nested_data = ''
|
||||
function mw.diagnostic_preprocess_nested(o)
|
||||
if o == 'clear' then
|
||||
diagnostic_preprocess_nested_data = ''
|
||||
elseif o == 'exec' then
|
||||
diagnostic_preprocess_nested_data = diagnostic_preprocess_nested_data .. 'once';
|
||||
end
|
||||
return diagnostic_preprocess_nested_data
|
||||
end
|
||||
--]]
|
||||
|
||||
--- Put an isolation-friendly package module into the specified environment
|
||||
-- table. The package module will have an empty cache, because caching of
|
||||
-- module functions from other cloned environments would break module isolation.
|
||||
--
|
||||
-- @param env The cloned environment
|
||||
local function makePackageModule( env )
|
||||
-- Remove loaders from env, we don't want it inheriting our loadPackage.
|
||||
if env.package then
|
||||
env.package.loaders = nil
|
||||
end
|
||||
|
||||
-- Create the package globals in the given environment
|
||||
setfenv( packageModuleFunc, env )()
|
||||
|
||||
-- Make a loader function
|
||||
local function loadPackage( modName )
|
||||
local init
|
||||
if packageCache[modName] == 'missing' then
|
||||
return nil
|
||||
elseif packageCache[modName] == nil then
|
||||
local lib = php.loadPHPLibrary( modName )
|
||||
if lib ~= nil then
|
||||
init = function ()
|
||||
return mw.clone( lib )
|
||||
end
|
||||
else
|
||||
init = php.loadPackage( modName )
|
||||
if init == nil then
|
||||
packageCache[modName] = 'missing'
|
||||
return nil
|
||||
end
|
||||
end
|
||||
packageCache[modName] = init
|
||||
else
|
||||
init = packageCache[modName]
|
||||
end
|
||||
|
||||
setfenv( init, env )
|
||||
return init
|
||||
end
|
||||
|
||||
table.insert( env.package.loaders, loadPackage )
|
||||
end
|
||||
|
||||
--- Set up the base environment. The PHP host calls this function after any
|
||||
-- necessary host-side initialisation has been done.
|
||||
function mw.setupInterface( options )
|
||||
-- Don't allow any more calls
|
||||
mw.setupInterface = nil
|
||||
|
||||
-- Don't allow getmetatable() on a non-table, since if you can get the metatable,
|
||||
-- you can set values in it, breaking isolation
|
||||
local old_getmetatable = getmetatable
|
||||
function getmetatable(obj)
|
||||
if type(obj) == 'table' then
|
||||
return old_getmetatable(obj)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if options.allowEnvFuncs then
|
||||
allowEnvFuncs = true
|
||||
end
|
||||
|
||||
-- Store the interface table
|
||||
--
|
||||
-- mw_interface.loadPackage() returns function values with their environment
|
||||
-- set to the base environment, which would violate module isolation if they
|
||||
-- were run from a cloned environment. We can only allow access to
|
||||
-- mw_interface.loadPackage via our environment-setting wrapper.
|
||||
--
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
|
||||
packageModuleFunc = php.loadPackage( 'package' )
|
||||
makePackageModule( _G )
|
||||
package.loaded.mw = mw
|
||||
packageCache = {}
|
||||
end
|
||||
|
||||
--- Create a table like the one os.date() returns, but with a metatable that sets TTLs as the values are looked at.
|
||||
local function wrapDateTable( now )
|
||||
return setmetatable( {}, {
|
||||
__index = function( t, k )
|
||||
if k == 'sec' then
|
||||
php.setTTL( 1 )
|
||||
elseif k == 'min' then
|
||||
php.setTTL( 60 - now.sec )
|
||||
elseif k == 'hour' then
|
||||
php.setTTL( 3600 - now.min * 60 - now.sec )
|
||||
elseif now[k] ~= nil then
|
||||
php.setTTL( 86400 - now.hour * 3600 - now.min * 60 - now.sec )
|
||||
end
|
||||
t[k] = now[k]
|
||||
return now[k]
|
||||
end
|
||||
} )
|
||||
end
|
||||
|
||||
--- Wrappers for os.date() and os.time() that set the TTL of the output, if necessary
|
||||
local function ttlDate( format, time )
|
||||
if time == nil and ( format == nil or type( format ) == 'string' ) then
|
||||
local now = os.date( format and format:sub( 1, 1 ) == '!' and '!*t' or '*t' )
|
||||
if format == '!*t' or format == '*t' then
|
||||
return wrapDateTable( now )
|
||||
end
|
||||
local cleanedFormat = format and format:gsub( '%%%%', '' )
|
||||
if not format or cleanedFormat:find( '%%[EO]?[crsSTX+]' ) then
|
||||
php.setTTL( 1 ) -- second
|
||||
elseif cleanedFormat:find( '%%[EO]?[MR]' ) then
|
||||
php.setTTL( 60 - now.sec ) -- minute
|
||||
elseif cleanedFormat:find( '%%[EO]?[HIkl]' ) then
|
||||
php.setTTL( 3600 - now.min * 60 - now.sec ) -- hour
|
||||
elseif cleanedFormat:find( '%%[EO]?[pP]' ) then
|
||||
php.setTTL( 43200 - ( now.hour % 12 ) * 3600 - now.min * 60 - now.sec ) -- am/pm
|
||||
else
|
||||
-- It's not worth the complexity to figure out the exact TTL of larger units than days.
|
||||
-- If they haven't used anything shorter than days, then just set the TTL to expire at
|
||||
-- the end of today.
|
||||
php.setTTL( 86400 - now.hour * 3600 - now.min * 60 - now.sec )
|
||||
end
|
||||
end
|
||||
return os.date( format, time )
|
||||
end
|
||||
|
||||
local function ttlTime( t )
|
||||
if t == nil then
|
||||
php.setTTL( 1 )
|
||||
end
|
||||
return os.time( t )
|
||||
end
|
||||
|
||||
local function newFrame( frameId, ... )
|
||||
if not php.frameExists( frameId ) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local frame = {}
|
||||
local parentFrameIds = { ... }
|
||||
local argCache = {}
|
||||
local argNames
|
||||
local args_mt = {}
|
||||
|
||||
local function checkSelf( self, method )
|
||||
if self ~= frame then
|
||||
error( "frame:" .. method .. ": invalid frame object. " ..
|
||||
"Did you call " .. method .. " with a dot instead of a colon, i.e. " ..
|
||||
"frame." .. method .. "() instead of frame:" .. method .. "()?",
|
||||
3 )
|
||||
end
|
||||
end
|
||||
|
||||
-- Getter for args
|
||||
local function getExpandedArgument( dummy, name )
|
||||
name = tostring( name )
|
||||
if argCache[name] == nil then
|
||||
local arg = php.getExpandedArgument( frameId, name )
|
||||
if arg == nil then
|
||||
argCache[name] = false
|
||||
else
|
||||
argCache[name] = arg
|
||||
end
|
||||
end
|
||||
if argCache[name] == false then
|
||||
return nil
|
||||
else
|
||||
return argCache[name]
|
||||
end
|
||||
end
|
||||
|
||||
args_mt.__index = getExpandedArgument
|
||||
|
||||
-- pairs handler for args
|
||||
args_mt.__pairs = function ()
|
||||
if not argNames then
|
||||
local arguments = php.getAllExpandedArguments( frameId )
|
||||
argNames = {}
|
||||
for name, value in pairs( arguments ) do
|
||||
table.insert( argNames, name )
|
||||
argCache[name] = value
|
||||
end
|
||||
end
|
||||
|
||||
local index = 0
|
||||
return function ()
|
||||
index = index + 1
|
||||
if argNames[index] then
|
||||
return argNames[index], argCache[argNames[index]]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ipairs 'next' function for args
|
||||
local function argsInext( dummy, i )
|
||||
local value = getExpandedArgument( dummy, i + 1 )
|
||||
if value then
|
||||
return i + 1, value
|
||||
end
|
||||
end
|
||||
|
||||
args_mt.__ipairs = function () return argsInext, nil, 0 end
|
||||
|
||||
frame.args = {}
|
||||
setmetatable( frame.args, args_mt )
|
||||
|
||||
local function newCallbackParserValue( callback )
|
||||
local value = {}
|
||||
local cache
|
||||
|
||||
function value:expand()
|
||||
if not cache then
|
||||
cache = callback()
|
||||
end
|
||||
return cache
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
function frame:getArgument( opt )
|
||||
checkSelf( self, 'getArgument' )
|
||||
|
||||
local name
|
||||
if type( opt ) == 'table' then
|
||||
name = opt.name
|
||||
else
|
||||
name = opt
|
||||
end
|
||||
|
||||
return newCallbackParserValue(
|
||||
function ()
|
||||
return getExpandedArgument( nil, name )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function frame:getParent()
|
||||
checkSelf( self, 'getParent' )
|
||||
|
||||
return newFrame( unpack( parentFrameIds ) )
|
||||
end
|
||||
|
||||
local function checkArgs( name, args )
|
||||
local ret = {}
|
||||
for k, v in pairs( args ) do
|
||||
local tp = type( k )
|
||||
if tp ~= 'string' and tp ~= 'number' then
|
||||
error( name .. ": arg keys must be strings or numbers, " .. tp .. " given", 3 )
|
||||
end
|
||||
tp = type( v )
|
||||
if tp == 'boolean' then
|
||||
ret[k] = v and '1' or ''
|
||||
elseif tp == 'string' or tp == 'number' then
|
||||
ret[k] = tostring( v )
|
||||
else
|
||||
error( name .. ": invalid type " .. tp .. " for arg '" .. k .. "'", 3 )
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function frame:newChild( opt )
|
||||
checkSelf( self, 'newChild' )
|
||||
|
||||
if type( opt ) ~= 'table' then
|
||||
error( "frame:newChild: the first parameter must be a table", 2 )
|
||||
end
|
||||
|
||||
local title, args
|
||||
if opt.title == nil then
|
||||
title = false
|
||||
else
|
||||
title = tostring( opt.title )
|
||||
end
|
||||
if opt.args == nil then
|
||||
args = {}
|
||||
elseif type( opt.args ) ~= 'table' then
|
||||
error( "frame:newChild: args must be a table", 2 )
|
||||
else
|
||||
args = checkArgs( 'frame:newChild', opt.args )
|
||||
end
|
||||
|
||||
local newFrameId = php.newChildFrame( frameId, title, args )
|
||||
return newFrame( newFrameId, frameId, unpack( parentFrameIds ) )
|
||||
end
|
||||
|
||||
function frame:expandTemplate( opt )
|
||||
checkSelf( self, 'expandTemplate' )
|
||||
|
||||
local title
|
||||
|
||||
if type( opt ) ~= 'table' then
|
||||
error( "frame:expandTemplate: the first parameter must be a table" )
|
||||
end
|
||||
if opt.title == nil then
|
||||
error( "frame:expandTemplate: a title is required" )
|
||||
else
|
||||
if type( opt.title ) == 'table' and opt.title.namespace == 0 then
|
||||
title = ':' .. tostring( opt.title )
|
||||
else
|
||||
title = tostring( opt.title )
|
||||
end
|
||||
end
|
||||
local args
|
||||
if opt.args == nil then
|
||||
args = {}
|
||||
elseif type( opt.args ) ~= 'table' then
|
||||
error( "frame:expandTemplate: args must be a table" )
|
||||
else
|
||||
args = checkArgs( 'frame:expandTemplate', opt.args )
|
||||
end
|
||||
|
||||
return php.expandTemplate( frameId, title, args )
|
||||
end
|
||||
|
||||
function frame:callParserFunction( name, args, ... )
|
||||
checkSelf( self, 'callParserFunction' )
|
||||
|
||||
if type( name ) == 'table' then
|
||||
name, args = name.name, name.args
|
||||
if type( args ) ~= 'table' then
|
||||
args = { args }
|
||||
end
|
||||
elseif type( args ) ~= 'table' then
|
||||
args = { args, ... }
|
||||
end
|
||||
|
||||
if name == nil then
|
||||
error( "frame:callParserFunction: a function name is required", 2 )
|
||||
elseif type( name ) == 'string' or type( name ) == 'number' then
|
||||
name = tostring( name )
|
||||
else
|
||||
error( "frame:callParserFunction: function name must be a string or number", 2 )
|
||||
end
|
||||
|
||||
args = checkArgs( 'frame:callParserFunction', args )
|
||||
|
||||
return php.callParserFunction( frameId, name, args )
|
||||
end
|
||||
|
||||
function frame:extensionTag( name, content, args )
|
||||
checkSelf( self, 'extensionTag' )
|
||||
|
||||
if type( name ) == 'table' then
|
||||
name, content, args = name.name, name.content, name.args
|
||||
end
|
||||
|
||||
if name == nil then
|
||||
error( "frame:extensionTag: a function name is required", 2 )
|
||||
elseif type( name ) == 'string' or type( name ) == 'number' then
|
||||
name = tostring( name )
|
||||
else
|
||||
error( "frame:extensionTag: tag name must be a string or number", 2 )
|
||||
end
|
||||
|
||||
if content == nil then
|
||||
content = ''
|
||||
elseif type( content ) == 'string' or type( content ) == 'number' then
|
||||
content = tostring( content )
|
||||
else
|
||||
error( "frame:extensionTag: content must be a string or number", 2 )
|
||||
end
|
||||
|
||||
if args == nil then
|
||||
args = { content }
|
||||
elseif type( args ) == 'string' or type( args ) == 'number' then
|
||||
args = { content, args }
|
||||
elseif type( args ) == 'table' then
|
||||
args = checkArgs( 'frame:extensionTag', args )
|
||||
table.insert( args, 1, content )
|
||||
else
|
||||
error( "frame:extensionTag: args must be a string, number, or table", 2 )
|
||||
end
|
||||
|
||||
return php.callParserFunction( frameId, '#tag:' .. name, args )
|
||||
end
|
||||
|
||||
function frame:preprocess( opt )
|
||||
checkSelf( self, 'preprocess' )
|
||||
|
||||
local text
|
||||
if type( opt ) == 'table' then
|
||||
text = opt.text
|
||||
else
|
||||
text = opt
|
||||
end
|
||||
text = tostring( text )
|
||||
return php.preprocess( frameId, text )
|
||||
end
|
||||
|
||||
function frame:newParserValue( opt )
|
||||
checkSelf( self, 'newParserValue' )
|
||||
|
||||
local text
|
||||
if type( opt ) == 'table' then
|
||||
text = opt.text
|
||||
else
|
||||
text = opt
|
||||
end
|
||||
|
||||
return newCallbackParserValue(
|
||||
function ()
|
||||
return self:preprocess( text )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function frame:newTemplateParserValue( opt )
|
||||
checkSelf( self, 'newTemplateParserValue' )
|
||||
|
||||
if type( opt ) ~= 'table' then
|
||||
error( "frame:newTemplateParserValue: the first parameter must be a table" )
|
||||
end
|
||||
if opt.title == nil then
|
||||
error( "frame:newTemplateParserValue: a title is required" )
|
||||
end
|
||||
return newCallbackParserValue(
|
||||
function ()
|
||||
return self:expandTemplate( opt )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function frame:getTitle()
|
||||
checkSelf( self, 'getTitle' )
|
||||
return php.getFrameTitle( frameId )
|
||||
end
|
||||
|
||||
-- For backwards compat
|
||||
function frame:argumentPairs()
|
||||
checkSelf( self, 'argumentPairs' )
|
||||
return pairs( self.args )
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
--- Set up a cloned environment for execution of a module chunk, then execute
|
||||
-- the module in that environment. This is called by the host to implement
|
||||
-- {{#invoke}}.
|
||||
--
|
||||
-- @param chunk The module chunk
|
||||
-- @param name The name of the function to be returned. Nil or false causes the entire export table to be returned
|
||||
-- @return boolean Whether the requested value was able to be returned
|
||||
-- @return table|function|string The requested value, or if that was unable to be returned, the type of the value returned by the module
|
||||
function mw.executeModule( chunk, name )
|
||||
local env = mw.clone( _G )
|
||||
makePackageModule( env )
|
||||
|
||||
-- These are unsafe
|
||||
env.mw.makeProtectedEnvFuncs = nil
|
||||
env.mw.executeModule = nil
|
||||
if name ~= false then -- console sets name to false when evaluating its code and nil when evaluating a module's
|
||||
env.mw.getLogBuffer = nil
|
||||
env.mw.clearLogBuffer = nil
|
||||
end
|
||||
|
||||
if allowEnvFuncs then
|
||||
env.setfenv, env.getfenv = mw.makeProtectedEnvFuncs( {[_G] = true}, {} )
|
||||
else
|
||||
env.setfenv = nil
|
||||
env.getfenv = nil
|
||||
end
|
||||
|
||||
env.os.date = ttlDate
|
||||
env.os.time = ttlTime
|
||||
|
||||
setfenv( chunk, env )
|
||||
|
||||
local oldFrame = currentFrame
|
||||
if not currentFrame then
|
||||
currentFrame = newFrame( 'current', 'parent' )
|
||||
end
|
||||
local res = chunk()
|
||||
currentFrame = oldFrame
|
||||
|
||||
if not name then -- catch console whether it's evaluating its own code or a module's
|
||||
return true, res
|
||||
end
|
||||
if type(res) ~= 'table' then
|
||||
return false, type(res)
|
||||
end
|
||||
return true, res[name]
|
||||
end
|
||||
|
||||
function mw.executeFunction( chunk )
|
||||
local frame = newFrame( 'current', 'parent' )
|
||||
local oldFrame = currentFrame
|
||||
|
||||
if executeFunctionDepth == 0 then
|
||||
-- math.random is defined as using C's rand(), and C's rand() uses 1 as
|
||||
-- a seed if not explicitly seeded. So reseed with 1 for each top-level
|
||||
-- #invoke to avoid people passing state via the RNG.
|
||||
math.randomseed( 1 )
|
||||
end
|
||||
executeFunctionDepth = executeFunctionDepth + 1
|
||||
|
||||
currentFrame = frame
|
||||
local results = { chunk( frame ) }
|
||||
currentFrame = oldFrame
|
||||
|
||||
local stringResults = {}
|
||||
for i, result in ipairs( results ) do
|
||||
stringResults[i] = tostring( result )
|
||||
end
|
||||
|
||||
executeFunctionDepth = executeFunctionDepth - 1
|
||||
|
||||
return table.concat( stringResults )
|
||||
end
|
||||
|
||||
function mw.allToString( ... )
|
||||
local t = { ... }
|
||||
for i = 1, select( '#', ... ) do
|
||||
t[i] = tostring( t[i] )
|
||||
end
|
||||
return table.concat( t, '\t' )
|
||||
end
|
||||
|
||||
function mw.log( ... )
|
||||
logBuffer = logBuffer .. mw.allToString( ... ) .. '\n'
|
||||
end
|
||||
|
||||
function mw.dumpObject( object )
|
||||
local doneTable = {}
|
||||
local doneObj = {}
|
||||
local ct = {}
|
||||
local function sorter( a, b )
|
||||
local ta, tb = type( a ), type( b )
|
||||
if ta ~= tb then
|
||||
return ta < tb
|
||||
end
|
||||
if ta == 'string' or ta == 'number' then
|
||||
return a < b
|
||||
end
|
||||
if ta == 'boolean' then
|
||||
return tostring( a ) < tostring( b )
|
||||
end
|
||||
return false -- Incomparable
|
||||
end
|
||||
local function _dumpObject( object, indent, expandTable )
|
||||
local tp = type( object )
|
||||
if tp == 'number' or tp == 'nil' or tp == 'boolean' then
|
||||
return tostring( object )
|
||||
elseif tp == 'string' then
|
||||
return string.format( "%q", object )
|
||||
elseif tp == 'table' then
|
||||
if not doneObj[object] then
|
||||
local s = tostring( object )
|
||||
if s == 'table' then
|
||||
ct[tp] = ( ct[tp] or 0 ) + 1
|
||||
doneObj[object] = 'table#' .. ct[tp]
|
||||
else
|
||||
doneObj[object] = s
|
||||
doneTable[object] = true
|
||||
end
|
||||
end
|
||||
if doneTable[object] or not expandTable then
|
||||
return doneObj[object]
|
||||
end
|
||||
doneTable[object] = true
|
||||
|
||||
local ret = { doneObj[object], ' {\n' }
|
||||
local mt = getmetatable( object )
|
||||
if mt then
|
||||
ret[#ret + 1] = string.rep( " ", indent + 2 )
|
||||
ret[#ret + 1] = 'metatable = '
|
||||
ret[#ret + 1] = _dumpObject( mt, indent + 2, false )
|
||||
ret[#ret + 1] = "\n"
|
||||
end
|
||||
|
||||
local doneKeys = {}
|
||||
for key, value in ipairs( object ) do
|
||||
doneKeys[key] = true
|
||||
ret[#ret + 1] = string.rep( " ", indent + 2 )
|
||||
ret[#ret + 1] = _dumpObject( value, indent + 2, true )
|
||||
ret[#ret + 1] = ',\n'
|
||||
end
|
||||
local keys = {}
|
||||
for key in pairs( object ) do
|
||||
if not doneKeys[key] then
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
end
|
||||
table.sort( keys, sorter )
|
||||
for i = 1, #keys do
|
||||
local key = keys[i]
|
||||
ret[#ret + 1] = string.rep( " ", indent + 2 )
|
||||
ret[#ret + 1] = '['
|
||||
ret[#ret + 1] = _dumpObject( key, indent + 3, false )
|
||||
ret[#ret + 1] = '] = '
|
||||
ret[#ret + 1] = _dumpObject( object[key], indent + 2, true )
|
||||
ret[#ret + 1] = ",\n"
|
||||
end
|
||||
ret[#ret + 1] = string.rep( " ", indent )
|
||||
ret[#ret + 1] = '}'
|
||||
return table.concat( ret )
|
||||
else
|
||||
if not doneObj[object] then
|
||||
ct[tp] = ( ct[tp] or 0 ) + 1
|
||||
doneObj[object] = tostring( object ) .. '#' .. ct[tp]
|
||||
end
|
||||
return doneObj[object]
|
||||
end
|
||||
end
|
||||
return _dumpObject( object, 0, true )
|
||||
end
|
||||
|
||||
function mw.logObject( object, prefix )
|
||||
if prefix and prefix ~= '' then
|
||||
logBuffer = logBuffer .. prefix .. ' = '
|
||||
end
|
||||
logBuffer = logBuffer .. mw.dumpObject( object ) .. '\n'
|
||||
end
|
||||
|
||||
function mw.clearLogBuffer()
|
||||
logBuffer = ''
|
||||
end
|
||||
|
||||
function mw.getLogBuffer()
|
||||
return logBuffer
|
||||
end
|
||||
|
||||
function mw.getCurrentFrame()
|
||||
if not currentFrame then
|
||||
currentFrame = newFrame( 'current', 'parent' )
|
||||
end
|
||||
return currentFrame
|
||||
end
|
||||
|
||||
function mw.isSubsting()
|
||||
return php.isSubsting()
|
||||
end
|
||||
|
||||
function mw.incrementExpensiveFunctionCount()
|
||||
php.incrementExpensiveFunctionCount()
|
||||
end
|
||||
|
||||
function mw.addWarning( text )
|
||||
-- php.addWarning( text )
|
||||
end
|
||||
|
||||
---
|
||||
-- Wrapper for mw.loadData. This creates the read-only dummy table for
|
||||
-- accessing the real data.
|
||||
--
|
||||
-- @param data table Data to access
|
||||
-- @param seen table|nil Table of already-seen tables.
|
||||
-- @return table
|
||||
local function dataWrapper( data, seen )
|
||||
local t = {}
|
||||
seen = seen or { [data] = t }
|
||||
|
||||
local function pairsfunc( s, k )
|
||||
k = next( data, k )
|
||||
if k ~= nil then
|
||||
return k, t[k]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function ipairsfunc( s, i )
|
||||
i = i + 1
|
||||
if data[i] ~= nil then
|
||||
return i, t[i]
|
||||
end
|
||||
return -- no nil to match default ipairs()
|
||||
end
|
||||
|
||||
local mt = {
|
||||
mw_loadData = true,
|
||||
__index = function ( tt, k )
|
||||
assert( t == tt )
|
||||
local v = data[k]
|
||||
if type( v ) == 'table' then
|
||||
seen[v] = seen[v] or dataWrapper( v, seen )
|
||||
return seen[v]
|
||||
end
|
||||
return v
|
||||
end,
|
||||
__newindex = function ( t, k, v )
|
||||
error( "table from mw.loadData is read-only", 2 )
|
||||
end,
|
||||
__pairs = function ( tt )
|
||||
assert( t == tt )
|
||||
return pairsfunc, t, nil
|
||||
end,
|
||||
__ipairs = function ( tt )
|
||||
assert( t == tt )
|
||||
return ipairsfunc, t, 0
|
||||
end,
|
||||
}
|
||||
-- This is just to make setmetatable() fail
|
||||
mt.__metatable = mt
|
||||
|
||||
return setmetatable( t, mt )
|
||||
end
|
||||
|
||||
---
|
||||
-- Validator for mw.loadData. This scans through the data looking for things
|
||||
-- that are not supported, e.g. functions (which may be closures).
|
||||
--
|
||||
-- @param d table Data to access.
|
||||
-- @param seen table|nil Table of already-seen tables.
|
||||
-- @return string|nil Error message, if any
|
||||
local function validateData( d, seen )
|
||||
seen = seen or {}
|
||||
local tp = type( d )
|
||||
if tp == 'nil' or tp == 'boolean' or tp == 'number' or tp == 'string' then
|
||||
return nil
|
||||
elseif tp == 'table' then
|
||||
if seen[d] then
|
||||
return nil
|
||||
end
|
||||
seen[d] = true
|
||||
if getmetatable( d ) ~= nil then
|
||||
return "data for mw.loadData contains a table with a metatable"
|
||||
end
|
||||
for k, v in pairs( d ) do
|
||||
if type( k ) == 'table' then
|
||||
return "data for mw.loadData contains a table as a key"
|
||||
end
|
||||
local err = validateData( k, seen ) or validateData( v, seen )
|
||||
if err then
|
||||
return err
|
||||
end
|
||||
end
|
||||
return nil
|
||||
else
|
||||
return "data for mw.loadData contains unsupported data type '" .. tp .. "'"
|
||||
end
|
||||
end
|
||||
|
||||
function mw.loadData( module )
|
||||
local data = loadedData[module]
|
||||
if type( data ) == 'string' then
|
||||
-- No point in re-validating
|
||||
error( data, 2 )
|
||||
end
|
||||
if not data then
|
||||
-- Don't allow accessing the current frame's info (bug 65687)
|
||||
local oldFrame = currentFrame
|
||||
currentFrame = newFrame( 'empty' )
|
||||
|
||||
-- The point of this is to load big data, so don't save it in package.loaded
|
||||
-- where it will have to be copied for all future modules.
|
||||
local l = package.loaded[module]
|
||||
local _
|
||||
|
||||
_, data = mw.executeModule( function() return require( module ) end )
|
||||
|
||||
package.loaded[module] = l
|
||||
currentFrame = oldFrame
|
||||
|
||||
-- Validate data
|
||||
local err
|
||||
if type( data ) == 'table' then
|
||||
err = validateData( data )
|
||||
else
|
||||
err = module .. ' returned ' .. type( data ) .. ', table expected'
|
||||
end
|
||||
if err then
|
||||
loadedData[module] = err
|
||||
error( err, 2 )
|
||||
end
|
||||
loadedData[module] = data
|
||||
end
|
||||
|
||||
return dataWrapper( data )
|
||||
end
|
||||
|
||||
return mw
|
||||
@@ -0,0 +1,220 @@
|
||||
local message = {}
|
||||
local php
|
||||
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local init_message_for_lang_needed = true -- xowa
|
||||
|
||||
local valuemt = {
|
||||
__tostring = function ( t )
|
||||
return tostring( t.raw or t.num )
|
||||
end
|
||||
}
|
||||
|
||||
local function checkScalar( name, argIdx, arg, level, valuemtOk )
|
||||
local tp = type( arg )
|
||||
|
||||
-- If special params are ok, detect them
|
||||
if valuemtOk and tp == 'table' and getmetatable( arg ) == valuemt then
|
||||
return arg
|
||||
end
|
||||
|
||||
-- If it's a table with a custom __tostring function, use that string
|
||||
if tp == 'table' and getmetatable( arg ) and getmetatable( arg ).__tostring then
|
||||
return tostring( arg )
|
||||
end
|
||||
|
||||
if tp ~= 'string' and tp ~= 'number' then
|
||||
error( string.format(
|
||||
"bad argument #%d to '%s' (string or number expected, got %s)",
|
||||
argIdx, name, tp
|
||||
), level + 1 )
|
||||
end
|
||||
|
||||
return arg
|
||||
end
|
||||
|
||||
local function checkParams( name, valueOk, ... )
|
||||
-- Accept an array of params, or params as individual command line arguments
|
||||
local params, nparams
|
||||
local first = select( 1, ... )
|
||||
if type( first ) == 'table' and
|
||||
not ( getmetatable( first ) and getmetatable( first ).__tostring )
|
||||
then
|
||||
if select( '#', ... ) == 1 then
|
||||
params = first
|
||||
nparams = table.maxn( params )
|
||||
else
|
||||
error(
|
||||
"bad arguments to '" .. name .. "' (pass either a table of params or params as individual arguments)",
|
||||
3
|
||||
)
|
||||
end
|
||||
else
|
||||
params = { ... }
|
||||
nparams = select( '#', ... )
|
||||
end
|
||||
for i = 1, nparams do
|
||||
params[i] = checkScalar( 'params', i, params[i], 3, valueOk )
|
||||
end
|
||||
return params
|
||||
end
|
||||
|
||||
function message.setupInterface( options )
|
||||
-- Boilerplate
|
||||
message.setupInterface = nil
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
php.options = message.make_proxy(options) -- xowa
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.message = message
|
||||
|
||||
package.loaded['mw.message'] = message
|
||||
end
|
||||
|
||||
local function makeMessage( options )
|
||||
local obj = {}
|
||||
local checkSelf = util.makeCheckSelfFunction( 'mw.message', 'msg', obj, 'message object' )
|
||||
|
||||
local data = {
|
||||
keys = options.keys,
|
||||
rawMessage = options.rawMessage,
|
||||
params = {},
|
||||
lang = php.options.lang,
|
||||
useDB = true,
|
||||
}
|
||||
|
||||
function obj:params( ... )
|
||||
checkSelf( self, 'params' )
|
||||
local params = checkParams( 'params', true, ... )
|
||||
local j = #data.params
|
||||
for i = 1, #params do
|
||||
data.params[j + i] = params[i]
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function obj:rawParams( ... )
|
||||
checkSelf( self, 'rawParams' )
|
||||
local params = checkParams( 'rawParams', false, ... )
|
||||
local j = #data.params
|
||||
for i = 1, #params do
|
||||
data.params[j + i] = setmetatable( { raw = params[i] }, valuemt )
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function obj:numParams( ... )
|
||||
checkSelf( self, 'numParams' )
|
||||
local params = checkParams( 'numParams', false, ... )
|
||||
local j = #data.params
|
||||
for i = 1, #params do
|
||||
data.params[j + i] = setmetatable( { num = params[i] }, valuemt )
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function obj:inLanguage( lang )
|
||||
checkSelf( self, 'inLanguage' )
|
||||
if type( lang ) == 'table' and lang.getCode then
|
||||
-- probably a mw.language object
|
||||
lang = lang:getCode()
|
||||
end
|
||||
checkType( 'inLanguage', 1, lang, 'string' )
|
||||
data.lang = lang
|
||||
return self
|
||||
end
|
||||
|
||||
function obj:useDatabase( value )
|
||||
checkSelf( self, 'useDatabase' )
|
||||
checkType( 'useDatabase', 1, value, 'boolean' )
|
||||
data.useDB = value
|
||||
return self
|
||||
end
|
||||
|
||||
function obj:plain()
|
||||
checkSelf( self, 'plain' )
|
||||
return php.plain( data )
|
||||
end
|
||||
|
||||
function obj:exists()
|
||||
checkSelf( self, 'exists' )
|
||||
return php.check( 'exists', data )
|
||||
end
|
||||
|
||||
function obj:isBlank()
|
||||
checkSelf( self, 'isBlank' )
|
||||
return php.check( 'isBlank', data )
|
||||
end
|
||||
|
||||
function obj:isDisabled()
|
||||
checkSelf( self, 'isDisabled' )
|
||||
return php.check( 'isDisabled', data )
|
||||
end
|
||||
|
||||
return setmetatable( obj, {
|
||||
__tostring = function ( t )
|
||||
return t:plain()
|
||||
end
|
||||
} )
|
||||
end
|
||||
|
||||
function message.new( key, ... )
|
||||
checkType( 'message.new', 1, key, 'string' )
|
||||
return makeMessage{ keys = { key } }:params( ... )
|
||||
end
|
||||
|
||||
function message.newFallbackSequence( ... )
|
||||
for i = 1, math.max( 1, select( '#', ... ) ) do
|
||||
checkType( 'message.newFallbackSequence', i, select( i, ... ), 'string' )
|
||||
end
|
||||
return makeMessage{ keys = { ... } }
|
||||
end
|
||||
|
||||
function message.newRawMessage( msg, ... )
|
||||
checkType( 'message.newRawMessage', 1, msg, 'string' )
|
||||
return makeMessage{ rawMessage = msg }:params( ... )
|
||||
end
|
||||
|
||||
function message.rawParam( value )
|
||||
value = checkScalar( 'message.rawParam', 1, value )
|
||||
return setmetatable( { raw = value }, valuemt )
|
||||
end
|
||||
|
||||
function message.numParam( value )
|
||||
value = checkScalar( 'message.numParam', 1, value )
|
||||
return setmetatable( { num = value }, valuemt )
|
||||
end
|
||||
|
||||
function message.getDefaultLanguage()
|
||||
if mw.language then
|
||||
return mw.language.new( php.options.lang )
|
||||
else
|
||||
return php.options.lang
|
||||
end
|
||||
end
|
||||
|
||||
-- xowa:bgn
|
||||
function message.notify_lang_changed()
|
||||
init_message_for_lang_needed = true
|
||||
end
|
||||
|
||||
function message.make_proxy(real)
|
||||
local proxy = {}
|
||||
setmetatable(proxy, {__index =
|
||||
function(tbl, key)
|
||||
if key == "lang" and init_message_for_lang_needed then
|
||||
real[key] = php.init_message_for_lang().lang
|
||||
init_message_for_lang_needed = false
|
||||
end
|
||||
return real[key]
|
||||
end
|
||||
}
|
||||
)
|
||||
return proxy
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
return message
|
||||
@@ -0,0 +1,141 @@
|
||||
local site = {}
|
||||
local init_site_for_wiki_needed = true -- xowa
|
||||
local php
|
||||
|
||||
function site.setupInterface( info )
|
||||
-- Boilerplate
|
||||
site.setupInterface = nil
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
|
||||
-- site.setupSite(info) -- xowa
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.site = site.make_proxy(site) -- xowa
|
||||
|
||||
package.loaded['mw.site'] = site
|
||||
end
|
||||
|
||||
-- xowa:bgn
|
||||
function site.notify_wiki_changed()
|
||||
init_site_for_wiki_needed = true
|
||||
end
|
||||
|
||||
function site.make_proxy(real)
|
||||
local proxy = {}
|
||||
setmetatable(proxy, {__index =
|
||||
function(tbl, key)
|
||||
if init_site_for_wiki_needed then
|
||||
info = php.init_site_for_wiki() -- more performant than calling setupInterface for every page load
|
||||
site.setupSite(info)
|
||||
init_site_for_wiki_needed = false
|
||||
end
|
||||
return real[key]
|
||||
end
|
||||
}
|
||||
)
|
||||
return proxy
|
||||
end
|
||||
function site.setupSite( info )
|
||||
site.siteName = info.siteName
|
||||
site.server = info.server
|
||||
site.scriptPath = info.scriptPath
|
||||
site.stylePath = info.stylePath
|
||||
site.currentVersion = info.currentVersion
|
||||
site.stats = info.stats
|
||||
site.stats.pagesInCategory = php.pagesInCategory
|
||||
site.stats.pagesInNamespace = php.pagesInNamespace
|
||||
site.stats.usersInGroup = php.usersInGroup
|
||||
site.interwikiMap = php.interwikiMap
|
||||
|
||||
-- Process namespace list into more useful tables
|
||||
site.namespaces = {}
|
||||
local namespacesByName = {}
|
||||
site.subjectNamespaces = {}
|
||||
site.talkNamespaces = {}
|
||||
site.contentNamespaces = {}
|
||||
for ns, data in pairs( info.namespaces ) do
|
||||
data.subject = info.namespaces[data.subject]
|
||||
data.talk = info.namespaces[data.talk]
|
||||
data.associated = info.namespaces[data.associated]
|
||||
|
||||
site.namespaces[ns] = data
|
||||
|
||||
namespacesByName[data.name] = data
|
||||
if data.canonicalName then
|
||||
namespacesByName[data.canonicalName] = data
|
||||
end
|
||||
for i = 1, #data.aliases do
|
||||
namespacesByName[data.aliases[i]] = data
|
||||
end
|
||||
|
||||
if data.isSubject then
|
||||
site.subjectNamespaces[ns] = data
|
||||
end
|
||||
if data.isTalk then
|
||||
site.talkNamespaces[ns] = data
|
||||
end
|
||||
if data.isContent then
|
||||
site.contentNamespaces[ns] = data
|
||||
end
|
||||
end
|
||||
|
||||
-- Set __index for namespacesByName to handle names-with-underscores
|
||||
-- and non-standard case
|
||||
local getNsIndex = php.getNsIndex
|
||||
setmetatable( namespacesByName, {
|
||||
__index = function ( t, k )
|
||||
if type( k ) == 'string' then
|
||||
-- Try with fixed underscores
|
||||
k = string.gsub( k, '_', ' ' )
|
||||
if rawget( t, k ) then
|
||||
return rawget( t, k )
|
||||
end
|
||||
|
||||
-- Ask PHP, because names are case-insensitive
|
||||
local ns = getNsIndex( k )
|
||||
if ns then
|
||||
rawset( t, k, site.namespaces[ns] )
|
||||
end
|
||||
end
|
||||
return rawget( t, k )
|
||||
end
|
||||
} )
|
||||
|
||||
-- Set namespacesByName as the lookup table for site.namespaces, so
|
||||
-- something like site.namespaces.Wikipedia works without having
|
||||
-- pairs( site.namespaces ) iterate all those names.
|
||||
setmetatable( site.namespaces, { __index = namespacesByName } )
|
||||
|
||||
-- Copy the site stats, and set up the metatable to load them if necessary.
|
||||
local loadSiteStats = php.loadSiteStats
|
||||
site.stats.x = php.loadSiteStats
|
||||
if info.stats then
|
||||
loadSiteStats = nil
|
||||
for k, v in pairs( info.stats ) do
|
||||
site.stats[k] = v
|
||||
end
|
||||
end
|
||||
setmetatable( site.stats, {
|
||||
__index = function ( t, k )
|
||||
if t ~= site.stats then -- cloned
|
||||
return site.stats[k]
|
||||
end
|
||||
|
||||
if k == 'admins' then
|
||||
t.admins = t.usersInGroup( 'sysop' )
|
||||
elseif loadSiteStats then
|
||||
for k, v in pairs( loadSiteStats() ) do
|
||||
t[k] = v
|
||||
end
|
||||
loadSiteStats = nil
|
||||
end
|
||||
|
||||
return rawget( t, k )
|
||||
end
|
||||
} )
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
return site
|
||||
@@ -0,0 +1,352 @@
|
||||
local mwtext = {}
|
||||
local php
|
||||
local options
|
||||
local init_text_for_wiki_needed = true -- xowa
|
||||
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local checkTypeForNamedArg = util.checkTypeForNamedArg
|
||||
|
||||
function mwtext.setupInterface( opts )
|
||||
-- Boilerplate
|
||||
mwtext.setupInterface = nil
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
-- options = opts -- xowa
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.text = mwtext.make_proxy(mwtext) -- xowa
|
||||
|
||||
package.loaded['mw.text'] = mwtext
|
||||
end
|
||||
|
||||
function mwtext.trim( s, charset )
|
||||
charset = charset or '\t\r\n\f '
|
||||
s = mw.ustring.gsub( s, '^[' .. charset .. ']*(.-)[' .. charset .. ']*$', '%1' )
|
||||
return s
|
||||
end
|
||||
|
||||
local htmlencode_map = {
|
||||
['>'] = '>',
|
||||
['<'] = '<',
|
||||
['&'] = '&',
|
||||
['"'] = '"',
|
||||
["'"] = ''',
|
||||
['\194\160'] = ' ',
|
||||
}
|
||||
local htmldecode_map = {}
|
||||
for k, v in pairs( htmlencode_map ) do
|
||||
htmldecode_map[v] = k
|
||||
end
|
||||
local decode_named_entities = nil
|
||||
|
||||
function mwtext.encode( s, charset )
|
||||
charset = charset or '<>&"\'\194\160'
|
||||
s = mw.ustring.gsub( s, '[' .. charset .. ']', function ( m )
|
||||
if not htmlencode_map[m] then
|
||||
local e = string.format( '&#%d;', mw.ustring.codepoint( m ) )
|
||||
htmlencode_map[m] = e
|
||||
htmldecode_map[e] = m
|
||||
end
|
||||
return htmlencode_map[m]
|
||||
end )
|
||||
return s
|
||||
end
|
||||
|
||||
function mwtext.decode( s, decodeNamedEntities )
|
||||
local dec
|
||||
if decodeNamedEntities then
|
||||
if decode_named_entities == nil then
|
||||
decode_named_entities = php.getEntityTable()
|
||||
setmetatable( decode_named_entities, { __index = htmldecode_map } )
|
||||
end
|
||||
dec = decode_named_entities
|
||||
else
|
||||
dec = htmldecode_map
|
||||
end
|
||||
-- string.gsub is safe here, because only ASCII chars are in the pattern
|
||||
s = string.gsub( s, '(&(#?x?)([a-zA-Z0-9]+);)', function ( m, flg, name )
|
||||
if not dec[m] then
|
||||
local n = nil
|
||||
if flg == '#' then
|
||||
n = tonumber( name, 10 )
|
||||
elseif flg == '#x' then
|
||||
n = tonumber( name, 16 )
|
||||
end
|
||||
if n and n <= 0x10ffff then
|
||||
n = mw.ustring.char( n )
|
||||
if n then
|
||||
htmldecode_map[m] = n
|
||||
htmlencode_map[n] = m
|
||||
end
|
||||
end
|
||||
end
|
||||
return dec[m]
|
||||
end )
|
||||
return s
|
||||
end
|
||||
|
||||
local nowikiRepl1 = {
|
||||
['"'] = '"',
|
||||
['&'] = '&',
|
||||
["'"] = ''',
|
||||
['<'] = '<',
|
||||
['='] = '=',
|
||||
['>'] = '>',
|
||||
['['] = '[',
|
||||
[']'] = ']',
|
||||
['{'] = '{',
|
||||
['|'] = '|',
|
||||
['}'] = '}',
|
||||
}
|
||||
|
||||
local nowikiRepl2 = {
|
||||
["\n#"] = "\n#", ["\r#"] = "\r#",
|
||||
["\n*"] = "\n*", ["\r*"] = "\r*",
|
||||
["\n:"] = "\n:", ["\r:"] = "\r:",
|
||||
["\n;"] = "\n;", ["\r;"] = "\r;",
|
||||
["\n "] = "\n ", ["\r "] = "\r ",
|
||||
["\n\n"] = "\n ", ["\r\n"] = " \n",
|
||||
["\n\r"] = "\n ", ["\r\r"] = "\r ",
|
||||
["\n\t"] = "\n	", ["\r\t"] = "\r	",
|
||||
}
|
||||
|
||||
local nowikiReplMagic = {}
|
||||
for sp, esc in pairs( {
|
||||
[' '] = ' ',
|
||||
['\t'] = '	',
|
||||
['\r'] = ' ',
|
||||
['\n'] = ' ',
|
||||
['\f'] = '',
|
||||
} ) do
|
||||
nowikiReplMagic['ISBN' .. sp] = 'ISBN' .. esc
|
||||
nowikiReplMagic['RFC' .. sp] = 'RFC' .. esc
|
||||
nowikiReplMagic['PMID' .. sp] = 'PMID' .. esc
|
||||
end
|
||||
|
||||
function mwtext.nowiki( s )
|
||||
-- string.gsub is safe here, because we're only caring about ASCII chars
|
||||
s = string.gsub( s, '["&\'<=>%[%]{|}]', nowikiRepl1 )
|
||||
s = '\n' .. s
|
||||
s = string.gsub( s, '[\r\n][#*:; \n\r\t]', nowikiRepl2 )
|
||||
s = string.gsub( s, '([\r\n])%-%-%-%-', '%1----' )
|
||||
s = string.sub( s, 2 )
|
||||
s = string.gsub( s, '__', '__' )
|
||||
s = string.gsub( s, '://', '://' )
|
||||
s = string.gsub( s, 'ISBN%s', nowikiReplMagic )
|
||||
s = string.gsub( s, 'RFC%s', nowikiReplMagic )
|
||||
s = string.gsub( s, 'PMID%s', nowikiReplMagic )
|
||||
for k, v in pairs( options.nowiki_protocols ) do
|
||||
s = string.gsub( s, k, v )
|
||||
end
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
function mwtext.tag( name, attrs, content )
|
||||
local named = false
|
||||
if type( name ) == 'table' then
|
||||
named = true
|
||||
name, attrs, content = name.name, name.attrs, name.content
|
||||
checkTypeForNamedArg( 'tag', 'name', name, 'string' )
|
||||
checkTypeForNamedArg( 'tag', 'attrs', attrs, 'table', true )
|
||||
else
|
||||
checkType( 'tag', 1, name, 'string' )
|
||||
checkType( 'tag', 2, attrs, 'table', true )
|
||||
end
|
||||
|
||||
local ret = { '<' .. name }
|
||||
for k, v in pairs( attrs or {} ) do
|
||||
if type( k ) ~= 'string' then
|
||||
error( "bad named argument attrs to 'tag' (keys must be strings, found " .. type( k ) .. ")", 2 )
|
||||
end
|
||||
if string.match( k, '[\t\r\n\f /<>"\'=]' ) then
|
||||
error( "bad named argument attrs to 'tag' (invalid key '" .. k .. "')", 2 )
|
||||
end
|
||||
local tp = type( v )
|
||||
if tp == 'boolean' then
|
||||
if v then
|
||||
ret[#ret+1] = ' ' .. k
|
||||
end
|
||||
elseif tp == 'string' or tp == 'number' then
|
||||
ret[#ret+1] = string.format( ' %s="%s"', k, mwtext.encode( tostring( v ) ) )
|
||||
else
|
||||
error( "bad named argument attrs to 'tag' (value for key '" .. k .. "' may not be " .. tp .. ")", 2 )
|
||||
end
|
||||
end
|
||||
|
||||
local tp = type( content )
|
||||
if content == nil then
|
||||
ret[#ret+1] = '>'
|
||||
elseif content == false then
|
||||
ret[#ret+1] = ' />'
|
||||
elseif tp == 'string' or tp == 'number' then
|
||||
ret[#ret+1] = '>'
|
||||
ret[#ret+1] = content
|
||||
ret[#ret+1] = '</' .. name .. '>'
|
||||
else
|
||||
if named then
|
||||
checkTypeForNamedArg( 'tag', 'content', content, 'string, number, nil, or false' )
|
||||
else
|
||||
checkType( 'tag', 3, content, 'string, number, nil, or false' )
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat( ret )
|
||||
end
|
||||
|
||||
function mwtext.unstrip( s )
|
||||
return php.unstrip( s )
|
||||
end
|
||||
|
||||
function mwtext.unstripNoWiki( s )
|
||||
return php.unstripNoWiki( s )
|
||||
end
|
||||
|
||||
function mwtext.killMarkers( s )
|
||||
return php.killMarkers( s )
|
||||
end
|
||||
|
||||
function mwtext.split( text, pattern, plain )
|
||||
local ret = {}
|
||||
for m in mwtext.gsplit( text, pattern, plain ) do
|
||||
ret[#ret+1] = m
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function mwtext.gsplit( text, pattern, plain )
|
||||
local s, l = 1, mw.ustring.len( text )
|
||||
return function ()
|
||||
if s then
|
||||
local e, n = mw.ustring.find( text, pattern, s, plain )
|
||||
local ret
|
||||
if not e then
|
||||
ret = mw.ustring.sub( text, s )
|
||||
s = nil
|
||||
elseif n < e then
|
||||
-- Empty separator!
|
||||
ret = mw.ustring.sub( text, s, e )
|
||||
if e < l then
|
||||
s = e + 1
|
||||
else
|
||||
s = nil
|
||||
end
|
||||
else
|
||||
ret = e > s and mw.ustring.sub( text, s, e - 1 ) or ''
|
||||
s = n + 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
end, nil, nil
|
||||
end
|
||||
|
||||
function mwtext.listToText( list, separator, conjunction )
|
||||
separator = separator or options.comma
|
||||
conjunction = conjunction or options['and']
|
||||
local n = #list
|
||||
|
||||
local ret
|
||||
if n > 1 then
|
||||
ret = table.concat( list, separator, 1, n - 1 ) .. conjunction .. list[n]
|
||||
else
|
||||
ret = tostring( list[1] or '' )
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function mwtext.truncate( text, length, ellipsis, adjustLength )
|
||||
local l = mw.ustring.len( text )
|
||||
if l <= math.abs( length ) then
|
||||
return text
|
||||
end
|
||||
|
||||
ellipsis = ellipsis or options.ellipsis
|
||||
local elen = 0
|
||||
if adjustLength then
|
||||
elen = mw.ustring.len( ellipsis )
|
||||
end
|
||||
|
||||
local ret
|
||||
if math.abs( length ) <= elen then
|
||||
ret = ellipsis
|
||||
elseif length > 0 then
|
||||
ret = mw.ustring.sub( text, 1, length - elen ) .. ellipsis
|
||||
else
|
||||
ret = ellipsis .. mw.ustring.sub( text, length + elen )
|
||||
end
|
||||
|
||||
if mw.ustring.len( ret ) < l then
|
||||
return ret
|
||||
else
|
||||
return text
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for stuff that can't even be passed to PHP properly and other stuff
|
||||
-- that gives different error messages in different versions of PHP
|
||||
local function checkForJsonEncode( t, seen, lvl )
|
||||
local tp = type( t )
|
||||
if tp == 'table' then
|
||||
if seen[t] then
|
||||
error( "mw.text.jsonEncode: Cannot use recursive tables", lvl )
|
||||
end
|
||||
seen[t] = 1
|
||||
for k, v in pairs( t ) do
|
||||
if type( k ) == 'number' then
|
||||
if k >= math.huge or k <= -math.huge then
|
||||
error( string.format( "mw.text.jsonEncode: Cannot use 'inf' as a table key", type( k ) ), lvl )
|
||||
end
|
||||
elseif type( k ) ~= 'string' then
|
||||
error( string.format( "mw.text.jsonEncode: Cannot use type '%s' as a table key", type( k ) ), lvl )
|
||||
end
|
||||
checkForJsonEncode( v, seen, lvl + 1 )
|
||||
end
|
||||
seen[t] = nil
|
||||
elseif tp == 'number' then
|
||||
if t ~= t or t >= math.huge or t <= -math.huge then
|
||||
error( "mw.text.jsonEncode: Cannot encode non-finite numbers", lvl )
|
||||
end
|
||||
elseif tp ~= 'boolean' and tp ~= 'string' and tp ~= 'nil' then
|
||||
error( string.format( "mw.text.jsonEncode: Cannot encode type '%s'", tp ), lvl )
|
||||
end
|
||||
end
|
||||
|
||||
function mwtext.jsonEncode( value, flags )
|
||||
checkForJsonEncode( value, {}, 3 )
|
||||
return php.jsonEncode( value, flags )
|
||||
end
|
||||
|
||||
function mwtext.jsonDecode( json, flags )
|
||||
return php.jsonDecode( json, flags )
|
||||
end
|
||||
|
||||
-- Matches PHP Scribunto_LuaTextLibrary constants
|
||||
mwtext.JSON_PRESERVE_KEYS = 1
|
||||
mwtext.JSON_TRY_FIXING = 2
|
||||
mwtext.JSON_PRETTY = 4
|
||||
|
||||
-- xowa:bgn
|
||||
function mwtext.notify_wiki_changed()
|
||||
init_text_for_wiki_needed = true
|
||||
end
|
||||
|
||||
function mwtext.make_proxy(real)
|
||||
local proxy = {}
|
||||
setmetatable(proxy, {__index =
|
||||
function(tbl, key)
|
||||
if init_text_for_wiki_needed then
|
||||
options = php.init_text_for_wiki() -- more performant than calling setupInterface for every page load
|
||||
init_text_for_wiki_needed = false
|
||||
end
|
||||
return real[key]
|
||||
end
|
||||
}
|
||||
)
|
||||
return proxy
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
return mwtext
|
||||
@@ -0,0 +1,392 @@
|
||||
local title = {}
|
||||
local php
|
||||
local NS_MEDIA = -2
|
||||
local init_title_for_page_needed = true -- xowa
|
||||
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local checkTypeForIndex = util.checkTypeForIndex
|
||||
|
||||
local function checkNamespace( name, argIdx, arg )
|
||||
if type( arg ) == 'string' and tostring( tonumber( arg ) ) == arg then
|
||||
arg = tonumber( arg )
|
||||
end
|
||||
if type( arg ) == 'number' then
|
||||
arg = math.floor( arg + 0.5 )
|
||||
if not mw.site.namespaces[arg] then
|
||||
local msg = string.format( "bad argument #%d to '%s' (unrecognized namespace number '%s')",
|
||||
argIdx, name, arg
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
elseif type( arg ) == 'string' then
|
||||
local ns = mw.site.namespaces[arg]
|
||||
if not ns then
|
||||
local msg = string.format( "bad argument #%d to '%s' (unrecognized namespace name '%s')",
|
||||
argIdx, name, arg
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
arg = ns.id
|
||||
else
|
||||
local msg = string.format( "bad argument #%d to '%s' (string or number expected, got %s)",
|
||||
argIdx, name, type( arg )
|
||||
)
|
||||
error( msg, 3 )
|
||||
end
|
||||
return arg
|
||||
end
|
||||
|
||||
|
||||
local function lt( a, b )
|
||||
if a.interwiki ~= b.interwiki then
|
||||
return a.interwiki < b.interwiki
|
||||
end
|
||||
if a.namespace ~= b.namespace then
|
||||
return a.namespace < b.namespace
|
||||
end
|
||||
return a.text < b.text
|
||||
end
|
||||
|
||||
local function makeTitleObject( data )
|
||||
if not data then
|
||||
return nil
|
||||
end
|
||||
|
||||
local obj = {}
|
||||
local checkSelf = util.makeCheckSelfFunction( 'mw.title', 'title', obj, 'title object' );
|
||||
local ns = mw.site.namespaces[data.namespace]
|
||||
|
||||
data.isContentPage = ns.isContent
|
||||
data.isExternal = data.interwiki ~= ''
|
||||
data.isSpecialPage = data.namespace == mw.site.namespaces.Special.id
|
||||
data.isTalkPage = ns.isTalk
|
||||
data.subjectNsText = ns.subject.name
|
||||
data.canTalk = ns.talk ~= nil
|
||||
|
||||
data.prefixedText = data.text
|
||||
if data.nsText ~= '' then
|
||||
data.prefixedText = string.gsub( data.nsText .. ':' .. data.prefixedText, '_', ' ' )
|
||||
end
|
||||
if data.interwiki ~= '' then
|
||||
data.prefixedText = data.interwiki .. ':' .. data.prefixedText
|
||||
end
|
||||
|
||||
local firstSlash, lastSlash
|
||||
if ns.hasSubpages then
|
||||
firstSlash, lastSlash = string.match( data.text, '^[^/]*().*()/[^/]*$' )
|
||||
end
|
||||
if firstSlash then
|
||||
data.isSubpage = true
|
||||
data.rootText = string.sub( data.text, 1, firstSlash - 1 )
|
||||
data.baseText = string.sub( data.text, 1, lastSlash - 1 )
|
||||
data.subpageText = string.sub( data.text, lastSlash + 1 )
|
||||
else
|
||||
data.isSubpage = false
|
||||
data.rootText = data.text
|
||||
data.baseText = data.text
|
||||
data.subpageText = data.text
|
||||
end
|
||||
|
||||
function data:inNamespace( ns )
|
||||
checkSelf( self, 'inNamespace' )
|
||||
ns = checkNamespace( 'inNamespace', 1, ns )
|
||||
return ns == self.namespace
|
||||
end
|
||||
|
||||
function data:inNamespaces( ... )
|
||||
checkSelf( self, 'inNamespaces' )
|
||||
for i = 1, select( '#', ... ) do
|
||||
local ns = checkNamespace( 'inNamespaces', i, select( i, ... ) )
|
||||
if ns == self.namespace then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function data:hasSubjectNamespace( ns )
|
||||
checkSelf( self, 'hasSubjectNamespace' )
|
||||
ns = checkNamespace( 'hasSubjectNamespace', 1, ns )
|
||||
return ns == mw.site.namespaces[self.namespace].subject.id
|
||||
end
|
||||
|
||||
function data:isSubpageOf( title )
|
||||
checkSelf( self, 'isSubpageOf' )
|
||||
checkType( 'isSubpageOf', 1, title, 'table' )
|
||||
return self.interwiki == title.interwiki and
|
||||
self.namespace == title.namespace and
|
||||
title.text .. '/' == string.sub( self.text, 1, #title.text + 1 )
|
||||
end
|
||||
|
||||
function data:subPageTitle( text )
|
||||
checkSelf( self, 'subpageTitle' )
|
||||
checkType( 'subpageTitle', 1, text, 'string' )
|
||||
return title.makeTitle( data.namespace, data.text .. '/' .. text )
|
||||
end
|
||||
|
||||
function data:partialUrl()
|
||||
checkSelf( self, 'partialUrl' )
|
||||
return data.thePartialUrl
|
||||
end
|
||||
|
||||
function data:fullUrl( query, proto )
|
||||
checkSelf( self, 'fullUrl' )
|
||||
return php.getUrl( self.fullText, 'fullUrl', query, proto )
|
||||
end
|
||||
|
||||
function data:localUrl( query )
|
||||
checkSelf( self, 'localUrl' )
|
||||
return php.getUrl( self.fullText, 'localUrl', query )
|
||||
end
|
||||
|
||||
function data:canonicalUrl( query )
|
||||
checkSelf( self, 'canonicalUrl' )
|
||||
return php.getUrl( self.fullText, 'canonicalUrl', query )
|
||||
end
|
||||
|
||||
function data:getContent()
|
||||
checkSelf( self, 'getContent' )
|
||||
local content = php.getContent( self.fullText )
|
||||
data.getContent = function ( self )
|
||||
checkSelf( self, 'getContent' )
|
||||
return content
|
||||
end
|
||||
return content
|
||||
end
|
||||
|
||||
-- Known fields, both those defined above and any dynamically handled in
|
||||
-- __index. Truthy values represent read-only, and falsey values represent
|
||||
-- read-write. If the value is the string 'e', expensive data will be loaded
|
||||
-- if the field is read.
|
||||
local readOnlyFields = {
|
||||
fragment = false,
|
||||
fullText = true,
|
||||
rootPageTitle = true,
|
||||
basePageTitle = true,
|
||||
talkPageTitle = true,
|
||||
subjectPageTitle = true,
|
||||
fileExists = true,
|
||||
file = true,
|
||||
protectionLevels = true,
|
||||
cascadingProtection = true,
|
||||
exists = 'e',
|
||||
isRedirect = 'e',
|
||||
contentModel = 'e',
|
||||
id = 'e',
|
||||
redirectTarget = true,
|
||||
}
|
||||
for k in pairs( data ) do
|
||||
readOnlyFields[k] = true
|
||||
end
|
||||
|
||||
local function pairsfunc( t, k )
|
||||
local v
|
||||
repeat
|
||||
k = next( readOnlyFields, k )
|
||||
if k == nil then
|
||||
return nil
|
||||
end
|
||||
v = t[k]
|
||||
until v ~= nil
|
||||
return k, v
|
||||
end
|
||||
|
||||
return setmetatable( obj, {
|
||||
__eq = title.equals,
|
||||
__lt = lt,
|
||||
__pairs = function ( t )
|
||||
return pairsfunc, t, nil
|
||||
end,
|
||||
__index = function ( t, k )
|
||||
if k == 'exists' and data.namespace == NS_MEDIA then
|
||||
k = 'fileExists'
|
||||
end
|
||||
|
||||
if readOnlyFields[k] == 'e' and data[k] == nil then
|
||||
for k,v in pairs( php.getExpensiveData( t.fullText ) ) do
|
||||
data[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if k == 'fullText' then
|
||||
if data.fragment ~= '' then
|
||||
return data.prefixedText .. '#' .. data.fragment
|
||||
else
|
||||
return data.prefixedText
|
||||
end
|
||||
end
|
||||
|
||||
if k == 'rootPageTitle' then
|
||||
return title.makeTitle( data.namespace, data.rootText )
|
||||
end
|
||||
if k == 'basePageTitle' then
|
||||
return title.makeTitle( data.namespace, data.baseText )
|
||||
end
|
||||
if k == 'talkPageTitle' then
|
||||
local ns = mw.site.namespaces[data.namespace].talk
|
||||
if not ns then
|
||||
return nil
|
||||
end
|
||||
if ns.id == data.namespace then
|
||||
return obj
|
||||
end
|
||||
return title.makeTitle( ns.id, data.text )
|
||||
end
|
||||
if k == 'subjectPageTitle' then
|
||||
local ns = mw.site.namespaces[data.namespace].subject
|
||||
if ns.id == data.namespace then
|
||||
return obj
|
||||
end
|
||||
return title.makeTitle( ns.id, data.text )
|
||||
end
|
||||
if k == 'file' then
|
||||
if data.file == nil then
|
||||
data.file = php.getFileInfo( data.prefixedText )
|
||||
end
|
||||
return data.file or nil
|
||||
end
|
||||
if k == 'fileExists' then -- Kept for backward compatibility. Since 1.25, file.exists is preferred over this
|
||||
return t.file and t.file.exists
|
||||
end
|
||||
if k == 'protectionLevels' then
|
||||
if data.protectionLevels == nil then
|
||||
data.protectionLevels = php.protectionLevels( data.prefixedText )
|
||||
end
|
||||
return data.protectionLevels
|
||||
end
|
||||
if k == 'cascadingProtection' then
|
||||
if data.cascadingProtection == nil then
|
||||
data.cascadingProtection = php.cascadingProtection( data.prefixedText )
|
||||
end
|
||||
return data.cascadingProtection
|
||||
end
|
||||
if k == 'redirectTarget' then
|
||||
if data.redirectTarget == nil then
|
||||
data.redirectTarget = makeTitleObject( php.redirectTarget( data.prefixedText ) ) or false
|
||||
end
|
||||
return data.redirectTarget
|
||||
end
|
||||
|
||||
return data[k]
|
||||
end,
|
||||
__newindex = function ( t, k, v )
|
||||
if k == 'fragment' then
|
||||
checkTypeForIndex( k, v, 'string' )
|
||||
v = string.gsub( v, '[%s_]+', ' ' )
|
||||
v = string.gsub( v, '^(.-) ?$', '%1' )
|
||||
data[k] = v
|
||||
elseif readOnlyFields[k] then
|
||||
error( "index '" .. k .. "' is read only", 2 )
|
||||
else
|
||||
readOnlyFields[k] = v and false -- assigns nil if v == nil, false otherwise
|
||||
rawset( t, k, v )
|
||||
end
|
||||
end,
|
||||
__tostring = function ( t )
|
||||
return t.prefixedText
|
||||
end
|
||||
} )
|
||||
end
|
||||
|
||||
function title.setupInterface( options )
|
||||
-- Boilerplate
|
||||
title.setupInterface = nil
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
NS_MEDIA = options.NS_MEDIA
|
||||
|
||||
-- xowa:bgn
|
||||
-- Set current title
|
||||
--title.getCurrentTitle = function ()
|
||||
-- return makeTitleObject( options.thisTitle )
|
||||
--end
|
||||
-- xowa:end
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.title = title
|
||||
title.notify_page_changed()
|
||||
|
||||
package.loaded['mw.title'] = title
|
||||
end
|
||||
|
||||
function title.new( text_or_id, defaultNamespace )
|
||||
return makeTitleObject( php.newTitle( text_or_id, defaultNamespace ) )
|
||||
end
|
||||
|
||||
function title.makeTitle( ns, title, fragment, interwiki )
|
||||
return makeTitleObject( php.makeTitle( ns, title, fragment, interwiki ) )
|
||||
end
|
||||
|
||||
function title.equals( a, b )
|
||||
return a.interwiki == b.interwiki and
|
||||
a.namespace == b.namespace and
|
||||
a.text == b.text
|
||||
end
|
||||
|
||||
function title.compare( a, b )
|
||||
if a.interwiki ~= b.interwiki then
|
||||
return a.interwiki < b.interwiki and -1 or 1
|
||||
end
|
||||
if a.namespace ~= b.namespace then
|
||||
return a.namespace < b.namespace and -1 or 1
|
||||
end
|
||||
if a.text ~= b.text then
|
||||
return a.text < b.text and -1 or 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- xowa:bgn
|
||||
local system_globals =
|
||||
{ ["string"] = true
|
||||
, ["xpcall"] = true
|
||||
, ["package"] = true
|
||||
, ["rawset"] = true
|
||||
, ["_VERSION"] = true
|
||||
, ["error"] = true
|
||||
, ["pcall"] = true
|
||||
, ["os"] = true
|
||||
, ["unpack"] = true
|
||||
, ["math"] = true
|
||||
, ["ipairs"] = true
|
||||
, ["require"] = true
|
||||
, ["mw"] = true
|
||||
, ["tostring"] = true
|
||||
, ["type"] = true
|
||||
, ["pairs"] = true
|
||||
, ["next"] = true
|
||||
, ["select"] = true
|
||||
, ["assert"] = true
|
||||
, ["rawget"] = true
|
||||
, ["tonumber"] = true
|
||||
, ["table"] = true
|
||||
, ["_G"] = true
|
||||
, ["rawequal"] = true
|
||||
, ["setfenv"] = true
|
||||
, ["setmetatable"] = true
|
||||
, ["getfenv"] = true
|
||||
, ["getmetatable"] = true
|
||||
, ["debug"] = true
|
||||
}
|
||||
|
||||
function title.notify_page_changed()
|
||||
local php_data = php.getCurrentTitle()
|
||||
title.getCurrentTitle = function ()
|
||||
return makeTitleObject(php_data)
|
||||
end
|
||||
local delete_list = {}
|
||||
for k,v in pairs(_G) do
|
||||
local type_name = type(v)
|
||||
if not system_globals[k] and type_name ~= 'function' and type_name ~= 'table' then
|
||||
delete_list[k] = k
|
||||
end
|
||||
end
|
||||
for k,v in pairs(delete_list) do
|
||||
_G[k] = nil
|
||||
end
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
return title
|
||||
@@ -0,0 +1,636 @@
|
||||
local uri = {}
|
||||
local urifuncs = {}
|
||||
local urimt = {}
|
||||
local php
|
||||
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local checkTypeForIndex = util.checkTypeForIndex
|
||||
local init_uri_for_page_needed = true -- xowa
|
||||
|
||||
function uri.setupInterface( options )
|
||||
-- Boilerplate
|
||||
uri.setupInterface = nil
|
||||
php = mw_interface
|
||||
mw_interface = nil
|
||||
|
||||
-- Store options
|
||||
php.options = uri.make_proxy(options) -- xowa
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.uri = uri
|
||||
|
||||
package.loaded['mw.uri'] = uri
|
||||
end
|
||||
|
||||
local function rawencode( s, space )
|
||||
space = space or '%20'
|
||||
local ret = string.gsub( s, '([^a-zA-Z0-9_.~-])', function ( c )
|
||||
if c == ' ' then
|
||||
return space
|
||||
else
|
||||
return string.format( '%%%02X', string.byte( c, 1, 1 ) )
|
||||
end
|
||||
end );
|
||||
return ret
|
||||
end
|
||||
|
||||
local function rawdecode( s )
|
||||
local ret = string.gsub( s, '%%(%x%x)', function ( hex )
|
||||
return string.char( tonumber( hex, 16 ) )
|
||||
end );
|
||||
return ret
|
||||
end
|
||||
|
||||
function uri.encode( s, enctype )
|
||||
checkType( 'encode', 1, s, 'string' )
|
||||
|
||||
enctype = string.upper( enctype or 'QUERY' )
|
||||
if enctype == 'QUERY' then
|
||||
return rawencode( s, '+' )
|
||||
elseif enctype == 'PATH' then
|
||||
return rawencode( s, '%20' )
|
||||
elseif enctype == 'WIKI' then
|
||||
return rawencode( s, '_' )
|
||||
else
|
||||
error( "bad argument #2 to 'encode' (expected QUERY, PATH, or WIKI)", 2 )
|
||||
end
|
||||
end
|
||||
|
||||
function uri.decode( s, enctype )
|
||||
checkType( 'decode', 1, s, 'string' )
|
||||
|
||||
enctype = string.upper( enctype or 'QUERY' )
|
||||
if enctype == 'QUERY' then
|
||||
return rawdecode( string.gsub( s, '%+', ' ' ) )
|
||||
elseif enctype == 'PATH' then
|
||||
return rawdecode( s )
|
||||
elseif enctype == 'WIKI' then
|
||||
return rawdecode( string.gsub( s, '_', ' ' ) )
|
||||
else
|
||||
error( "bad argument #2 to 'decode' (expected QUERY, PATH, or WIKI)", 2 )
|
||||
end
|
||||
end
|
||||
|
||||
function uri.anchorEncode( s )
|
||||
checkType( 'anchorEncode', 1, s, 'string' )
|
||||
return php.anchorEncode( s )
|
||||
end
|
||||
|
||||
function uri.localUrl( page, query )
|
||||
checkType( 'localurl', 1, page, 'string' )
|
||||
if query ~= nil and type( query ) ~= 'string' and type( query ) ~= 'table' then
|
||||
checkType( 'localurl', 2, query, 'string or table' )
|
||||
end
|
||||
local url = php.localUrl( page, query )
|
||||
if not url then
|
||||
return nil
|
||||
end
|
||||
return uri.new( url )
|
||||
end
|
||||
|
||||
function uri.fullUrl( page, query )
|
||||
checkType( 'fullurl', 1, page, 'string' )
|
||||
if query ~= nil and type( query ) ~= 'string' and type( query ) ~= 'table' then
|
||||
checkType( 'fullurl', 2, query, 'string or table' )
|
||||
end
|
||||
local url = php.fullUrl( page, query )
|
||||
if not url then
|
||||
return nil
|
||||
end
|
||||
return uri.new( url )
|
||||
end
|
||||
|
||||
function uri.canonicalUrl( page, query )
|
||||
checkType( 'canonicalurl', 1, page, 'string' )
|
||||
if query ~= nil and type( query ) ~= 'string' and type( query ) ~= 'table' then
|
||||
checkType( 'canonicalurl', 2, query, 'string or table' )
|
||||
end
|
||||
local url = php.canonicalUrl( page, query )
|
||||
if not url then
|
||||
return nil
|
||||
end
|
||||
return uri.new( url )
|
||||
end
|
||||
|
||||
|
||||
function uri.new( s )
|
||||
if s == nil or type( s ) == 'string' then
|
||||
local obj = {
|
||||
-- Yes, technically all this does nothing.
|
||||
protocol = nil,
|
||||
user = nil,
|
||||
password = nil,
|
||||
host = nil,
|
||||
port = nil,
|
||||
path = nil,
|
||||
query = nil,
|
||||
fragment = nil,
|
||||
}
|
||||
setmetatable( obj, urimt )
|
||||
obj:parse( s or php.options.defaultUrl )
|
||||
return obj
|
||||
elseif type( s ) == 'table' then
|
||||
local obj = {
|
||||
protocol = s.protocol,
|
||||
user = s.user,
|
||||
password = s.password,
|
||||
host = s.host,
|
||||
port = s.port,
|
||||
path = s.path,
|
||||
query = mw.clone( s.query ),
|
||||
fragment = s.fragment,
|
||||
}
|
||||
setmetatable( obj, urimt )
|
||||
return obj
|
||||
else
|
||||
checkType( 'new', 1, s, 'string or table or nil' )
|
||||
end
|
||||
end
|
||||
|
||||
function uri.validate( obj )
|
||||
checkType( 'validate', 1, obj, 'table' )
|
||||
|
||||
local err = {}
|
||||
|
||||
if obj.protocol then
|
||||
if type( obj.protocol ) ~= 'string' then
|
||||
err[#err+1] = '.protocol must be a string, not ' .. type( obj.protocol )
|
||||
elseif not string.match( obj.protocol, '^[^:/?#]+$' ) then
|
||||
err[#err+1] = 'invalid .protocol'
|
||||
end
|
||||
end
|
||||
|
||||
if obj.user then
|
||||
if type( obj.user ) ~= 'string' then
|
||||
err[#err+1] = '.user must be a string, not ' .. type( obj.user )
|
||||
elseif not string.match( obj.user, '^[^:@/?#]*$' ) then
|
||||
err[#err+1] = 'invalid .user'
|
||||
end
|
||||
end
|
||||
|
||||
if obj.password then
|
||||
if type( obj.password ) ~= 'string' then
|
||||
err[#err+1] = '.password must be a string, not ' .. type( obj.password )
|
||||
elseif not string.match( obj.password, '^[^:@/?#]*$' ) then
|
||||
err[#err+1] = 'invalid .password'
|
||||
end
|
||||
end
|
||||
|
||||
if obj.host then
|
||||
if type( obj.host ) ~= 'string' then
|
||||
err[#err+1] = '.host must be a string, not ' .. type( obj.host )
|
||||
elseif not string.match( obj.host, '^[^:/?#]*$' ) then
|
||||
err[#err+1] = 'invalid .host'
|
||||
end
|
||||
end
|
||||
|
||||
if obj.port then
|
||||
if type( obj.port ) ~= 'number' or math.floor( obj.port ) ~= obj.port then
|
||||
err[#err+1] = '.port must be an integer, not ' .. type( obj.port )
|
||||
elseif obj.port < 1 or obj.port > 65535 then
|
||||
err[#err+1] = 'invalid .port'
|
||||
end
|
||||
end
|
||||
|
||||
local authority = obj.user or obj.password or obj.host or obj.port
|
||||
if not obj.path then
|
||||
err[#err+1] = 'missing .path'
|
||||
elseif type( obj.path ) ~= 'string' then
|
||||
err[#err+1] = '.path must be a string, not ' .. type( obj.path )
|
||||
elseif authority and not ( obj.path == '' or string.match( obj.path, '^/[^?#]*$' ) ) then
|
||||
err[#err+1] = 'invalid .path'
|
||||
elseif not authority and not (
|
||||
obj.path == '' or
|
||||
obj.path == '/' or
|
||||
string.match( obj.path, '^/[^?#/][^?#]*$' ) or
|
||||
obj.protocol and string.match( obj.path, '^[^?#/][^?#]*$' ) or
|
||||
not obj.protocol and string.match( obj.path, '^[^?#/:]+$' ) or
|
||||
not obj.protocol and string.match( obj.path, '^[^?#/:]+/[^?#]*$' )
|
||||
) then
|
||||
err[#err+1] = 'invalid .path'
|
||||
end
|
||||
|
||||
if obj.query and type( obj.query ) ~= 'table' then
|
||||
err[#err+1] = '.query must be a table, not ' .. type( obj.query )
|
||||
end
|
||||
|
||||
if obj.fragment and type( obj.fragment ) ~= 'string' then
|
||||
err[#err+1] = '.fragment must be a string, not ' .. type( obj.fragment )
|
||||
end
|
||||
|
||||
return #err == 0, table.concat( err, '; ' )
|
||||
end
|
||||
|
||||
-- Lua tables are unsorted, but we want to preserve the insertion order.
|
||||
-- So, track the insertion order explicitly.
|
||||
local function makeQueryTable()
|
||||
local ret = {}
|
||||
local keys = {}
|
||||
local seenkeys = {}
|
||||
|
||||
setmetatable( ret, {
|
||||
__newindex = function ( t, k, v )
|
||||
if seenkeys[k] and not t[k] then
|
||||
for i = 1, #keys do
|
||||
if keys[i] == k then
|
||||
table.remove( keys, i )
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
seenkeys[k] = 1
|
||||
keys[#keys+1] = k
|
||||
rawset( t, k, v )
|
||||
end,
|
||||
__pairs = function ( t )
|
||||
local i, l = 0, #keys
|
||||
return function ()
|
||||
while i < l do
|
||||
i = i + 1
|
||||
local k = keys[i]
|
||||
if t[k] ~= nil then
|
||||
return k, t[k]
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
||||
} )
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function uri.parseQueryString( s, i, j )
|
||||
checkType( 'parseQueryString', 1, s, 'string' )
|
||||
checkType( 'parseQueryString', 2, i, 'number', true )
|
||||
checkType( 'parseQueryString', 3, j, 'number', true )
|
||||
|
||||
s = string.gsub( string.sub( s, i or 1, j or -1 ), '%+', ' ' )
|
||||
i = 1
|
||||
j = string.len( s )
|
||||
|
||||
local qs = makeQueryTable()
|
||||
if string.sub( s, i, 1 ) == '?' then
|
||||
i = i + 1
|
||||
end
|
||||
while i <= j do
|
||||
local amp = string.find( s, '&', i, true )
|
||||
if not amp or amp > j then
|
||||
amp = j + 1
|
||||
end
|
||||
local eq = string.find( s, '=', i, true )
|
||||
local k, v
|
||||
if not eq or eq > amp then
|
||||
k = rawdecode( string.sub( s, i, amp - 1 ) )
|
||||
v = false
|
||||
else
|
||||
k = rawdecode( string.sub( s, i, eq - 1 ) )
|
||||
v = rawdecode( string.sub( s, eq + 1, amp - 1 ) )
|
||||
end
|
||||
if qs[k] then
|
||||
if type( qs[k] ) ~= table then
|
||||
qs[k] = { qs[k], v }
|
||||
else
|
||||
table.insert( qs[k], v )
|
||||
end
|
||||
else
|
||||
qs[k] = v
|
||||
end
|
||||
i = amp + 1
|
||||
end
|
||||
return qs
|
||||
end
|
||||
|
||||
function uri.buildQueryString( qs )
|
||||
checkType( 'buildQueryString', 1, qs, 'table' )
|
||||
|
||||
local t = {}
|
||||
for k, v in pairs( qs ) do
|
||||
if type( v ) ~= 'table' then
|
||||
v = { v }
|
||||
end
|
||||
for i = 1, #v do
|
||||
t[#t+1] = '&'
|
||||
t[#t+1] = rawencode( k, '+' )
|
||||
if v[i] then
|
||||
t[#t+1] = '='
|
||||
t[#t+1] = rawencode( v[i], '+' )
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.concat( t, '', 2 )
|
||||
end
|
||||
|
||||
-- Fields mapped to whether they're handled by __index
|
||||
local knownFields = {
|
||||
protocol = false,
|
||||
user = false,
|
||||
password = false,
|
||||
host = false,
|
||||
port = false,
|
||||
path = false,
|
||||
query = false,
|
||||
fragment = false,
|
||||
userInfo = true,
|
||||
hostPort = true,
|
||||
authority = true,
|
||||
queryString = true,
|
||||
relativePath = true,
|
||||
}
|
||||
|
||||
local function pairsfunc( t, k )
|
||||
local v, f
|
||||
repeat
|
||||
k, f = next( knownFields, k )
|
||||
if k == nil then
|
||||
return nil
|
||||
end
|
||||
if f then
|
||||
v = t[k]
|
||||
else
|
||||
v = rawget( t, k )
|
||||
end
|
||||
until v ~= nil
|
||||
return k, v
|
||||
end
|
||||
function urimt:__pairs()
|
||||
return pairsfunc, self, nil
|
||||
end
|
||||
|
||||
function urimt:__index( key )
|
||||
if urifuncs[key] then
|
||||
return urifuncs[key]
|
||||
end
|
||||
|
||||
if key == 'userInfo' then
|
||||
local user = rawget( self, 'user' )
|
||||
local password = rawget( self, 'password' )
|
||||
if user and password then
|
||||
return user .. ':' .. password
|
||||
else
|
||||
return user
|
||||
end
|
||||
end
|
||||
|
||||
if key == 'hostPort' then
|
||||
local host = rawget( self, 'host' )
|
||||
local port = rawget( self, 'port' )
|
||||
if port then
|
||||
return ( host or '' ) .. ':' .. port
|
||||
else
|
||||
return host
|
||||
end
|
||||
end
|
||||
|
||||
if key == 'authority' then
|
||||
local info = self.userInfo
|
||||
local hostPort = self.hostPort
|
||||
if info then
|
||||
return info .. '@' .. ( hostPort or '' )
|
||||
else
|
||||
return hostPort
|
||||
end
|
||||
end
|
||||
|
||||
if key == 'queryString' then
|
||||
local query = rawget( self, 'query' )
|
||||
if not query then
|
||||
return nil
|
||||
end
|
||||
return uri.buildQueryString( query )
|
||||
end
|
||||
|
||||
if key == 'relativePath' then
|
||||
local ret = rawget( self, 'path' ) or ''
|
||||
local qs = self.queryString
|
||||
if qs then
|
||||
ret = ret .. '?' .. qs
|
||||
end
|
||||
local fragment = rawget( self, 'fragment' )
|
||||
if fragment then
|
||||
ret = ret .. '#' .. fragment
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function urimt:__newindex( key, value )
|
||||
if key == 'userInfo' then
|
||||
local user, password = nil, nil
|
||||
if value then
|
||||
checkTypeForIndex( key, value, 'string' )
|
||||
local i = string.find( value, ':', 1, true )
|
||||
if i then
|
||||
user = string.sub( value, 1, i - 1 )
|
||||
password = string.sub( value, i + 1 )
|
||||
else
|
||||
user = value
|
||||
end
|
||||
end
|
||||
rawset( self, 'user', user )
|
||||
rawset( self, 'password', password )
|
||||
return
|
||||
end
|
||||
|
||||
if key == 'hostPort' then
|
||||
local host, port = nil, nil
|
||||
if value then
|
||||
checkTypeForIndex( key, value, 'string' )
|
||||
local i = string.find( value, ':', 1, true )
|
||||
if i then
|
||||
host = string.sub( value, 1, i - 1 )
|
||||
port = tonumber( string.sub( value, i + 1 ) )
|
||||
if not port then
|
||||
error( string.format( "Invalid port in '%s'", value ), 2 )
|
||||
end
|
||||
else
|
||||
host = value
|
||||
end
|
||||
end
|
||||
rawset( self, 'host', host )
|
||||
rawset( self, 'port', port )
|
||||
return
|
||||
end
|
||||
|
||||
if key == 'authority' then
|
||||
if value then
|
||||
checkTypeForIndex( key, value, 'string' )
|
||||
local i = string.find( value, '@', 1, true )
|
||||
if i then
|
||||
self.userInfo = string.sub( value, 1, i - 1 )
|
||||
self.hostPort = string.sub( value, i + 1 )
|
||||
else
|
||||
self.userInfo = nil
|
||||
self.hostPort = value
|
||||
end
|
||||
else
|
||||
self.userInfo = nil
|
||||
self.hostPort = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if key == 'queryString' then
|
||||
if value then
|
||||
checkTypeForIndex( key, value, 'string' )
|
||||
rawset( self, 'query', uri.parseQueryString( value ) )
|
||||
else
|
||||
rawset( self, 'query', nil )
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if key == 'relativePath' then
|
||||
local path, query, fragment = nil, nil, nil
|
||||
if value then
|
||||
checkTypeForIndex( key, value, 'string' )
|
||||
local i, j = nil, string.len( value )
|
||||
i = string.find( value, '#', 1, true )
|
||||
if i and i <= j then
|
||||
fragment = string.sub( value, i + 1, j )
|
||||
j = i - 1
|
||||
end
|
||||
i = string.find( value, '?', 1, true )
|
||||
if i and i <= j then
|
||||
query = uri.parseQueryString( string.sub( value, i + 1, j ) )
|
||||
j = i - 1
|
||||
end
|
||||
path = string.sub( value, 1, j )
|
||||
end
|
||||
rawset( self, 'path', path )
|
||||
rawset( self, 'query', query )
|
||||
rawset( self, 'fragment', fragment )
|
||||
return
|
||||
end
|
||||
|
||||
if knownFields[key] then
|
||||
error( "index '" .. key .. "' is read only", 2 )
|
||||
end
|
||||
|
||||
-- Default behavior
|
||||
knownFields[key] = false
|
||||
rawset( self, key, value )
|
||||
end
|
||||
|
||||
function urimt:__tostring()
|
||||
local ret = ''
|
||||
local protocol = self.protocol
|
||||
local authority = self.authority
|
||||
if protocol then
|
||||
ret = protocol .. ':'
|
||||
end
|
||||
if authority then
|
||||
ret = ret .. '//' .. authority
|
||||
end
|
||||
return ret .. self.relativePath
|
||||
end
|
||||
|
||||
urifuncs.validate = uri.validate
|
||||
|
||||
function urifuncs:parse( s )
|
||||
checkType( 'uri:parse', 1, s, 'string' )
|
||||
|
||||
-- Since Lua's patterns can't do (...)?, we have to try with and without each part.
|
||||
local protocol, authority, relativePath = string.match( s, '^([^:/?#]+)://([^/?#]*)(.*)$' )
|
||||
if not ( protocol or authority or relativePath ) then
|
||||
authority, relativePath = string.match( s, '^//([^/?#]*)(.*)$' )
|
||||
end
|
||||
if not ( protocol or authority or relativePath ) then
|
||||
protocol, relativePath = string.match( s, '^([^:/?#]+):(.*)$' )
|
||||
end
|
||||
if not ( protocol or authority or relativePath ) then
|
||||
relativePath = s
|
||||
end
|
||||
|
||||
-- Parse it into a temporary object, so if there's an error we didn't break the real one
|
||||
local tmp = { protocol = protocol }
|
||||
setmetatable( tmp, urimt )
|
||||
if not pcall( urimt.__newindex, tmp, 'authority', authority ) then
|
||||
error( 'Invalid port number in string', 2 )
|
||||
end
|
||||
tmp.relativePath = relativePath
|
||||
|
||||
-- Check for validity
|
||||
local ok, err = uri.validate( tmp )
|
||||
if not ok then
|
||||
error( err, 2 )
|
||||
end
|
||||
|
||||
-- Merge in fields
|
||||
if tmp.protocol then
|
||||
self.protocol = tmp.protocol
|
||||
end
|
||||
if tmp.user or tmp.password then
|
||||
self.user, self.password = tmp.user, tmp.password
|
||||
end
|
||||
if tmp.host then
|
||||
self.host = tmp.host
|
||||
end
|
||||
if tmp.port then
|
||||
self.port = tmp.port
|
||||
end
|
||||
if tmp.path then
|
||||
self.path = tmp.path
|
||||
end
|
||||
if tmp.query then
|
||||
self.query = tmp.query
|
||||
end
|
||||
if tmp.fragment then
|
||||
self.fragment = tmp.fragment
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function urifuncs:clone()
|
||||
return uri.new( self )
|
||||
end
|
||||
|
||||
function urifuncs:extend( parameters )
|
||||
checkType( 'uri:extend', 1, parameters, 'table' )
|
||||
|
||||
local query = self.query
|
||||
if not query then
|
||||
query = makeQueryTable()
|
||||
self.query = query
|
||||
end
|
||||
for k, v in pairs( parameters ) do
|
||||
query[k] = v
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-- Add all urifuncs as known fields
|
||||
for k in pairs( urifuncs ) do
|
||||
knownFields[k] = true
|
||||
end
|
||||
|
||||
-- xowa:bgn
|
||||
function uri.notify_page_changed()
|
||||
init_uri_for_page_needed = true
|
||||
end
|
||||
|
||||
function uri.make_proxy(real)
|
||||
local proxy = {}
|
||||
setmetatable(proxy, {__index =
|
||||
function(tbl, key)
|
||||
if key == "defaultUrl" then
|
||||
if init_uri_for_page_needed then
|
||||
real[key] = php.init_uri_for_page()
|
||||
init_uri_for_page_needed = false
|
||||
end
|
||||
end
|
||||
return real[key]
|
||||
end
|
||||
}
|
||||
)
|
||||
return proxy
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
return uri
|
||||
@@ -0,0 +1,85 @@
|
||||
-- Get a fresh copy of the basic ustring
|
||||
local old_ustring = package.loaded.ustring
|
||||
package.loaded.ustring = nil
|
||||
local ustring = require( 'ustring' )
|
||||
package.loaded.ustring = old_ustring
|
||||
old_ustring = nil
|
||||
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
|
||||
local gmatch_init = nil
|
||||
local gmatch_callback = nil
|
||||
local function php_gmatch( s, pattern )
|
||||
checkType( 'gmatch', 1, s, 'string' )
|
||||
checkType( 'gmatch', 2, pattern, 'string' )
|
||||
|
||||
local re, capt = gmatch_init( s, pattern )
|
||||
local pos = 0
|
||||
return function()
|
||||
local ret
|
||||
pos, ret = gmatch_callback( s, re, capt, pos )
|
||||
return unpack( ret )
|
||||
end, nil, nil
|
||||
end
|
||||
|
||||
function ustring.setupInterface( opt )
|
||||
-- Boilerplate
|
||||
ustring.setupInterface = nil
|
||||
|
||||
-- Set string limits
|
||||
ustring.maxStringLength = opt.stringLengthLimit
|
||||
ustring.maxPatternLength = opt.patternLengthLimit
|
||||
|
||||
-- Gmatch
|
||||
if mw_interface.gmatch_callback and mw_interface.gmatch_init then
|
||||
gmatch_init = mw_interface.gmatch_init
|
||||
gmatch_callback = mw_interface.gmatch_callback
|
||||
ustring.gmatch = php_gmatch
|
||||
end
|
||||
mw_interface.gmatch_init = nil
|
||||
mw_interface.gmatch_callback = nil
|
||||
|
||||
-- Replace pure-lua implementation with php callbacks
|
||||
local nargs = {
|
||||
char = 0,
|
||||
find = 2,
|
||||
match = 2,
|
||||
gsub = 3,
|
||||
}
|
||||
for k, v in pairs( mw_interface ) do
|
||||
local n = nargs[k] or 1
|
||||
if n == 0 then
|
||||
ustring[k] = v
|
||||
else
|
||||
-- Avoid PHP warnings for missing arguments by checking before
|
||||
-- calling PHP.
|
||||
ustring[k] = function ( ... )
|
||||
if select( '#', ... ) < n then
|
||||
error( "too few arguments to mw.ustring." .. k, 2 )
|
||||
end
|
||||
return v( ... )
|
||||
end
|
||||
end
|
||||
end
|
||||
mw_interface = nil
|
||||
|
||||
-- Replace upper/lower with mw.language versions if available
|
||||
if mw and mw.language then
|
||||
local lang = mw.language.getContentLanguage()
|
||||
ustring.upper = function ( s )
|
||||
return lang:uc( s )
|
||||
end
|
||||
ustring.lower = function ( s )
|
||||
return lang:lc( s )
|
||||
end
|
||||
end
|
||||
|
||||
-- Register this library in the "mw" global
|
||||
mw = mw or {}
|
||||
mw.ustring = ustring
|
||||
|
||||
package.loaded['mw.ustring'] = ustring
|
||||
end
|
||||
|
||||
return ustring
|
||||
@@ -0,0 +1,255 @@
|
||||
--XOWA:updated 2017-03-16
|
||||
--[[
|
||||
Registers and defines functions to handle Wikibase Entities through the Scribunto extension.
|
||||
|
||||
@since 0.5
|
||||
|
||||
@license GNU GPL v2+
|
||||
@author Marius Hoch < hoo@online.de >
|
||||
@author Bene* < benestar.wikimedia@gmail.com >
|
||||
]]
|
||||
|
||||
local php = mw_interface
|
||||
local entity = {}
|
||||
local metatable = {}
|
||||
local methodtable = {}
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local checkTypeMulti = util.checkTypeMulti
|
||||
|
||||
metatable.__index = methodtable
|
||||
|
||||
-- Claim ranks (Claim::RANK_* in PHP)
|
||||
entity.claimRanks = {
|
||||
RANK_TRUTH = 3,
|
||||
RANK_PREFERRED = 2,
|
||||
RANK_NORMAL = 1,
|
||||
RANK_DEPRECATED = 0
|
||||
}
|
||||
|
||||
-- Create new entity object from given data
|
||||
--
|
||||
-- @param {table} data
|
||||
entity.create = function( data )
|
||||
if type( data ) ~= 'table' or type( data.schemaVersion ) ~= 'number' then
|
||||
error( 'The entity data must be a table obtained via mw.wikibase.getEntityObject' )
|
||||
end
|
||||
|
||||
if data.schemaVersion < 2 then
|
||||
error( 'mw.wikibase.entity must not be constructed using legacy data' )
|
||||
end
|
||||
|
||||
local entity = data
|
||||
setmetatable( entity, metatable )
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
-- Get a term of a given type for a given language code or the content language (on monolingual wikis)
|
||||
-- or the user's language (on multilingual wikis).
|
||||
-- Second return parameter is the language the term is in.
|
||||
--
|
||||
-- @param {table} entity
|
||||
-- @param {string} termType A valid key in the entity table (either labels, descriptions or aliases)
|
||||
-- @param {string|number} langCode
|
||||
local getTermAndLang = function( entity, termType, langCode )
|
||||
langCode = langCode or php.getLanguageCode()
|
||||
|
||||
if langCode == nil then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
if entity[termType] == nil then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local term = entity[termType][langCode]
|
||||
|
||||
if term == nil then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local actualLang = term.language or langCode
|
||||
return term.value, actualLang
|
||||
end
|
||||
|
||||
-- Get the label for a given language code or the content language (on monolingual wikis)
|
||||
-- or the user's language (on multilingual wikis).
|
||||
--
|
||||
-- @param {string|number} [langCode]
|
||||
methodtable.getLabel = function( entity, langCode )
|
||||
checkTypeMulti( 'getLabel', 1, langCode, { 'string', 'number', 'nil' } )
|
||||
|
||||
local label = getTermAndLang( entity, 'labels', langCode )
|
||||
return label
|
||||
end
|
||||
|
||||
-- Get the description for a given language code or the content language (on monolingual wikis)
|
||||
-- or the user's language (on multilingual wikis).
|
||||
--
|
||||
-- @param {string|number} [langCode]
|
||||
methodtable.getDescription = function( entity, langCode )
|
||||
checkTypeMulti( 'getDescription', 1, langCode, { 'string', 'number', 'nil' } )
|
||||
|
||||
local description = getTermAndLang( entity, 'descriptions', langCode )
|
||||
return description
|
||||
end
|
||||
|
||||
-- Get the label for a given language code or the content language (on monolingual wikis)
|
||||
-- or the user's language (on multilingual wikis).
|
||||
-- Has the language the returned label is in as an additional second return parameter.
|
||||
--
|
||||
-- @param {string|number} [langCode]
|
||||
methodtable.getLabelWithLang = function( entity, langCode )
|
||||
checkTypeMulti( 'getLabelWithLang', 1, langCode, { 'string', 'number', 'nil' } )
|
||||
|
||||
return getTermAndLang( entity, 'labels', langCode )
|
||||
end
|
||||
|
||||
-- Get the description for a given language code or the content language (on monolingual wikis)
|
||||
-- or the user's language (on multilingual wikis).
|
||||
-- Has the language the returned description is in as an additional second return parameter.
|
||||
--
|
||||
-- @param {string|number} [langCode]
|
||||
methodtable.getDescriptionWithLang = function( entity, langCode )
|
||||
checkTypeMulti( 'getDescriptionWithLang', 1, langCode, { 'string', 'number', 'nil' } )
|
||||
|
||||
return getTermAndLang( entity, 'descriptions', langCode )
|
||||
end
|
||||
|
||||
-- Get the sitelink title linking to the given site id
|
||||
--
|
||||
-- @param {string|number} [globalSiteId]
|
||||
methodtable.getSitelink = function( entity, globalSiteId )
|
||||
checkTypeMulti( 'getSitelink', 1, globalSiteId, { 'string', 'number', 'nil' } )
|
||||
|
||||
if entity.sitelinks == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
globalSiteId = globalSiteId or php.getGlobalSiteId()
|
||||
|
||||
if globalSiteId == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local sitelink = entity.sitelinks[globalSiteId]
|
||||
|
||||
if sitelink == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
return sitelink.title
|
||||
end
|
||||
|
||||
-- Get the best statements with the given property id
|
||||
--
|
||||
-- @param {string} propertyId
|
||||
methodtable.getBestStatements = function( entity, propertyId )
|
||||
if entity.claims == nil or not entity.claims[propertyId] then
|
||||
return {}
|
||||
end
|
||||
|
||||
local statements = {}
|
||||
local bestRank = 'normal'
|
||||
|
||||
for k, statement in pairs( entity.claims[propertyId] ) do
|
||||
if statement.rank == bestRank then
|
||||
statements[#statements + 1] = statement
|
||||
elseif statement.rank == 'preferred' then
|
||||
statements = { statement }
|
||||
bestRank = 'preferred'
|
||||
end
|
||||
end
|
||||
|
||||
return statements
|
||||
end
|
||||
|
||||
-- Get a table with all property ids attached to the entity.
|
||||
methodtable.getProperties = function( entity )
|
||||
if entity.claims == nil then
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Get the keys (property ids)
|
||||
local properties = {}
|
||||
|
||||
local n = 0
|
||||
for k, v in pairs( entity.claims ) do
|
||||
n = n + 1
|
||||
properties[n] = k
|
||||
end
|
||||
|
||||
return properties
|
||||
end
|
||||
|
||||
-- Get the formatted value of the claims with the given property id
|
||||
--
|
||||
-- @param {table} entity
|
||||
-- @param {string} phpFormatterFunction
|
||||
-- @param {string} propertyLabelOrId
|
||||
-- @param {table} [acceptableRanks]
|
||||
local formatValuesByPropertyId = function( entity, phpFormatterFunction, propertyLabelOrId, acceptableRanks )
|
||||
acceptableRanks = acceptableRanks or nil
|
||||
|
||||
local formatted = php[phpFormatterFunction](
|
||||
entity.id,
|
||||
propertyLabelOrId,
|
||||
acceptableRanks
|
||||
)
|
||||
|
||||
local label
|
||||
if propertyLabelOrId:match( '^P%d+$' ) then
|
||||
label = mw.wikibase.label( propertyLabelOrId )
|
||||
end
|
||||
|
||||
if label == nil then
|
||||
-- Make the label fallback on the entity id for convenience/ consistency
|
||||
label = propertyLabelOrId
|
||||
end
|
||||
|
||||
return {
|
||||
value = formatted,
|
||||
label = label
|
||||
}
|
||||
end
|
||||
|
||||
-- Format the main Snaks belonging to a Statement (which is identified by a PropertyId
|
||||
-- or the label of a Property) as wikitext escaped plain text.
|
||||
--
|
||||
-- @param {string} propertyLabelOrId
|
||||
-- @param {table} [acceptableRanks]
|
||||
methodtable.formatPropertyValues = function( entity, propertyLabelOrId, acceptableRanks )
|
||||
checkType( 'formatPropertyValues', 1, propertyLabelOrId, 'string' )
|
||||
checkTypeMulti( 'formatPropertyValues', 2, acceptableRanks, { 'table', 'nil' } )
|
||||
|
||||
return formatValuesByPropertyId(
|
||||
entity,
|
||||
'formatPropertyValues',
|
||||
propertyLabelOrId,
|
||||
acceptableRanks
|
||||
);
|
||||
end
|
||||
|
||||
-- Format the main Snaks belonging to a Statement (which is identified by a PropertyId
|
||||
-- or the label of a Property) as rich wikitext.
|
||||
--
|
||||
-- @param {string} propertyLabelOrId
|
||||
-- @param {table} [acceptableRanks]
|
||||
methodtable.formatStatements = function( entity, propertyLabelOrId, acceptableRanks )
|
||||
checkType( 'formatStatements', 1, propertyLabelOrId, 'string' )
|
||||
checkTypeMulti( 'formatStatements', 2, acceptableRanks, { 'table', 'nil' } )
|
||||
|
||||
return formatValuesByPropertyId(
|
||||
entity,
|
||||
'formatStatements',
|
||||
propertyLabelOrId,
|
||||
acceptableRanks
|
||||
);
|
||||
end
|
||||
|
||||
mw.wikibase.entity = entity
|
||||
package.loaded['mw.wikibase.entity'] = entity
|
||||
mw_interface = nil
|
||||
|
||||
return entity
|
||||
@@ -0,0 +1,418 @@
|
||||
--XOWA:updated 2018-06-21
|
||||
--[[
|
||||
Registers and defines functions to access Wikibase through the Scribunto extension
|
||||
Provides Lua setupInterface
|
||||
|
||||
@since 0.4
|
||||
|
||||
@license GNU GPL v2+
|
||||
@author Jens Ohlig < jens.ohlig@wikimedia.de >
|
||||
@author Marius Hoch < hoo@online.de >
|
||||
@author Bene* < benestar.wikimedia@gmail.com >
|
||||
]]
|
||||
|
||||
local wikibase = {}
|
||||
-- xowa:bgn "uncache" entities; DATE:2014-07-26
|
||||
local reset_entity_id = false
|
||||
local reset_entity_cache = false
|
||||
-- xowa:end
|
||||
local util = require 'libraryUtil'
|
||||
local checkType = util.checkType
|
||||
local checkTypeMulti = util.checkTypeMulti
|
||||
|
||||
local cacheSize = 15 -- Size of the LRU cache being used to cache entities
|
||||
local cacheOrder = {}
|
||||
local entityCache = {}
|
||||
|
||||
-- Cache a given entity (can also be false, in case it doesn't exist).
|
||||
--
|
||||
-- @param entityId
|
||||
-- @param entity
|
||||
local cacheEntity = function( entityId, entity )
|
||||
if #cacheOrder == cacheSize then
|
||||
local entityIdToRemove = table.remove( cacheOrder, cacheSize )
|
||||
entityCache[ entityIdToRemove ] = nil
|
||||
end
|
||||
|
||||
table.insert( cacheOrder, 1, entityId )
|
||||
entityCache[ entityId ] = entity
|
||||
end
|
||||
|
||||
-- Retrieve an entity. Will return false in case it's known to not exist
|
||||
-- and nil in case of a cache miss.
|
||||
--
|
||||
-- @param entityId
|
||||
local getCachedEntity = function( entityId )
|
||||
-- xowa:bgn
|
||||
if reset_entity_cache then
|
||||
reset_entity_cache = false
|
||||
entityCache = {}
|
||||
end
|
||||
-- xowa:end
|
||||
if entityCache[ entityId ] ~= nil then
|
||||
for cacheOrderId, cacheOrderEntityId in pairs( cacheOrder ) do
|
||||
if cacheOrderEntityId == entityId then
|
||||
table.remove( cacheOrder, cacheOrderId )
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert( cacheOrder, 1, entityId )
|
||||
end
|
||||
|
||||
return entityCache[ entityId ]
|
||||
end
|
||||
|
||||
function wikibase.setupInterface()
|
||||
local php = mw_interface
|
||||
mw_interface = nil
|
||||
|
||||
-- Caching variable for the entity id string belonging to the current page (nil if page is not linked to an entity)
|
||||
local pageEntityId = false
|
||||
|
||||
-- Get the entity id of the connected item, if id is nil. Cached.
|
||||
local getIdOfConnectedItemIfNil = function( id )
|
||||
if id == nil then
|
||||
return wikibase.getEntityIdForCurrentPage()
|
||||
end
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
-- Get the mw.wikibase.entity object for a given id. Cached.
|
||||
local getEntityObject = function( id )
|
||||
local entity = getCachedEntity( id )
|
||||
|
||||
if entity == nil then
|
||||
entity = php.getEntity( id )
|
||||
|
||||
if id ~= wikibase.getEntityIdForCurrentPage() then
|
||||
-- Accessing an arbitrary entity is supposed to increment the expensive function count
|
||||
php.incrementExpensiveFunctionCount()
|
||||
end
|
||||
|
||||
if type( entity ) ~= 'table' then
|
||||
entity = false
|
||||
end
|
||||
|
||||
cacheEntity( id, entity )
|
||||
end
|
||||
|
||||
if type( entity ) ~= 'table' then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Use a deep clone here, so that people can't modify the entity
|
||||
return wikibase.entity.create( mw.clone( entity ) )
|
||||
end
|
||||
|
||||
-- Get the entity id for the current page. Cached.
|
||||
-- Nil if not linked to an entity.
|
||||
wikibase.getEntityIdForCurrentPage = function()
|
||||
-- xowa:bgn
|
||||
if reset_entity_id then
|
||||
reset_entity_id = false
|
||||
pageEntityId = false
|
||||
end
|
||||
-- xowa:end
|
||||
if pageEntityId == false then
|
||||
pageEntityId = php.getEntityId( tostring( mw.title.getCurrentTitle().prefixedText ) )
|
||||
end
|
||||
|
||||
return pageEntityId
|
||||
end
|
||||
|
||||
-- Takes a page title string either in the local wiki or another wiki on the same cluster
|
||||
-- specified by the global site identifier, and returns the item ID connected via a sitelink, if
|
||||
-- one exists. Returns nil if there's no linked item.
|
||||
--
|
||||
-- @param {string} pageTitle
|
||||
-- @param {string} [globalSiteId]
|
||||
wikibase.getEntityIdForTitle = function( pageTitle, globalSiteId )
|
||||
checkType( 'getEntityIdForTitle', 1, pageTitle, 'string' )
|
||||
checkTypeMulti( 'getEntityIdForTitle', 2, globalSiteId, { 'string', 'nil' } )
|
||||
return php.getEntityId( pageTitle, globalSiteId )
|
||||
end
|
||||
|
||||
-- Get the mw.wikibase.entity object for the current page or for the
|
||||
-- specified id.
|
||||
--
|
||||
-- @param {string} [id]
|
||||
wikibase.getEntity = function( id )
|
||||
checkTypeMulti( 'getEntity', 1, id, { 'string', 'nil' } )
|
||||
|
||||
id = getIdOfConnectedItemIfNil( id )
|
||||
|
||||
if id == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- if not php.getSetting( 'allowArbitraryDataAccess' ) and id ~= wikibase.getEntityIdForCurrentPage() then
|
||||
-- error( 'Access to arbitrary entities has been disabled.', 2 )
|
||||
-- end
|
||||
|
||||
return getEntityObject( id )
|
||||
end
|
||||
|
||||
-- getEntityObject is an alias for getEntity as these used to be different.
|
||||
wikibase.getEntityObject = wikibase.getEntity
|
||||
|
||||
-- @param {string} entityId
|
||||
-- @param {string} propertyId
|
||||
-- @param {string} funcName for error logging
|
||||
-- @param {string} rank Which statements to include. Either "best" or "all".
|
||||
local getEntityStatements = function( entityId, propertyId, funcName, rank )
|
||||
-- if not php.getSetting( 'allowArbitraryDataAccess' ) and entityId ~= wikibase.getEntityIdForCurrentPage() then
|
||||
-- error( 'Access to arbitrary entities has been disabled.', 2 )
|
||||
-- end
|
||||
|
||||
checkType( funcName, 1, entityId, 'string' )
|
||||
checkType( funcName, 2, propertyId, 'string' )
|
||||
|
||||
local statements = php.getEntityStatements( entityId, propertyId, rank )
|
||||
if statements and statements[propertyId] then
|
||||
return statements[propertyId]
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Returns a table with the "best" statements matching the given property ID on the given entity
|
||||
-- ID. The definition of "best" is that the function will return "preferred" statements, if
|
||||
-- there are any, otherwise "normal" ranked statements. It will never return "deprecated"
|
||||
-- statements. This is what you usually want when surfacing values to an ordinary reader.
|
||||
--
|
||||
-- @param {string} entityId
|
||||
-- @param {string} propertyId
|
||||
wikibase.getBestStatements = function( entityId, propertyId )
|
||||
return getEntityStatements( entityId, propertyId, 'getBestStatements', 'best' )
|
||||
end
|
||||
|
||||
-- Returns a table with all statements (including all ranks, even "deprecated") matching the
|
||||
-- given property ID on the given entity ID.
|
||||
--
|
||||
-- @param {string} entityId
|
||||
-- @param {string} propertyId
|
||||
wikibase.getAllStatements = function( entityId, propertyId )
|
||||
return getEntityStatements( entityId, propertyId, 'getAllStatements', 'all' )
|
||||
end
|
||||
|
||||
-- Get the URL for the given entity id, if specified, or of the
|
||||
-- connected entity, if exists.
|
||||
--
|
||||
-- @param {string} [id]
|
||||
wikibase.getEntityUrl = function( id )
|
||||
checkTypeMulti( 'getEntityUrl', 1, id, { 'string', 'nil' } )
|
||||
|
||||
id = getIdOfConnectedItemIfNil( id )
|
||||
|
||||
if id == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
return php.getEntityUrl( id )
|
||||
end
|
||||
|
||||
-- Get the label, label language for the given entity id, if specified,
|
||||
-- or of the connected entity, if exists.
|
||||
--
|
||||
-- @param {string} [id]
|
||||
wikibase.getLabelWithLang = function( id )
|
||||
checkTypeMulti( 'getLabelWithLang', 1, id, { 'string', 'nil' } )
|
||||
|
||||
id = getIdOfConnectedItemIfNil( id )
|
||||
|
||||
if id == nil then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
return php.getLabel( id )
|
||||
end
|
||||
|
||||
-- Like wikibase.getLabelWithLang, but only returns the plain label.
|
||||
--
|
||||
-- @param {string} [id]
|
||||
wikibase.getLabel = function( id )
|
||||
checkTypeMulti( 'getLabel', 1, id, { 'string', 'nil' } )
|
||||
local label = wikibase.getLabelWithLang( id )
|
||||
|
||||
return label
|
||||
end
|
||||
|
||||
-- Legacy alias for getLabel
|
||||
wikibase.label = wikibase.getLabel
|
||||
|
||||
-- Get the label in languageCode for the given entity id.
|
||||
--
|
||||
-- @param {string} id
|
||||
-- @param {string} languageCode
|
||||
wikibase.getLabelByLang = function( id, languageCode )
|
||||
checkType( 'getLabelByLang', 1, id, 'string' )
|
||||
checkType( 'getLabelByLang', 2, languageCode, 'string' )
|
||||
|
||||
return php.getLabelByLanguage( id, languageCode )
|
||||
end
|
||||
|
||||
-- Get the description, description language for the given entity id, if specified,
|
||||
-- or of the connected entity, if exists.
|
||||
--
|
||||
-- @param {string} [id]
|
||||
wikibase.getDescriptionWithLang = function( id )
|
||||
checkTypeMulti( 'getDescriptionWithLang', 1, id, { 'string', 'nil' } )
|
||||
|
||||
id = getIdOfConnectedItemIfNil( id )
|
||||
|
||||
if id == nil then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
return php.getDescription( id )
|
||||
end
|
||||
|
||||
-- Like wikibase.getDescriptionWithLang, but only returns the plain description.
|
||||
--
|
||||
-- @param {string} [id]
|
||||
wikibase.getDescription = function( id )
|
||||
checkTypeMulti( 'getDescription', 1, id, { 'string', 'nil' } )
|
||||
local description = wikibase.getDescriptionWithLang( id )
|
||||
|
||||
return description
|
||||
end
|
||||
|
||||
-- Legacy alias for getDescription
|
||||
wikibase.description = wikibase.getDescription
|
||||
|
||||
-- Get the local sitelink title for the given entity id.
|
||||
--
|
||||
-- @param {string} itemId
|
||||
-- @param {string} [globalSiteId]
|
||||
wikibase.getSitelink = function( itemId, globalSiteId )
|
||||
checkType( 'getSitelink', 1, itemId, 'string' )
|
||||
checkTypeMulti( 'getSitelink', 2, globalSiteId, { 'string', 'nil' } )
|
||||
|
||||
return php.getSiteLinkPageName( itemId, globalSiteId )
|
||||
end
|
||||
|
||||
-- Legacy alias for getSitelink
|
||||
wikibase.sitelink = wikibase.getSitelink
|
||||
|
||||
-- Is this a valid (parseable) entity id?
|
||||
--
|
||||
-- @param {string} entityIdSerialization
|
||||
wikibase.isValidEntityId = function( entityIdSerialization )
|
||||
checkType( 'isValidEntityId', 1, entityIdSerialization, 'string' )
|
||||
|
||||
return php.isValidEntityId( entityIdSerialization )
|
||||
end
|
||||
|
||||
-- Does the entity in question exist?
|
||||
--
|
||||
-- @param {string} entityId
|
||||
wikibase.entityExists = function( entityId )
|
||||
checkType( 'entityExists', 1, entityId, 'string' )
|
||||
|
||||
if not php.getSetting( 'allowArbitraryDataAccess' ) and entityId ~= wikibase.getEntityIdForCurrentPage() then
|
||||
error( 'Access to arbitrary entities has been disabled.', 2 )
|
||||
end
|
||||
|
||||
return php.entityExists( entityId )
|
||||
end
|
||||
|
||||
-- Render a Snak value from its serialization as wikitext escaped plain text.
|
||||
--
|
||||
-- @param {table} snakSerialization
|
||||
wikibase.renderSnak = function( snakSerialization )
|
||||
checkType( 'renderSnak', 1, snakSerialization, 'table' )
|
||||
|
||||
return php.renderSnak( snakSerialization )
|
||||
end
|
||||
|
||||
-- Render a Snak value from its serialization as rich wikitext.
|
||||
--
|
||||
-- @param {table} snakSerialization
|
||||
wikibase.formatValue = function( snakSerialization )
|
||||
checkType( 'formatValue', 1, snakSerialization, 'table' )
|
||||
|
||||
return php.formatValue( snakSerialization )
|
||||
end
|
||||
|
||||
-- Render a list of Snak values from their serialization as wikitext escaped plain text.
|
||||
--
|
||||
-- @param {table} snaksSerialization
|
||||
wikibase.renderSnaks = function( snaksSerialization )
|
||||
checkType( 'renderSnaks', 1, snaksSerialization, 'table' )
|
||||
|
||||
return php.renderSnaks( snaksSerialization )
|
||||
end
|
||||
|
||||
-- Render a list of Snak values from their serialization as rich wikitext.
|
||||
--
|
||||
-- @param {table} snaksSerialization
|
||||
wikibase.formatValues = function( snaksSerialization )
|
||||
checkType( 'formatValues', 1, snaksSerialization, 'table' )
|
||||
|
||||
return php.formatValues( snaksSerialization )
|
||||
end
|
||||
|
||||
-- Returns a property id for the given label or id
|
||||
--
|
||||
-- @param {string} propertyLabelOrId
|
||||
wikibase.resolvePropertyId = function( propertyLabelOrId )
|
||||
checkType( 'resolvePropertyId', 1, propertyLabelOrId, 'string' )
|
||||
|
||||
return php.resolvePropertyId( propertyLabelOrId )
|
||||
end
|
||||
|
||||
-- Returns a table of the given property IDs ordered
|
||||
--
|
||||
-- @param {table} propertyIds
|
||||
wikibase.orderProperties = function( propertyIds )
|
||||
checkType( 'orderProperties', 1, propertyIds, 'table' )
|
||||
return php.orderProperties( propertyIds )
|
||||
end
|
||||
|
||||
-- Returns an ordered table of serialized property IDs
|
||||
wikibase.getPropertyOrder = function()
|
||||
return php.getPropertyOrder()
|
||||
end
|
||||
|
||||
-- Get the closest referenced entity (out of toIds), from a given entity.
|
||||
--
|
||||
-- @param {string} fromEntityId
|
||||
-- @param {string} propertyId
|
||||
-- @param {table} toIds
|
||||
wikibase.getReferencedEntityId = function( fromEntityId, propertyId, toIds )
|
||||
checkType( 'getReferencedEntityId', 1, fromEntityId, 'string' )
|
||||
checkType( 'getReferencedEntityId', 2, propertyId, 'string' )
|
||||
checkType( 'getReferencedEntityId', 3, toIds, 'table' )
|
||||
|
||||
-- Check the type of all toId values, Scribunto has no function for this yet (T196048)
|
||||
for i, toId in ipairs( toIds ) do
|
||||
if type( toId ) ~= 'string' then
|
||||
error(
|
||||
'toIds value at index ' .. i .. ' must be string, ' .. type( toId ) .. ' given.',
|
||||
1
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if not php.getSetting( 'allowArbitraryDataAccess' ) then
|
||||
error( 'Access to arbitrary entities has been disabled.', 2 )
|
||||
end
|
||||
|
||||
return php.getReferencedEntityId( fromEntityId, propertyId, toIds )
|
||||
end
|
||||
|
||||
mw = mw or {}
|
||||
mw.wikibase = wikibase
|
||||
package.loaded['mw.wikibase'] = wikibase
|
||||
wikibase.setupInterface = nil
|
||||
end
|
||||
|
||||
-- xowa:bgn
|
||||
function wikibase.notify_page_changed()
|
||||
reset_entity_id = true
|
||||
reset_entity_cache = true
|
||||
end
|
||||
-- xowa:end
|
||||
|
||||
return wikibase
|
||||
@@ -0,0 +1,134 @@
|
||||
-- This file is for anything that needs to be set up before a Lua engine can
|
||||
-- start. Things in this file may run more than once, so avoid putting anything
|
||||
-- other than function definitions in it. Also, because this can run before
|
||||
-- PHP can do anything, mw_interface is unavailable here.
|
||||
|
||||
mw = mw or {}
|
||||
|
||||
-- Extend pairs and ipairs to recognize __pairs and __ipairs, if they don't already
|
||||
do
|
||||
local t = {}
|
||||
setmetatable( t, { __pairs = function() return 1, 2, 3 end } )
|
||||
local f = pairs( t )
|
||||
if f ~= 1 then
|
||||
local old_pairs = pairs
|
||||
pairs = function ( t )
|
||||
local mt = getmetatable( t )
|
||||
local f, s, var = ( mt and mt.__pairs or old_pairs )( t )
|
||||
return f, s, var
|
||||
end
|
||||
local old_ipairs = ipairs
|
||||
ipairs = function ( t )
|
||||
local mt = getmetatable( t )
|
||||
local f, s, var = ( mt and mt.__ipairs or old_ipairs )( t )
|
||||
return f, s, var
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Do a "deep copy" of a table or other value.
|
||||
function mw.clone( val )
|
||||
local tableRefs = {}
|
||||
local function recursiveClone( val )
|
||||
if type( val ) == 'table' then
|
||||
-- Encode circular references correctly
|
||||
if tableRefs[val] ~= nil then
|
||||
return tableRefs[val]
|
||||
end
|
||||
|
||||
local retVal
|
||||
retVal = {}
|
||||
tableRefs[val] = retVal
|
||||
|
||||
-- Copy metatable
|
||||
if getmetatable( val ) then
|
||||
setmetatable( retVal, recursiveClone( getmetatable( val ) ) )
|
||||
end
|
||||
|
||||
for key, elt in pairs( val ) do
|
||||
retVal[key] = recursiveClone( elt )
|
||||
end
|
||||
return retVal
|
||||
else
|
||||
return val
|
||||
end
|
||||
end
|
||||
return recursiveClone( val )
|
||||
end
|
||||
|
||||
--- Make isolation-safe setfenv and getfenv functions
|
||||
--
|
||||
-- @param protectedEnvironments A table where the keys are protected environment
|
||||
-- tables. These environments cannot be accessed with getfenv(), and
|
||||
-- functions with these environments cannot be modified or accessed using
|
||||
-- integer indexes to setfenv(). However, functions with these environments
|
||||
-- can have their environment set with setfenv() with a function value
|
||||
-- argument.
|
||||
--
|
||||
-- @param protectedFunctions A table where the keys are protected functions,
|
||||
-- which cannot have their environments set by setfenv() with a function
|
||||
-- value argument.
|
||||
--
|
||||
-- @return setfenv
|
||||
-- @return getfenv
|
||||
function mw.makeProtectedEnvFuncs( protectedEnvironments, protectedFunctions )
|
||||
local old_setfenv = setfenv
|
||||
local old_getfenv = getfenv
|
||||
|
||||
local function my_setfenv( func, newEnv )
|
||||
if type( func ) == 'number' then
|
||||
local stackIndex = math.floor( func )
|
||||
if stackIndex <= 0 then
|
||||
error( "'setfenv' cannot set the global environment, it is protected", 2 )
|
||||
end
|
||||
if stackIndex > 10 then
|
||||
error( "'setfenv' cannot set an environment at a level greater than 10", 2 )
|
||||
end
|
||||
|
||||
-- Add one because we are still in Lua and 1 is right here
|
||||
stackIndex = stackIndex + 1
|
||||
|
||||
local env = old_getfenv( stackIndex )
|
||||
if env == nil or protectedEnvironments[ env ] then
|
||||
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
|
||||
end
|
||||
func = old_setfenv( stackIndex, newEnv )
|
||||
elseif type( func ) == 'function' then
|
||||
if protectedFunctions[func] then
|
||||
error( "'setfenv' cannot be called on a protected function", 2 )
|
||||
end
|
||||
local env = old_getfenv( func )
|
||||
if env == nil or protectedEnvironments[ env ] then
|
||||
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
|
||||
end
|
||||
old_setfenv( func, newEnv )
|
||||
else
|
||||
error( "'setfenv' can only be called with a function or integer as the first argument", 2 )
|
||||
end
|
||||
return func
|
||||
end
|
||||
|
||||
local function my_getfenv( func )
|
||||
local env
|
||||
if type( func ) == 'number' then
|
||||
if func <= 0 then
|
||||
error( "'getfenv' cannot get the global environment" )
|
||||
end
|
||||
env = old_getfenv( func + 1 )
|
||||
elseif type( func ) == 'function' then
|
||||
env = old_getfenv( func )
|
||||
else
|
||||
error( "'getfenv' cannot get the global environment" )
|
||||
end
|
||||
|
||||
if protectedEnvironments[env] then
|
||||
return nil
|
||||
else
|
||||
return env
|
||||
end
|
||||
end
|
||||
|
||||
return my_setfenv, my_getfenv
|
||||
end
|
||||
|
||||
return mw
|
||||
@@ -0,0 +1,127 @@
|
||||
--[[
|
||||
-- A package library similar to the one that comes with Lua 5.1, but without
|
||||
-- the local filesystem access. Based on Compat-5.1 which comes with the
|
||||
-- following license notice:
|
||||
--
|
||||
-- Copyright © 2004-2006 The Kepler Project.
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
-- the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
-- subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
--]]
|
||||
|
||||
|
||||
local assert, error, ipairs, setmetatable, type = assert, error, ipairs, setmetatable, type
|
||||
local format = string.format
|
||||
|
||||
--
|
||||
-- avoid overwriting the package table if it's already there
|
||||
--
|
||||
package = package or {}
|
||||
local _PACKAGE = package
|
||||
|
||||
package.loaded = package.loaded or {}
|
||||
package.loaded.debug = debug
|
||||
package.loaded.string = string
|
||||
package.loaded.math = math
|
||||
package.loaded.io = io
|
||||
package.loaded.os = os
|
||||
package.loaded.table = table
|
||||
package.loaded._G = _G
|
||||
package.loaded.coroutine = coroutine
|
||||
package.loaded.package = package
|
||||
local _LOADED = package.loaded
|
||||
|
||||
--
|
||||
-- avoid overwriting the package.preload table if it's already there
|
||||
--
|
||||
package.preload = package.preload or {}
|
||||
local _PRELOAD = package.preload
|
||||
|
||||
--
|
||||
-- check whether library is already loaded
|
||||
--
|
||||
local function loader_preload (name)
|
||||
assert (type(name) == "string", format (
|
||||
"bad argument #1 to `require' (string expected, got %s)", type(name)))
|
||||
assert (type(_PRELOAD) == "table", "`package.preload' must be a table")
|
||||
return _PRELOAD[name]
|
||||
end
|
||||
|
||||
-- create `loaders' table
|
||||
package.loaders = package.loaders or { loader_preload }
|
||||
local _LOADERS = package.loaders
|
||||
|
||||
--
|
||||
-- iterate over available loaders
|
||||
--
|
||||
local function load (name, loaders)
|
||||
-- iterate over available loaders
|
||||
assert (type (loaders) == "table", "`package.loaders' must be a table")
|
||||
for i, loader in ipairs (loaders) do
|
||||
local f = loader (name)
|
||||
if f then
|
||||
return f
|
||||
end
|
||||
end
|
||||
error (format ("module `%s' not found", name))
|
||||
end
|
||||
|
||||
-- sentinel
|
||||
local sentinel = function () end
|
||||
|
||||
--
|
||||
-- require
|
||||
--
|
||||
function _G.require (modname)
|
||||
assert (type(modname) == "string", format (
|
||||
"bad argument #1 to `require' (string expected, got %s)", type(modname)))
|
||||
local p = _LOADED[modname]
|
||||
if p then -- is it there?
|
||||
if p == sentinel then
|
||||
error (format ("loop or previous error loading module '%s'", modname))
|
||||
end
|
||||
return p -- package is already loaded
|
||||
end
|
||||
local init = load (modname, _LOADERS)
|
||||
_LOADED[modname] = sentinel
|
||||
local actual_arg = _G.arg
|
||||
_G.arg = { modname }
|
||||
local res = init (modname)
|
||||
if res then
|
||||
_LOADED[modname] = res
|
||||
end
|
||||
_G.arg = actual_arg
|
||||
if _LOADED[modname] == sentinel then
|
||||
_LOADED[modname] = true
|
||||
end
|
||||
return _LOADED[modname]
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- package.seeall function
|
||||
--
|
||||
function _PACKAGE.seeall (module)
|
||||
local t = type(module)
|
||||
assert (t == "table", "bad argument #1 to package.seeall (table expected, got "..t..")")
|
||||
local meta = getmetatable (module)
|
||||
if not meta then
|
||||
meta = {}
|
||||
setmetatable (module, meta)
|
||||
end
|
||||
meta.__index = _G
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
This is ustring, a pure-Lua library to handle UTF-8 strings.
|
||||
|
||||
It implements generally the same interface as the standard string library, with
|
||||
the following differences:
|
||||
* Most functions work on codepoints rather than bytes or characters. Yes, this
|
||||
means that even though "á" and "á" should appear identical and represent the
|
||||
same character, the former is one codepoint (U+00E1) while the latter is two
|
||||
(U+0061 U+0301).
|
||||
* Added functions isutf8, byteoffset, codepoint, gcodepoint, toNFC, toNFD.
|
||||
* No workalike for string.reverse is provided.
|
||||
|
||||
Contents:
|
||||
* README - This file.
|
||||
* ustring.lua - The main file for the library.
|
||||
* string.lua - Extend the string metatable with methods from this library.
|
||||
* upper.lua - Data table for ustring.upper.
|
||||
* lower.lua - Data table for ustring.lower.
|
||||
* charsets.lua - Data tables for pattern matching functions.
|
||||
* make-tables.php - Regenerate upper.lua and lower.lua using PHP's multibyte
|
||||
string library, and charsets.lua using PCRE.
|
||||
* normalization-data.lua - Data tables for toNFC and toNFD.
|
||||
* make-normalization-table.php - Regenerate normalization-data.lua based on the
|
||||
file includes/normal/UtfNormalData.inc from MediaWiki core.
|
||||
|
||||
|
||||
This library (consisting of the files described above) is released under the MIT
|
||||
License:
|
||||
|
||||
Copyright (C) 2012 Brad Jorsch <bjorsch@wikimedia.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,968 @@
|
||||
-- This file is automatically generated by make-tables.php
|
||||
return {
|
||||
["A"] = "a",
|
||||
["B"] = "b",
|
||||
["C"] = "c",
|
||||
["D"] = "d",
|
||||
["E"] = "e",
|
||||
["F"] = "f",
|
||||
["G"] = "g",
|
||||
["H"] = "h",
|
||||
["I"] = "i",
|
||||
["J"] = "j",
|
||||
["K"] = "k",
|
||||
["L"] = "l",
|
||||
["M"] = "m",
|
||||
["N"] = "n",
|
||||
["O"] = "o",
|
||||
["P"] = "p",
|
||||
["Q"] = "q",
|
||||
["R"] = "r",
|
||||
["S"] = "s",
|
||||
["T"] = "t",
|
||||
["U"] = "u",
|
||||
["V"] = "v",
|
||||
["W"] = "w",
|
||||
["X"] = "x",
|
||||
["Y"] = "y",
|
||||
["Z"] = "z",
|
||||
["À"] = "à",
|
||||
["Á"] = "á",
|
||||
["Â"] = "â",
|
||||
["Ã"] = "ã",
|
||||
["Ä"] = "ä",
|
||||
["Å"] = "å",
|
||||
["Æ"] = "æ",
|
||||
["Ç"] = "ç",
|
||||
["È"] = "è",
|
||||
["É"] = "é",
|
||||
["Ê"] = "ê",
|
||||
["Ë"] = "ë",
|
||||
["Ì"] = "ì",
|
||||
["Í"] = "í",
|
||||
["Î"] = "î",
|
||||
["Ï"] = "ï",
|
||||
["Ð"] = "ð",
|
||||
["Ñ"] = "ñ",
|
||||
["Ò"] = "ò",
|
||||
["Ó"] = "ó",
|
||||
["Ô"] = "ô",
|
||||
["Õ"] = "õ",
|
||||
["Ö"] = "ö",
|
||||
["Ø"] = "ø",
|
||||
["Ù"] = "ù",
|
||||
["Ú"] = "ú",
|
||||
["Û"] = "û",
|
||||
["Ü"] = "ü",
|
||||
["Ý"] = "ý",
|
||||
["Þ"] = "þ",
|
||||
["Ā"] = "ā",
|
||||
["Ă"] = "ă",
|
||||
["Ą"] = "ą",
|
||||
["Ć"] = "ć",
|
||||
["Ĉ"] = "ĉ",
|
||||
["Ċ"] = "ċ",
|
||||
["Č"] = "č",
|
||||
["Ď"] = "ď",
|
||||
["Đ"] = "đ",
|
||||
["Ē"] = "ē",
|
||||
["Ĕ"] = "ĕ",
|
||||
["Ė"] = "ė",
|
||||
["Ę"] = "ę",
|
||||
["Ě"] = "ě",
|
||||
["Ĝ"] = "ĝ",
|
||||
["Ğ"] = "ğ",
|
||||
["Ġ"] = "ġ",
|
||||
["Ģ"] = "ģ",
|
||||
["Ĥ"] = "ĥ",
|
||||
["Ħ"] = "ħ",
|
||||
["Ĩ"] = "ĩ",
|
||||
["Ī"] = "ī",
|
||||
["Ĭ"] = "ĭ",
|
||||
["Į"] = "į",
|
||||
["İ"] = "i",
|
||||
["IJ"] = "ij",
|
||||
["Ĵ"] = "ĵ",
|
||||
["Ķ"] = "ķ",
|
||||
["Ĺ"] = "ĺ",
|
||||
["Ļ"] = "ļ",
|
||||
["Ľ"] = "ľ",
|
||||
["Ŀ"] = "ŀ",
|
||||
["Ł"] = "ł",
|
||||
["Ń"] = "ń",
|
||||
["Ņ"] = "ņ",
|
||||
["Ň"] = "ň",
|
||||
["Ŋ"] = "ŋ",
|
||||
["Ō"] = "ō",
|
||||
["Ŏ"] = "ŏ",
|
||||
["Ő"] = "ő",
|
||||
["Œ"] = "œ",
|
||||
["Ŕ"] = "ŕ",
|
||||
["Ŗ"] = "ŗ",
|
||||
["Ř"] = "ř",
|
||||
["Ś"] = "ś",
|
||||
["Ŝ"] = "ŝ",
|
||||
["Ş"] = "ş",
|
||||
["Š"] = "š",
|
||||
["Ţ"] = "ţ",
|
||||
["Ť"] = "ť",
|
||||
["Ŧ"] = "ŧ",
|
||||
["Ũ"] = "ũ",
|
||||
["Ū"] = "ū",
|
||||
["Ŭ"] = "ŭ",
|
||||
["Ů"] = "ů",
|
||||
["Ű"] = "ű",
|
||||
["Ų"] = "ų",
|
||||
["Ŵ"] = "ŵ",
|
||||
["Ŷ"] = "ŷ",
|
||||
["Ÿ"] = "ÿ",
|
||||
["Ź"] = "ź",
|
||||
["Ż"] = "ż",
|
||||
["Ž"] = "ž",
|
||||
["Ɓ"] = "ɓ",
|
||||
["Ƃ"] = "ƃ",
|
||||
["Ƅ"] = "ƅ",
|
||||
["Ɔ"] = "ɔ",
|
||||
["Ƈ"] = "ƈ",
|
||||
["Ɖ"] = "ɖ",
|
||||
["Ɗ"] = "ɗ",
|
||||
["Ƌ"] = "ƌ",
|
||||
["Ǝ"] = "ǝ",
|
||||
["Ə"] = "ə",
|
||||
["Ɛ"] = "ɛ",
|
||||
["Ƒ"] = "ƒ",
|
||||
["Ɠ"] = "ɠ",
|
||||
["Ɣ"] = "ɣ",
|
||||
["Ɩ"] = "ɩ",
|
||||
["Ɨ"] = "ɨ",
|
||||
["Ƙ"] = "ƙ",
|
||||
["Ɯ"] = "ɯ",
|
||||
["Ɲ"] = "ɲ",
|
||||
["Ɵ"] = "ɵ",
|
||||
["Ơ"] = "ơ",
|
||||
["Ƣ"] = "ƣ",
|
||||
["Ƥ"] = "ƥ",
|
||||
["Ʀ"] = "ʀ",
|
||||
["Ƨ"] = "ƨ",
|
||||
["Ʃ"] = "ʃ",
|
||||
["Ƭ"] = "ƭ",
|
||||
["Ʈ"] = "ʈ",
|
||||
["Ư"] = "ư",
|
||||
["Ʊ"] = "ʊ",
|
||||
["Ʋ"] = "ʋ",
|
||||
["Ƴ"] = "ƴ",
|
||||
["Ƶ"] = "ƶ",
|
||||
["Ʒ"] = "ʒ",
|
||||
["Ƹ"] = "ƹ",
|
||||
["Ƽ"] = "ƽ",
|
||||
["DŽ"] = "dž",
|
||||
["LJ"] = "lj",
|
||||
["NJ"] = "nj",
|
||||
["Ǎ"] = "ǎ",
|
||||
["Ǐ"] = "ǐ",
|
||||
["Ǒ"] = "ǒ",
|
||||
["Ǔ"] = "ǔ",
|
||||
["Ǖ"] = "ǖ",
|
||||
["Ǘ"] = "ǘ",
|
||||
["Ǚ"] = "ǚ",
|
||||
["Ǜ"] = "ǜ",
|
||||
["Ǟ"] = "ǟ",
|
||||
["Ǡ"] = "ǡ",
|
||||
["Ǣ"] = "ǣ",
|
||||
["Ǥ"] = "ǥ",
|
||||
["Ǧ"] = "ǧ",
|
||||
["Ǩ"] = "ǩ",
|
||||
["Ǫ"] = "ǫ",
|
||||
["Ǭ"] = "ǭ",
|
||||
["Ǯ"] = "ǯ",
|
||||
["DZ"] = "dz",
|
||||
["Ǵ"] = "ǵ",
|
||||
["Ƕ"] = "ƕ",
|
||||
["Ƿ"] = "ƿ",
|
||||
["Ǹ"] = "ǹ",
|
||||
["Ǻ"] = "ǻ",
|
||||
["Ǽ"] = "ǽ",
|
||||
["Ǿ"] = "ǿ",
|
||||
["Ȁ"] = "ȁ",
|
||||
["Ȃ"] = "ȃ",
|
||||
["Ȅ"] = "ȅ",
|
||||
["Ȇ"] = "ȇ",
|
||||
["Ȉ"] = "ȉ",
|
||||
["Ȋ"] = "ȋ",
|
||||
["Ȍ"] = "ȍ",
|
||||
["Ȏ"] = "ȏ",
|
||||
["Ȑ"] = "ȑ",
|
||||
["Ȓ"] = "ȓ",
|
||||
["Ȕ"] = "ȕ",
|
||||
["Ȗ"] = "ȗ",
|
||||
["Ș"] = "ș",
|
||||
["Ț"] = "ț",
|
||||
["Ȝ"] = "ȝ",
|
||||
["Ȟ"] = "ȟ",
|
||||
["Ƞ"] = "ƞ",
|
||||
["Ȣ"] = "ȣ",
|
||||
["Ȥ"] = "ȥ",
|
||||
["Ȧ"] = "ȧ",
|
||||
["Ȩ"] = "ȩ",
|
||||
["Ȫ"] = "ȫ",
|
||||
["Ȭ"] = "ȭ",
|
||||
["Ȯ"] = "ȯ",
|
||||
["Ȱ"] = "ȱ",
|
||||
["Ȳ"] = "ȳ",
|
||||
["Ⱥ"] = "ⱥ",
|
||||
["Ȼ"] = "ȼ",
|
||||
["Ƚ"] = "ƚ",
|
||||
["Ⱦ"] = "ⱦ",
|
||||
["Ɂ"] = "ɂ",
|
||||
["Ƀ"] = "ƀ",
|
||||
["Ʉ"] = "ʉ",
|
||||
["Ʌ"] = "ʌ",
|
||||
["Ɇ"] = "ɇ",
|
||||
["Ɉ"] = "ɉ",
|
||||
["Ɋ"] = "ɋ",
|
||||
["Ɍ"] = "ɍ",
|
||||
["Ɏ"] = "ɏ",
|
||||
["Ͱ"] = "ͱ",
|
||||
["Ͳ"] = "ͳ",
|
||||
["Ͷ"] = "ͷ",
|
||||
["Ά"] = "ά",
|
||||
["Έ"] = "έ",
|
||||
["Ή"] = "ή",
|
||||
["Ί"] = "ί",
|
||||
["Ό"] = "ό",
|
||||
["Ύ"] = "ύ",
|
||||
["Ώ"] = "ώ",
|
||||
["Α"] = "α",
|
||||
["Β"] = "β",
|
||||
["Γ"] = "γ",
|
||||
["Δ"] = "δ",
|
||||
["Ε"] = "ε",
|
||||
["Ζ"] = "ζ",
|
||||
["Η"] = "η",
|
||||
["Θ"] = "θ",
|
||||
["Ι"] = "ι",
|
||||
["Κ"] = "κ",
|
||||
["Λ"] = "λ",
|
||||
["Μ"] = "μ",
|
||||
["Ν"] = "ν",
|
||||
["Ξ"] = "ξ",
|
||||
["Ο"] = "ο",
|
||||
["Π"] = "π",
|
||||
["Ρ"] = "ρ",
|
||||
["Σ"] = "σ",
|
||||
["Τ"] = "τ",
|
||||
["Υ"] = "υ",
|
||||
["Φ"] = "φ",
|
||||
["Χ"] = "χ",
|
||||
["Ψ"] = "ψ",
|
||||
["Ω"] = "ω",
|
||||
["Ϊ"] = "ϊ",
|
||||
["Ϋ"] = "ϋ",
|
||||
["Ϗ"] = "ϗ",
|
||||
["Ϙ"] = "ϙ",
|
||||
["Ϛ"] = "ϛ",
|
||||
["Ϝ"] = "ϝ",
|
||||
["Ϟ"] = "ϟ",
|
||||
["Ϡ"] = "ϡ",
|
||||
["Ϣ"] = "ϣ",
|
||||
["Ϥ"] = "ϥ",
|
||||
["Ϧ"] = "ϧ",
|
||||
["Ϩ"] = "ϩ",
|
||||
["Ϫ"] = "ϫ",
|
||||
["Ϭ"] = "ϭ",
|
||||
["Ϯ"] = "ϯ",
|
||||
["ϴ"] = "θ",
|
||||
["Ϸ"] = "ϸ",
|
||||
["Ϲ"] = "ϲ",
|
||||
["Ϻ"] = "ϻ",
|
||||
["Ͻ"] = "ͻ",
|
||||
["Ͼ"] = "ͼ",
|
||||
["Ͽ"] = "ͽ",
|
||||
["Ѐ"] = "ѐ",
|
||||
["Ё"] = "ё",
|
||||
["Ђ"] = "ђ",
|
||||
["Ѓ"] = "ѓ",
|
||||
["Є"] = "є",
|
||||
["Ѕ"] = "ѕ",
|
||||
["І"] = "і",
|
||||
["Ї"] = "ї",
|
||||
["Ј"] = "ј",
|
||||
["Љ"] = "љ",
|
||||
["Њ"] = "њ",
|
||||
["Ћ"] = "ћ",
|
||||
["Ќ"] = "ќ",
|
||||
["Ѝ"] = "ѝ",
|
||||
["Ў"] = "ў",
|
||||
["Џ"] = "џ",
|
||||
["А"] = "а",
|
||||
["Б"] = "б",
|
||||
["В"] = "в",
|
||||
["Г"] = "г",
|
||||
["Д"] = "д",
|
||||
["Е"] = "е",
|
||||
["Ж"] = "ж",
|
||||
["З"] = "з",
|
||||
["И"] = "и",
|
||||
["Й"] = "й",
|
||||
["К"] = "к",
|
||||
["Л"] = "л",
|
||||
["М"] = "м",
|
||||
["Н"] = "н",
|
||||
["О"] = "о",
|
||||
["П"] = "п",
|
||||
["Р"] = "р",
|
||||
["С"] = "с",
|
||||
["Т"] = "т",
|
||||
["У"] = "у",
|
||||
["Ф"] = "ф",
|
||||
["Х"] = "х",
|
||||
["Ц"] = "ц",
|
||||
["Ч"] = "ч",
|
||||
["Ш"] = "ш",
|
||||
["Щ"] = "щ",
|
||||
["Ъ"] = "ъ",
|
||||
["Ы"] = "ы",
|
||||
["Ь"] = "ь",
|
||||
["Э"] = "э",
|
||||
["Ю"] = "ю",
|
||||
["Я"] = "я",
|
||||
["Ѡ"] = "ѡ",
|
||||
["Ѣ"] = "ѣ",
|
||||
["Ѥ"] = "ѥ",
|
||||
["Ѧ"] = "ѧ",
|
||||
["Ѩ"] = "ѩ",
|
||||
["Ѫ"] = "ѫ",
|
||||
["Ѭ"] = "ѭ",
|
||||
["Ѯ"] = "ѯ",
|
||||
["Ѱ"] = "ѱ",
|
||||
["Ѳ"] = "ѳ",
|
||||
["Ѵ"] = "ѵ",
|
||||
["Ѷ"] = "ѷ",
|
||||
["Ѹ"] = "ѹ",
|
||||
["Ѻ"] = "ѻ",
|
||||
["Ѽ"] = "ѽ",
|
||||
["Ѿ"] = "ѿ",
|
||||
["Ҁ"] = "ҁ",
|
||||
["Ҋ"] = "ҋ",
|
||||
["Ҍ"] = "ҍ",
|
||||
["Ҏ"] = "ҏ",
|
||||
["Ґ"] = "ґ",
|
||||
["Ғ"] = "ғ",
|
||||
["Ҕ"] = "ҕ",
|
||||
["Җ"] = "җ",
|
||||
["Ҙ"] = "ҙ",
|
||||
["Қ"] = "қ",
|
||||
["Ҝ"] = "ҝ",
|
||||
["Ҟ"] = "ҟ",
|
||||
["Ҡ"] = "ҡ",
|
||||
["Ң"] = "ң",
|
||||
["Ҥ"] = "ҥ",
|
||||
["Ҧ"] = "ҧ",
|
||||
["Ҩ"] = "ҩ",
|
||||
["Ҫ"] = "ҫ",
|
||||
["Ҭ"] = "ҭ",
|
||||
["Ү"] = "ү",
|
||||
["Ұ"] = "ұ",
|
||||
["Ҳ"] = "ҳ",
|
||||
["Ҵ"] = "ҵ",
|
||||
["Ҷ"] = "ҷ",
|
||||
["Ҹ"] = "ҹ",
|
||||
["Һ"] = "һ",
|
||||
["Ҽ"] = "ҽ",
|
||||
["Ҿ"] = "ҿ",
|
||||
["Ӏ"] = "ӏ",
|
||||
["Ӂ"] = "ӂ",
|
||||
["Ӄ"] = "ӄ",
|
||||
["Ӆ"] = "ӆ",
|
||||
["Ӈ"] = "ӈ",
|
||||
["Ӊ"] = "ӊ",
|
||||
["Ӌ"] = "ӌ",
|
||||
["Ӎ"] = "ӎ",
|
||||
["Ӑ"] = "ӑ",
|
||||
["Ӓ"] = "ӓ",
|
||||
["Ӕ"] = "ӕ",
|
||||
["Ӗ"] = "ӗ",
|
||||
["Ә"] = "ә",
|
||||
["Ӛ"] = "ӛ",
|
||||
["Ӝ"] = "ӝ",
|
||||
["Ӟ"] = "ӟ",
|
||||
["Ӡ"] = "ӡ",
|
||||
["Ӣ"] = "ӣ",
|
||||
["Ӥ"] = "ӥ",
|
||||
["Ӧ"] = "ӧ",
|
||||
["Ө"] = "ө",
|
||||
["Ӫ"] = "ӫ",
|
||||
["Ӭ"] = "ӭ",
|
||||
["Ӯ"] = "ӯ",
|
||||
["Ӱ"] = "ӱ",
|
||||
["Ӳ"] = "ӳ",
|
||||
["Ӵ"] = "ӵ",
|
||||
["Ӷ"] = "ӷ",
|
||||
["Ӹ"] = "ӹ",
|
||||
["Ӻ"] = "ӻ",
|
||||
["Ӽ"] = "ӽ",
|
||||
["Ӿ"] = "ӿ",
|
||||
["Ԁ"] = "ԁ",
|
||||
["Ԃ"] = "ԃ",
|
||||
["Ԅ"] = "ԅ",
|
||||
["Ԇ"] = "ԇ",
|
||||
["Ԉ"] = "ԉ",
|
||||
["Ԋ"] = "ԋ",
|
||||
["Ԍ"] = "ԍ",
|
||||
["Ԏ"] = "ԏ",
|
||||
["Ԑ"] = "ԑ",
|
||||
["Ԓ"] = "ԓ",
|
||||
["Ԕ"] = "ԕ",
|
||||
["Ԗ"] = "ԗ",
|
||||
["Ԙ"] = "ԙ",
|
||||
["Ԛ"] = "ԛ",
|
||||
["Ԝ"] = "ԝ",
|
||||
["Ԟ"] = "ԟ",
|
||||
["Ԡ"] = "ԡ",
|
||||
["Ԣ"] = "ԣ",
|
||||
["Ԥ"] = "ԥ",
|
||||
["Ԧ"] = "ԧ",
|
||||
["Ա"] = "ա",
|
||||
["Բ"] = "բ",
|
||||
["Գ"] = "գ",
|
||||
["Դ"] = "դ",
|
||||
["Ե"] = "ե",
|
||||
["Զ"] = "զ",
|
||||
["Է"] = "է",
|
||||
["Ը"] = "ը",
|
||||
["Թ"] = "թ",
|
||||
["Ժ"] = "ժ",
|
||||
["Ի"] = "ի",
|
||||
["Լ"] = "լ",
|
||||
["Խ"] = "խ",
|
||||
["Ծ"] = "ծ",
|
||||
["Կ"] = "կ",
|
||||
["Հ"] = "հ",
|
||||
["Ձ"] = "ձ",
|
||||
["Ղ"] = "ղ",
|
||||
["Ճ"] = "ճ",
|
||||
["Մ"] = "մ",
|
||||
["Յ"] = "յ",
|
||||
["Ն"] = "ն",
|
||||
["Շ"] = "շ",
|
||||
["Ո"] = "ո",
|
||||
["Չ"] = "չ",
|
||||
["Պ"] = "պ",
|
||||
["Ջ"] = "ջ",
|
||||
["Ռ"] = "ռ",
|
||||
["Ս"] = "ս",
|
||||
["Վ"] = "վ",
|
||||
["Տ"] = "տ",
|
||||
["Ր"] = "ր",
|
||||
["Ց"] = "ց",
|
||||
["Ւ"] = "ւ",
|
||||
["Փ"] = "փ",
|
||||
["Ք"] = "ք",
|
||||
["Օ"] = "օ",
|
||||
["Ֆ"] = "ֆ",
|
||||
["Ⴀ"] = "ⴀ",
|
||||
["Ⴁ"] = "ⴁ",
|
||||
["Ⴂ"] = "ⴂ",
|
||||
["Ⴃ"] = "ⴃ",
|
||||
["Ⴄ"] = "ⴄ",
|
||||
["Ⴅ"] = "ⴅ",
|
||||
["Ⴆ"] = "ⴆ",
|
||||
["Ⴇ"] = "ⴇ",
|
||||
["Ⴈ"] = "ⴈ",
|
||||
["Ⴉ"] = "ⴉ",
|
||||
["Ⴊ"] = "ⴊ",
|
||||
["Ⴋ"] = "ⴋ",
|
||||
["Ⴌ"] = "ⴌ",
|
||||
["Ⴍ"] = "ⴍ",
|
||||
["Ⴎ"] = "ⴎ",
|
||||
["Ⴏ"] = "ⴏ",
|
||||
["Ⴐ"] = "ⴐ",
|
||||
["Ⴑ"] = "ⴑ",
|
||||
["Ⴒ"] = "ⴒ",
|
||||
["Ⴓ"] = "ⴓ",
|
||||
["Ⴔ"] = "ⴔ",
|
||||
["Ⴕ"] = "ⴕ",
|
||||
["Ⴖ"] = "ⴖ",
|
||||
["Ⴗ"] = "ⴗ",
|
||||
["Ⴘ"] = "ⴘ",
|
||||
["Ⴙ"] = "ⴙ",
|
||||
["Ⴚ"] = "ⴚ",
|
||||
["Ⴛ"] = "ⴛ",
|
||||
["Ⴜ"] = "ⴜ",
|
||||
["Ⴝ"] = "ⴝ",
|
||||
["Ⴞ"] = "ⴞ",
|
||||
["Ⴟ"] = "ⴟ",
|
||||
["Ⴠ"] = "ⴠ",
|
||||
["Ⴡ"] = "ⴡ",
|
||||
["Ⴢ"] = "ⴢ",
|
||||
["Ⴣ"] = "ⴣ",
|
||||
["Ⴤ"] = "ⴤ",
|
||||
["Ⴥ"] = "ⴥ",
|
||||
["Ḁ"] = "ḁ",
|
||||
["Ḃ"] = "ḃ",
|
||||
["Ḅ"] = "ḅ",
|
||||
["Ḇ"] = "ḇ",
|
||||
["Ḉ"] = "ḉ",
|
||||
["Ḋ"] = "ḋ",
|
||||
["Ḍ"] = "ḍ",
|
||||
["Ḏ"] = "ḏ",
|
||||
["Ḑ"] = "ḑ",
|
||||
["Ḓ"] = "ḓ",
|
||||
["Ḕ"] = "ḕ",
|
||||
["Ḗ"] = "ḗ",
|
||||
["Ḙ"] = "ḙ",
|
||||
["Ḛ"] = "ḛ",
|
||||
["Ḝ"] = "ḝ",
|
||||
["Ḟ"] = "ḟ",
|
||||
["Ḡ"] = "ḡ",
|
||||
["Ḣ"] = "ḣ",
|
||||
["Ḥ"] = "ḥ",
|
||||
["Ḧ"] = "ḧ",
|
||||
["Ḩ"] = "ḩ",
|
||||
["Ḫ"] = "ḫ",
|
||||
["Ḭ"] = "ḭ",
|
||||
["Ḯ"] = "ḯ",
|
||||
["Ḱ"] = "ḱ",
|
||||
["Ḳ"] = "ḳ",
|
||||
["Ḵ"] = "ḵ",
|
||||
["Ḷ"] = "ḷ",
|
||||
["Ḹ"] = "ḹ",
|
||||
["Ḻ"] = "ḻ",
|
||||
["Ḽ"] = "ḽ",
|
||||
["Ḿ"] = "ḿ",
|
||||
["Ṁ"] = "ṁ",
|
||||
["Ṃ"] = "ṃ",
|
||||
["Ṅ"] = "ṅ",
|
||||
["Ṇ"] = "ṇ",
|
||||
["Ṉ"] = "ṉ",
|
||||
["Ṋ"] = "ṋ",
|
||||
["Ṍ"] = "ṍ",
|
||||
["Ṏ"] = "ṏ",
|
||||
["Ṑ"] = "ṑ",
|
||||
["Ṓ"] = "ṓ",
|
||||
["Ṕ"] = "ṕ",
|
||||
["Ṗ"] = "ṗ",
|
||||
["Ṙ"] = "ṙ",
|
||||
["Ṛ"] = "ṛ",
|
||||
["Ṝ"] = "ṝ",
|
||||
["Ṟ"] = "ṟ",
|
||||
["Ṡ"] = "ṡ",
|
||||
["Ṣ"] = "ṣ",
|
||||
["Ṥ"] = "ṥ",
|
||||
["Ṧ"] = "ṧ",
|
||||
["Ṩ"] = "ṩ",
|
||||
["Ṫ"] = "ṫ",
|
||||
["Ṭ"] = "ṭ",
|
||||
["Ṯ"] = "ṯ",
|
||||
["Ṱ"] = "ṱ",
|
||||
["Ṳ"] = "ṳ",
|
||||
["Ṵ"] = "ṵ",
|
||||
["Ṷ"] = "ṷ",
|
||||
["Ṹ"] = "ṹ",
|
||||
["Ṻ"] = "ṻ",
|
||||
["Ṽ"] = "ṽ",
|
||||
["Ṿ"] = "ṿ",
|
||||
["Ẁ"] = "ẁ",
|
||||
["Ẃ"] = "ẃ",
|
||||
["Ẅ"] = "ẅ",
|
||||
["Ẇ"] = "ẇ",
|
||||
["Ẉ"] = "ẉ",
|
||||
["Ẋ"] = "ẋ",
|
||||
["Ẍ"] = "ẍ",
|
||||
["Ẏ"] = "ẏ",
|
||||
["Ẑ"] = "ẑ",
|
||||
["Ẓ"] = "ẓ",
|
||||
["Ẕ"] = "ẕ",
|
||||
["ẞ"] = "ß",
|
||||
["Ạ"] = "ạ",
|
||||
["Ả"] = "ả",
|
||||
["Ấ"] = "ấ",
|
||||
["Ầ"] = "ầ",
|
||||
["Ẩ"] = "ẩ",
|
||||
["Ẫ"] = "ẫ",
|
||||
["Ậ"] = "ậ",
|
||||
["Ắ"] = "ắ",
|
||||
["Ằ"] = "ằ",
|
||||
["Ẳ"] = "ẳ",
|
||||
["Ẵ"] = "ẵ",
|
||||
["Ặ"] = "ặ",
|
||||
["Ẹ"] = "ẹ",
|
||||
["Ẻ"] = "ẻ",
|
||||
["Ẽ"] = "ẽ",
|
||||
["Ế"] = "ế",
|
||||
["Ề"] = "ề",
|
||||
["Ể"] = "ể",
|
||||
["Ễ"] = "ễ",
|
||||
["Ệ"] = "ệ",
|
||||
["Ỉ"] = "ỉ",
|
||||
["Ị"] = "ị",
|
||||
["Ọ"] = "ọ",
|
||||
["Ỏ"] = "ỏ",
|
||||
["Ố"] = "ố",
|
||||
["Ồ"] = "ồ",
|
||||
["Ổ"] = "ổ",
|
||||
["Ỗ"] = "ỗ",
|
||||
["Ộ"] = "ộ",
|
||||
["Ớ"] = "ớ",
|
||||
["Ờ"] = "ờ",
|
||||
["Ở"] = "ở",
|
||||
["Ỡ"] = "ỡ",
|
||||
["Ợ"] = "ợ",
|
||||
["Ụ"] = "ụ",
|
||||
["Ủ"] = "ủ",
|
||||
["Ứ"] = "ứ",
|
||||
["Ừ"] = "ừ",
|
||||
["Ử"] = "ử",
|
||||
["Ữ"] = "ữ",
|
||||
["Ự"] = "ự",
|
||||
["Ỳ"] = "ỳ",
|
||||
["Ỵ"] = "ỵ",
|
||||
["Ỷ"] = "ỷ",
|
||||
["Ỹ"] = "ỹ",
|
||||
["Ỻ"] = "ỻ",
|
||||
["Ỽ"] = "ỽ",
|
||||
["Ỿ"] = "ỿ",
|
||||
["Ἀ"] = "ἀ",
|
||||
["Ἁ"] = "ἁ",
|
||||
["Ἂ"] = "ἂ",
|
||||
["Ἃ"] = "ἃ",
|
||||
["Ἄ"] = "ἄ",
|
||||
["Ἅ"] = "ἅ",
|
||||
["Ἆ"] = "ἆ",
|
||||
["Ἇ"] = "ἇ",
|
||||
["Ἐ"] = "ἐ",
|
||||
["Ἑ"] = "ἑ",
|
||||
["Ἒ"] = "ἒ",
|
||||
["Ἓ"] = "ἓ",
|
||||
["Ἔ"] = "ἔ",
|
||||
["Ἕ"] = "ἕ",
|
||||
["Ἠ"] = "ἠ",
|
||||
["Ἡ"] = "ἡ",
|
||||
["Ἢ"] = "ἢ",
|
||||
["Ἣ"] = "ἣ",
|
||||
["Ἤ"] = "ἤ",
|
||||
["Ἥ"] = "ἥ",
|
||||
["Ἦ"] = "ἦ",
|
||||
["Ἧ"] = "ἧ",
|
||||
["Ἰ"] = "ἰ",
|
||||
["Ἱ"] = "ἱ",
|
||||
["Ἲ"] = "ἲ",
|
||||
["Ἳ"] = "ἳ",
|
||||
["Ἴ"] = "ἴ",
|
||||
["Ἵ"] = "ἵ",
|
||||
["Ἶ"] = "ἶ",
|
||||
["Ἷ"] = "ἷ",
|
||||
["Ὀ"] = "ὀ",
|
||||
["Ὁ"] = "ὁ",
|
||||
["Ὂ"] = "ὂ",
|
||||
["Ὃ"] = "ὃ",
|
||||
["Ὄ"] = "ὄ",
|
||||
["Ὅ"] = "ὅ",
|
||||
["Ὑ"] = "ὑ",
|
||||
["Ὓ"] = "ὓ",
|
||||
["Ὕ"] = "ὕ",
|
||||
["Ὗ"] = "ὗ",
|
||||
["Ὠ"] = "ὠ",
|
||||
["Ὡ"] = "ὡ",
|
||||
["Ὢ"] = "ὢ",
|
||||
["Ὣ"] = "ὣ",
|
||||
["Ὤ"] = "ὤ",
|
||||
["Ὥ"] = "ὥ",
|
||||
["Ὦ"] = "ὦ",
|
||||
["Ὧ"] = "ὧ",
|
||||
["Ᾰ"] = "ᾰ",
|
||||
["Ᾱ"] = "ᾱ",
|
||||
["Ὰ"] = "ὰ",
|
||||
["Ά"] = "ά",
|
||||
["Ὲ"] = "ὲ",
|
||||
["Έ"] = "έ",
|
||||
["Ὴ"] = "ὴ",
|
||||
["Ή"] = "ή",
|
||||
["Ῐ"] = "ῐ",
|
||||
["Ῑ"] = "ῑ",
|
||||
["Ὶ"] = "ὶ",
|
||||
["Ί"] = "ί",
|
||||
["Ῠ"] = "ῠ",
|
||||
["Ῡ"] = "ῡ",
|
||||
["Ὺ"] = "ὺ",
|
||||
["Ύ"] = "ύ",
|
||||
["Ῥ"] = "ῥ",
|
||||
["Ὸ"] = "ὸ",
|
||||
["Ό"] = "ό",
|
||||
["Ὼ"] = "ὼ",
|
||||
["Ώ"] = "ώ",
|
||||
["Ω"] = "ω",
|
||||
["K"] = "k",
|
||||
["Å"] = "å",
|
||||
["Ⅎ"] = "ⅎ",
|
||||
["Ↄ"] = "ↄ",
|
||||
["Ⰰ"] = "ⰰ",
|
||||
["Ⰱ"] = "ⰱ",
|
||||
["Ⰲ"] = "ⰲ",
|
||||
["Ⰳ"] = "ⰳ",
|
||||
["Ⰴ"] = "ⰴ",
|
||||
["Ⰵ"] = "ⰵ",
|
||||
["Ⰶ"] = "ⰶ",
|
||||
["Ⰷ"] = "ⰷ",
|
||||
["Ⰸ"] = "ⰸ",
|
||||
["Ⰹ"] = "ⰹ",
|
||||
["Ⰺ"] = "ⰺ",
|
||||
["Ⰻ"] = "ⰻ",
|
||||
["Ⰼ"] = "ⰼ",
|
||||
["Ⰽ"] = "ⰽ",
|
||||
["Ⰾ"] = "ⰾ",
|
||||
["Ⰿ"] = "ⰿ",
|
||||
["Ⱀ"] = "ⱀ",
|
||||
["Ⱁ"] = "ⱁ",
|
||||
["Ⱂ"] = "ⱂ",
|
||||
["Ⱃ"] = "ⱃ",
|
||||
["Ⱄ"] = "ⱄ",
|
||||
["Ⱅ"] = "ⱅ",
|
||||
["Ⱆ"] = "ⱆ",
|
||||
["Ⱇ"] = "ⱇ",
|
||||
["Ⱈ"] = "ⱈ",
|
||||
["Ⱉ"] = "ⱉ",
|
||||
["Ⱊ"] = "ⱊ",
|
||||
["Ⱋ"] = "ⱋ",
|
||||
["Ⱌ"] = "ⱌ",
|
||||
["Ⱍ"] = "ⱍ",
|
||||
["Ⱎ"] = "ⱎ",
|
||||
["Ⱏ"] = "ⱏ",
|
||||
["Ⱐ"] = "ⱐ",
|
||||
["Ⱑ"] = "ⱑ",
|
||||
["Ⱒ"] = "ⱒ",
|
||||
["Ⱓ"] = "ⱓ",
|
||||
["Ⱔ"] = "ⱔ",
|
||||
["Ⱕ"] = "ⱕ",
|
||||
["Ⱖ"] = "ⱖ",
|
||||
["Ⱗ"] = "ⱗ",
|
||||
["Ⱘ"] = "ⱘ",
|
||||
["Ⱙ"] = "ⱙ",
|
||||
["Ⱚ"] = "ⱚ",
|
||||
["Ⱛ"] = "ⱛ",
|
||||
["Ⱜ"] = "ⱜ",
|
||||
["Ⱝ"] = "ⱝ",
|
||||
["Ⱞ"] = "ⱞ",
|
||||
["Ⱡ"] = "ⱡ",
|
||||
["Ɫ"] = "ɫ",
|
||||
["Ᵽ"] = "ᵽ",
|
||||
["Ɽ"] = "ɽ",
|
||||
["Ⱨ"] = "ⱨ",
|
||||
["Ⱪ"] = "ⱪ",
|
||||
["Ⱬ"] = "ⱬ",
|
||||
["Ɑ"] = "ɑ",
|
||||
["Ɱ"] = "ɱ",
|
||||
["Ɐ"] = "ɐ",
|
||||
["Ɒ"] = "ɒ",
|
||||
["Ⱳ"] = "ⱳ",
|
||||
["Ⱶ"] = "ⱶ",
|
||||
["Ȿ"] = "ȿ",
|
||||
["Ɀ"] = "ɀ",
|
||||
["Ⲁ"] = "ⲁ",
|
||||
["Ⲃ"] = "ⲃ",
|
||||
["Ⲅ"] = "ⲅ",
|
||||
["Ⲇ"] = "ⲇ",
|
||||
["Ⲉ"] = "ⲉ",
|
||||
["Ⲋ"] = "ⲋ",
|
||||
["Ⲍ"] = "ⲍ",
|
||||
["Ⲏ"] = "ⲏ",
|
||||
["Ⲑ"] = "ⲑ",
|
||||
["Ⲓ"] = "ⲓ",
|
||||
["Ⲕ"] = "ⲕ",
|
||||
["Ⲗ"] = "ⲗ",
|
||||
["Ⲙ"] = "ⲙ",
|
||||
["Ⲛ"] = "ⲛ",
|
||||
["Ⲝ"] = "ⲝ",
|
||||
["Ⲟ"] = "ⲟ",
|
||||
["Ⲡ"] = "ⲡ",
|
||||
["Ⲣ"] = "ⲣ",
|
||||
["Ⲥ"] = "ⲥ",
|
||||
["Ⲧ"] = "ⲧ",
|
||||
["Ⲩ"] = "ⲩ",
|
||||
["Ⲫ"] = "ⲫ",
|
||||
["Ⲭ"] = "ⲭ",
|
||||
["Ⲯ"] = "ⲯ",
|
||||
["Ⲱ"] = "ⲱ",
|
||||
["Ⲳ"] = "ⲳ",
|
||||
["Ⲵ"] = "ⲵ",
|
||||
["Ⲷ"] = "ⲷ",
|
||||
["Ⲹ"] = "ⲹ",
|
||||
["Ⲻ"] = "ⲻ",
|
||||
["Ⲽ"] = "ⲽ",
|
||||
["Ⲿ"] = "ⲿ",
|
||||
["Ⳁ"] = "ⳁ",
|
||||
["Ⳃ"] = "ⳃ",
|
||||
["Ⳅ"] = "ⳅ",
|
||||
["Ⳇ"] = "ⳇ",
|
||||
["Ⳉ"] = "ⳉ",
|
||||
["Ⳋ"] = "ⳋ",
|
||||
["Ⳍ"] = "ⳍ",
|
||||
["Ⳏ"] = "ⳏ",
|
||||
["Ⳑ"] = "ⳑ",
|
||||
["Ⳓ"] = "ⳓ",
|
||||
["Ⳕ"] = "ⳕ",
|
||||
["Ⳗ"] = "ⳗ",
|
||||
["Ⳙ"] = "ⳙ",
|
||||
["Ⳛ"] = "ⳛ",
|
||||
["Ⳝ"] = "ⳝ",
|
||||
["Ⳟ"] = "ⳟ",
|
||||
["Ⳡ"] = "ⳡ",
|
||||
["Ⳣ"] = "ⳣ",
|
||||
["Ⳬ"] = "ⳬ",
|
||||
["Ⳮ"] = "ⳮ",
|
||||
["Ꙁ"] = "ꙁ",
|
||||
["Ꙃ"] = "ꙃ",
|
||||
["Ꙅ"] = "ꙅ",
|
||||
["Ꙇ"] = "ꙇ",
|
||||
["Ꙉ"] = "ꙉ",
|
||||
["Ꙋ"] = "ꙋ",
|
||||
["Ꙍ"] = "ꙍ",
|
||||
["Ꙏ"] = "ꙏ",
|
||||
["Ꙑ"] = "ꙑ",
|
||||
["Ꙓ"] = "ꙓ",
|
||||
["Ꙕ"] = "ꙕ",
|
||||
["Ꙗ"] = "ꙗ",
|
||||
["Ꙙ"] = "ꙙ",
|
||||
["Ꙛ"] = "ꙛ",
|
||||
["Ꙝ"] = "ꙝ",
|
||||
["Ꙟ"] = "ꙟ",
|
||||
["Ꙡ"] = "ꙡ",
|
||||
["Ꙣ"] = "ꙣ",
|
||||
["Ꙥ"] = "ꙥ",
|
||||
["Ꙧ"] = "ꙧ",
|
||||
["Ꙩ"] = "ꙩ",
|
||||
["Ꙫ"] = "ꙫ",
|
||||
["Ꙭ"] = "ꙭ",
|
||||
["Ꚁ"] = "ꚁ",
|
||||
["Ꚃ"] = "ꚃ",
|
||||
["Ꚅ"] = "ꚅ",
|
||||
["Ꚇ"] = "ꚇ",
|
||||
["Ꚉ"] = "ꚉ",
|
||||
["Ꚋ"] = "ꚋ",
|
||||
["Ꚍ"] = "ꚍ",
|
||||
["Ꚏ"] = "ꚏ",
|
||||
["Ꚑ"] = "ꚑ",
|
||||
["Ꚓ"] = "ꚓ",
|
||||
["Ꚕ"] = "ꚕ",
|
||||
["Ꚗ"] = "ꚗ",
|
||||
["Ꜣ"] = "ꜣ",
|
||||
["Ꜥ"] = "ꜥ",
|
||||
["Ꜧ"] = "ꜧ",
|
||||
["Ꜩ"] = "ꜩ",
|
||||
["Ꜫ"] = "ꜫ",
|
||||
["Ꜭ"] = "ꜭ",
|
||||
["Ꜯ"] = "ꜯ",
|
||||
["Ꜳ"] = "ꜳ",
|
||||
["Ꜵ"] = "ꜵ",
|
||||
["Ꜷ"] = "ꜷ",
|
||||
["Ꜹ"] = "ꜹ",
|
||||
["Ꜻ"] = "ꜻ",
|
||||
["Ꜽ"] = "ꜽ",
|
||||
["Ꜿ"] = "ꜿ",
|
||||
["Ꝁ"] = "ꝁ",
|
||||
["Ꝃ"] = "ꝃ",
|
||||
["Ꝅ"] = "ꝅ",
|
||||
["Ꝇ"] = "ꝇ",
|
||||
["Ꝉ"] = "ꝉ",
|
||||
["Ꝋ"] = "ꝋ",
|
||||
["Ꝍ"] = "ꝍ",
|
||||
["Ꝏ"] = "ꝏ",
|
||||
["Ꝑ"] = "ꝑ",
|
||||
["Ꝓ"] = "ꝓ",
|
||||
["Ꝕ"] = "ꝕ",
|
||||
["Ꝗ"] = "ꝗ",
|
||||
["Ꝙ"] = "ꝙ",
|
||||
["Ꝛ"] = "ꝛ",
|
||||
["Ꝝ"] = "ꝝ",
|
||||
["Ꝟ"] = "ꝟ",
|
||||
["Ꝡ"] = "ꝡ",
|
||||
["Ꝣ"] = "ꝣ",
|
||||
["Ꝥ"] = "ꝥ",
|
||||
["Ꝧ"] = "ꝧ",
|
||||
["Ꝩ"] = "ꝩ",
|
||||
["Ꝫ"] = "ꝫ",
|
||||
["Ꝭ"] = "ꝭ",
|
||||
["Ꝯ"] = "ꝯ",
|
||||
["Ꝺ"] = "ꝺ",
|
||||
["Ꝼ"] = "ꝼ",
|
||||
["Ᵹ"] = "ᵹ",
|
||||
["Ꝿ"] = "ꝿ",
|
||||
["Ꞁ"] = "ꞁ",
|
||||
["Ꞃ"] = "ꞃ",
|
||||
["Ꞅ"] = "ꞅ",
|
||||
["Ꞇ"] = "ꞇ",
|
||||
["Ꞌ"] = "ꞌ",
|
||||
["Ɥ"] = "ɥ",
|
||||
["Ꞑ"] = "ꞑ",
|
||||
["Ꞡ"] = "ꞡ",
|
||||
["Ꞣ"] = "ꞣ",
|
||||
["Ꞥ"] = "ꞥ",
|
||||
["Ꞧ"] = "ꞧ",
|
||||
["Ꞩ"] = "ꞩ",
|
||||
["A"] = "a",
|
||||
["B"] = "b",
|
||||
["C"] = "c",
|
||||
["D"] = "d",
|
||||
["E"] = "e",
|
||||
["F"] = "f",
|
||||
["G"] = "g",
|
||||
["H"] = "h",
|
||||
["I"] = "i",
|
||||
["J"] = "j",
|
||||
["K"] = "k",
|
||||
["L"] = "l",
|
||||
["M"] = "m",
|
||||
["N"] = "n",
|
||||
["O"] = "o",
|
||||
["P"] = "p",
|
||||
["Q"] = "q",
|
||||
["R"] = "r",
|
||||
["S"] = "s",
|
||||
["T"] = "t",
|
||||
["U"] = "u",
|
||||
["V"] = "v",
|
||||
["W"] = "w",
|
||||
["X"] = "x",
|
||||
["Y"] = "y",
|
||||
["Z"] = "z",
|
||||
["𐐀"] = "𐐨",
|
||||
["𐐁"] = "𐐩",
|
||||
["𐐂"] = "𐐪",
|
||||
["𐐃"] = "𐐫",
|
||||
["𐐄"] = "𐐬",
|
||||
["𐐅"] = "𐐭",
|
||||
["𐐆"] = "𐐮",
|
||||
["𐐇"] = "𐐯",
|
||||
["𐐈"] = "𐐰",
|
||||
["𐐉"] = "𐐱",
|
||||
["𐐊"] = "𐐲",
|
||||
["𐐋"] = "𐐳",
|
||||
["𐐌"] = "𐐴",
|
||||
["𐐍"] = "𐐵",
|
||||
["𐐎"] = "𐐶",
|
||||
["𐐏"] = "𐐷",
|
||||
["𐐐"] = "𐐸",
|
||||
["𐐑"] = "𐐹",
|
||||
["𐐒"] = "𐐺",
|
||||
["𐐓"] = "𐐻",
|
||||
["𐐔"] = "𐐼",
|
||||
["𐐕"] = "𐐽",
|
||||
["𐐖"] = "𐐾",
|
||||
["𐐗"] = "𐐿",
|
||||
["𐐘"] = "𐑀",
|
||||
["𐐙"] = "𐑁",
|
||||
["𐐚"] = "𐑂",
|
||||
["𐐛"] = "𐑃",
|
||||
["𐐜"] = "𐑄",
|
||||
["𐐝"] = "𐑅",
|
||||
["𐐞"] = "𐑆",
|
||||
["𐐟"] = "𐑇",
|
||||
["𐐠"] = "𐑈",
|
||||
["𐐡"] = "𐑉",
|
||||
["𐐢"] = "𐑊",
|
||||
["𐐣"] = "𐑋",
|
||||
["𐐤"] = "𐑌",
|
||||
["𐐥"] = "𐑍",
|
||||
["𐐦"] = "𐑎",
|
||||
["𐐧"] = "𐑏",
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/php
|
||||
<?
|
||||
|
||||
if ( PHP_SAPI !== 'cli' ) {
|
||||
die( "This script may only be executed from the command line.\n" );
|
||||
}
|
||||
|
||||
$datafile = null;
|
||||
if ( count( $argv ) > 1 ) {
|
||||
$datafile = $argv[1];
|
||||
if ( !file_exists( $datafile ) ) {
|
||||
die( "The specified file '$datafile' does not exist\n" );
|
||||
}
|
||||
} else {
|
||||
foreach( array(
|
||||
__DIR__ . '/../../../../../core/includes/normal/UtfNormalData.inc',
|
||||
__DIR__ . '/../../../../../includes/normal/UtfNormalData.inc',
|
||||
) as $tryfile ) {
|
||||
$tryfile = realpath( $tryfile );
|
||||
if ( file_exists( $tryfile ) ) {
|
||||
$datafile = $tryfile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !$datafile ) {
|
||||
die( "Cannot find UtfNormalData.inc. Please specify the path explicitly.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
echo "Loading data file $datafile...\n";
|
||||
class UtfNormal {
|
||||
static $utfCheckNFC = null;
|
||||
static $utfCombiningClass = null;
|
||||
static $utfCanonicalDecomp = null;
|
||||
static $utfCanonicalComp = null;
|
||||
}
|
||||
require_once( $datafile );
|
||||
|
||||
if ( !UtfNormal::$utfCheckNFC ||
|
||||
!UtfNormal::$utfCombiningClass ||
|
||||
!UtfNormal::$utfCanonicalDecomp ||
|
||||
!UtfNormal::$utfCanonicalComp
|
||||
) {
|
||||
die( "Data file $datafile did not contain needed data.\n" );
|
||||
}
|
||||
|
||||
function uord( $c, $firstOnly ) {
|
||||
$ret = unpack( 'N*', mb_convert_encoding( $c, 'UTF-32BE', 'UTF-8' ) );
|
||||
return $firstOnly ? $ret[1] : $ret;
|
||||
}
|
||||
|
||||
echo "Creating normalization table...\n";
|
||||
$X = fopen( __DIR__ . '/normalization-data.lua', 'w' );
|
||||
if ( !$X ) {
|
||||
die( "Failed to open normalization-data.lua\n" );
|
||||
}
|
||||
fprintf( $X, "-- This file is automatically generated by make-normalization-table.php\n" );
|
||||
fprintf( $X, "local normal = {\n" );
|
||||
fprintf( $X, "\t-- Characters that might change depending on the following combiner\n" );
|
||||
fprintf( $X, "\t-- (minus any that are themselves combiners, those are added later)\n" );
|
||||
fprintf( $X, "\tcheck = {\n" );
|
||||
foreach ( UtfNormal::$utfCheckNFC as $k => $v ) {
|
||||
if ( isset( UtfNormal::$utfCombiningClass[$k] ) ) {
|
||||
// Skip, because it's in the other table already
|
||||
continue;
|
||||
}
|
||||
fprintf( $X, "\t\t[0x%06x] = 1,\n", uord( $k, true ) );
|
||||
}
|
||||
fprintf( $X, "\t},\n\n" );
|
||||
fprintf( $X, "\t-- Combining characters, mapped to combining class\n" );
|
||||
fprintf( $X, "\tcombclass = {\n" );
|
||||
$comb = array();
|
||||
foreach ( UtfNormal::$utfCombiningClass as $k => $v ) {
|
||||
$cp = uord( $k, true );
|
||||
$comb[$cp] = 1;
|
||||
fprintf( $X, "\t\t[0x%06x] = %d,\n", $cp, $v );
|
||||
}
|
||||
fprintf( $X, "\t},\n\n" );
|
||||
fprintf( $X, "\t-- Characters mapped to what they decompose to\n" );
|
||||
fprintf( $X, "\t-- Note Hangul to Jamo is done separately below\n" );
|
||||
fprintf( $X, "\tdecomp = {\n" );
|
||||
foreach ( UtfNormal::$utfCanonicalDecomp as $k => $v ) {
|
||||
fprintf( $X, "\t\t[0x%06x] = { ", uord( $k, true ) );
|
||||
$fmt = "0x%06x";
|
||||
foreach ( uord( $v, false ) as $c ) {
|
||||
fprintf( $X, $fmt, $c );
|
||||
$fmt = ", 0x%06x";
|
||||
}
|
||||
fprintf( $X, " },\n" );
|
||||
}
|
||||
fprintf( $X, "\t},\n\n" );
|
||||
|
||||
fprintf( $X, "\t-- Character-pairs mapped to what they compose to\n" );
|
||||
fprintf( $X, "\t-- Note Jamo to Hangul is done separately below\n" );
|
||||
$t = array();
|
||||
foreach ( UtfNormal::$utfCanonicalComp as $k => $v ) {
|
||||
$k = uord( $k, false );
|
||||
if ( count( $k ) == 1 ) {
|
||||
// No idea why these are in the file
|
||||
continue;
|
||||
}
|
||||
if ( isset( $comb[$k[1]] ) ) {
|
||||
// Non-starter, no idea why these are in the file either
|
||||
continue;
|
||||
}
|
||||
$t[$k[1]][$k[2]] = uord( $v, true );
|
||||
}
|
||||
fprintf( $X, "\tcomp = {\n" );
|
||||
ksort( $t );
|
||||
foreach ( $t as $k1 => $v1 ) {
|
||||
fprintf( $X, "\t\t[0x%06x] = {\n", $k1 );
|
||||
ksort( $v1 );
|
||||
foreach ( $v1 as $k2 => $v2 ) {
|
||||
if ( $k2 < 0 ) {
|
||||
fprintf( $X, "\t\t\t[-1] = 0x%06x,\n", $v2 );
|
||||
} else {
|
||||
fprintf( $X, "\t\t\t[0x%06x] = 0x%06x,\n", $k2, $v2 );
|
||||
}
|
||||
}
|
||||
fprintf( $X, "\t\t},\n" );
|
||||
}
|
||||
fprintf( $X, "\t},\n" );
|
||||
|
||||
fprintf( $X, "}\n" );
|
||||
|
||||
fprintf( $X, "\n%s\n", <<<LUA
|
||||
-- All combining characters need to be checked, so just do that
|
||||
setmetatable( normal.check, { __index = normal.combclass } )
|
||||
|
||||
-- Handle Hangul to Jamo decomposition
|
||||
setmetatable( normal.decomp, { __index = function ( _, k )
|
||||
if k >= 0xac00 and k <= 0xd7a3 then
|
||||
-- Decompose a Hangul syllable into Jamo
|
||||
k = k - 0xac00
|
||||
local ret = {
|
||||
0x1100 + math.floor( k / 588 ),
|
||||
0x1161 + math.floor( ( k % 588 ) / 28 )
|
||||
}
|
||||
if k % 28 ~= 0 then
|
||||
ret[3] = 0x11a7 + ( k % 28 )
|
||||
end
|
||||
return ret
|
||||
end
|
||||
return nil
|
||||
end } )
|
||||
|
||||
-- Handle Jamo to Hangul composition
|
||||
local jamo_l_v_mt = { __index = function ( t, k )
|
||||
if k >= 0x1161 and k <= 0x1175 then
|
||||
-- Jamo leading + Jamo vowel
|
||||
return t.base + 28 * ( k - 0x1161 )
|
||||
end
|
||||
return nil
|
||||
end }
|
||||
local hangul_jamo_mt = { __index = function ( t, k )
|
||||
if k >= 0x11a7 and k <= 0x11c2 then
|
||||
-- Hangul + jamo final
|
||||
return t.base + k - 0x11a7
|
||||
end
|
||||
return nil
|
||||
end }
|
||||
setmetatable( normal.comp, { __index = function ( t, k )
|
||||
if k >= 0x1100 and k <= 0x1112 then
|
||||
-- Jamo leading, return a second table that combines with a Jamo vowel
|
||||
local t2 = { base = 0xac00 + 588 * ( k - 0x1100 ) }
|
||||
setmetatable( t2, jamo_l_v_mt )
|
||||
t[k] = t2 -- cache it
|
||||
return t2
|
||||
elseif k >= 0xac00 and k <= 0xd7a3 and k % 28 == 16 then
|
||||
-- Hangul. "k % 28 == 16" picks out just the ones that are
|
||||
-- Jamo leading + vowel, no final. Return a second table that combines
|
||||
-- with a Jamo final.
|
||||
local t2 = { base = k }
|
||||
setmetatable( t2, hangul_jamo_mt )
|
||||
t[k] = t2 -- cache it
|
||||
return t2
|
||||
end
|
||||
return nil
|
||||
end } )
|
||||
|
||||
return normal
|
||||
LUA
|
||||
);
|
||||
|
||||
fclose( $X );
|
||||
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/php
|
||||
<?
|
||||
|
||||
if ( PHP_SAPI !== 'cli' ) {
|
||||
die( "This script may only be executed from the command line.\n" );
|
||||
}
|
||||
|
||||
$chars=array();
|
||||
for ( $i = 0; $i <= 0x10ffff; $i++ ) {
|
||||
if ( $i < 0xd800 || $i > 0xdfff ) { // Skip UTF-16 surrogates
|
||||
$chars[$i] = mb_convert_encoding( pack( 'N', $i ), 'UTF-8', 'UTF-32BE' );
|
||||
}
|
||||
}
|
||||
|
||||
### Uppercase and Lowercase mappings
|
||||
echo "Creating upper and lower tables...\n";
|
||||
$L = fopen( __DIR__ . '/lower.lua', 'w' );
|
||||
if ( !$L ) {
|
||||
die( "Failed to open lower.lua\n" );
|
||||
}
|
||||
$U = fopen( __DIR__ . '/upper.lua', 'w' );
|
||||
if ( !$U ) {
|
||||
die( "Failed to open upper.lua\n" );
|
||||
}
|
||||
fprintf( $L, "-- This file is automatically generated by make-tables.php\n" );
|
||||
fprintf( $L, "return {\n" );
|
||||
fprintf( $U, "-- This file is automatically generated by make-tables.php\n" );
|
||||
fprintf( $U, "return {\n" );
|
||||
foreach ( $chars as $i => $c ) {
|
||||
$l = mb_strtolower( $c, 'UTF-8' );
|
||||
$u = mb_strtoupper( $c, 'UTF-8' );
|
||||
if ( $c !== $l ) {
|
||||
fprintf( $L, "\t[\"%s\"] = \"%s\",\n", $c, $l );
|
||||
}
|
||||
if ( $c !== $u ) {
|
||||
fprintf( $U, "\t[\"%s\"] = \"%s\",\n", $c, $u );
|
||||
}
|
||||
}
|
||||
fprintf( $L, "}\n" );
|
||||
fprintf( $U, "}\n" );
|
||||
fclose( $L );
|
||||
fclose( $U );
|
||||
|
||||
### Pattern code mappings
|
||||
echo "Creating charsets table...\n";
|
||||
$X = fopen( __DIR__ . '/charsets.lua', 'w' );
|
||||
if ( !$X ) {
|
||||
die( "Failed to open charsets.lua\n" );
|
||||
}
|
||||
$pats = array(
|
||||
// These should match the expressions in UstringLibrary::patternToRegex()
|
||||
'a' => array( '\p{L}', 'lu' ),
|
||||
'c' => array( '\p{Cc}', null ),
|
||||
'd' => array( '\p{Nd}', null ),
|
||||
'l' => array( '\p{Ll}', null ),
|
||||
'p' => array( '\p{P}', null ),
|
||||
's' => array( '\p{Xps}', null ),
|
||||
'u' => array( '\p{Lu}', null ),
|
||||
'w' => array( null, 'da' ), # '[\p{L}\p{Nd}]' exactly matches 'a' + 'd'
|
||||
'x' => array( '[0-9A-Fa-f0-9A-Fa-f]', null ),
|
||||
'z' => array( '\0', null ),
|
||||
);
|
||||
|
||||
$ranges = array();
|
||||
function addRange( $k, $start, $end ) {
|
||||
global $X, $ranges;
|
||||
// Speed/memory tradeoff
|
||||
if ( !( $start >= 0x20 && $start < 0x7f ) && $end - $start >= 10 ) {
|
||||
$ranges[$k][] = sprintf( "c >= 0x%06x and c < 0x%06x", $start, $end );
|
||||
} else {
|
||||
for ( $i = $start; $i < $end; $i++ ) {
|
||||
fprintf( $X, "\t\t[0x%06x] = 1,\n", $i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf( $X, "-- This file is automatically generated by make-tables.php\n" );
|
||||
fprintf( $X, "local pats = {\n" );
|
||||
foreach ( $pats as $k => $pp ) {
|
||||
$ranges[$k] = array();
|
||||
$re=$pp[0];
|
||||
if ( !$re ) {
|
||||
fprintf( $X, "\t[0x%02x] = {},\n", ord( $k ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
$re2='fail';
|
||||
if ( $pp[1] ) {
|
||||
$re2 = array();
|
||||
foreach ( str_split( $pp[1] ) as $p ) {
|
||||
$re2[] = $pats[$p][0];
|
||||
}
|
||||
$re2=join( '|', $re2 );
|
||||
}
|
||||
|
||||
fprintf( $X, "\t[0x%02x] = {\n", ord( $k ) );
|
||||
$rstart = null;
|
||||
foreach ( $chars as $i => $c ) {
|
||||
if ( preg_match( "/^$re$/u", $c ) && !preg_match( "/^$re2$/u", $c ) ) {
|
||||
if ( $rstart === null ) {
|
||||
$rstart = $i;
|
||||
}
|
||||
} else {
|
||||
if ( $rstart !== null ) {
|
||||
addRange( $k, $rstart, $i );
|
||||
$rstart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $rstart !== null ) {
|
||||
addRange( $k, $rstart, 0x110000 );
|
||||
}
|
||||
fprintf( $X, "\t},\n" );
|
||||
}
|
||||
foreach ( $pats as $k => $pp ) {
|
||||
$kk = strtoupper( $k );
|
||||
fprintf( $X, "\t[0x%02x] = {},\n", ord( $kk ) );
|
||||
}
|
||||
fprintf( $X, "}\n" );
|
||||
foreach ( $pats as $k => $pp ) {
|
||||
$body = '';
|
||||
$check = array();
|
||||
if ( $pp[1] ) {
|
||||
foreach ( str_split( $pp[1] ) as $p ) {
|
||||
$check[] = sprintf( "pats[0x%02x][k]", ord( $p ) );
|
||||
}
|
||||
}
|
||||
if ( $ranges[$k] ) {
|
||||
$body = "\tlocal c = tonumber( k ) or 0/0;\n";
|
||||
$check = array_merge( $check, $ranges[$k] );
|
||||
}
|
||||
if ( $check ) {
|
||||
$body .= "\treturn " . join( " or\n\t\t", $check );
|
||||
fprintf( $X, "setmetatable( pats[0x%02x], { __index = function ( t, k )\n%s\nend } )\n",
|
||||
ord( $k ), $body );
|
||||
}
|
||||
}
|
||||
foreach ( $pats as $k => $pp ) {
|
||||
fprintf( $X, "setmetatable( pats[0x%02x], { __index = function ( t, k ) return k and not pats[0x%02x][k] end } )\n",
|
||||
ord( strtoupper( $k ) ), ord( $k ) );
|
||||
}
|
||||
fprintf( $X, "\n-- For speed, cache printable ASCII characters in main tables\n" );
|
||||
fprintf( $X, "for k, t in pairs( pats ) do\n" );
|
||||
fprintf( $X, "\tif k >= 0x61 then\n" );
|
||||
fprintf( $X, "\t\tfor i = 0x20, 0x7e do\n" );
|
||||
fprintf( $X, "\t\t\tt[i] = t[i] or false\n" );
|
||||
fprintf( $X, "\t\tend\n" );
|
||||
fprintf( $X, "\tend\n" );
|
||||
fprintf( $X, "end\n" );
|
||||
fprintf( $X, "\nreturn pats\n" );
|
||||
fclose( $X );
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
string.isutf8 = ustring.isutf8
|
||||
string.byteoffset = ustring.byteoffset
|
||||
string.codepoint = ustring.codepoint
|
||||
string.gcodepoint = ustring.gcodepoint
|
||||
string.toNFC = ustring.toNFC
|
||||
string.toNFD = ustring.toNFD
|
||||
string.uchar = ustring.char
|
||||
string.ulen = ustring.len
|
||||
string.usub = ustring.sub
|
||||
string.uupper = ustring.upper
|
||||
string.ulower = ustring.lower
|
||||
string.ufind = ustring.find
|
||||
string.umatch = ustring.match
|
||||
string.ugmatch = ustring.gmatch
|
||||
string.ugsub = ustring.gsub
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,643 @@
|
||||
MWServer = {}
|
||||
|
||||
--- Create a new MWServer object
|
||||
function MWServer:new()
|
||||
obj = {
|
||||
nextChunkId = 1,
|
||||
chunks = {},
|
||||
xchunks = {},
|
||||
protectedFunctions = {},
|
||||
protectedEnvironments = {},
|
||||
baseEnv = {}
|
||||
}
|
||||
setmetatable( obj, self )
|
||||
self.__index = self
|
||||
|
||||
obj:init()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
--- Initialise a new MWServer object
|
||||
function MWServer:init()
|
||||
self.baseEnv = self:newEnvironment()
|
||||
for funcName, func in pairs( self ) do
|
||||
if type(func) == 'function' then
|
||||
self.protectedFunctions[func] = true
|
||||
end
|
||||
end
|
||||
self.protectedEnvironments[_G] = true
|
||||
end
|
||||
|
||||
--- Serve requests until exit is requested
|
||||
function MWServer:execute()
|
||||
self:dispatch( nil )
|
||||
self:debug( 'MWServer:execute: returning' )
|
||||
end
|
||||
|
||||
-- Convert a multiple-return-value or a ... into a count and a table
|
||||
function MWServer:listToCountAndTable( ... )
|
||||
return select( '#', ... ), { ... }
|
||||
end
|
||||
|
||||
--- Call a PHP function
|
||||
-- Raise an error if the PHP handler requests it. May return any number
|
||||
-- of values.
|
||||
--
|
||||
-- @param id The function ID, specified by a registerLibrary message
|
||||
-- @param nargs Count of function arguments
|
||||
-- @param args The function arguments
|
||||
-- @return The return values from the PHP function
|
||||
function MWServer:call( id, nargs, args )
|
||||
local result = self:dispatch( {
|
||||
op = 'call',
|
||||
id = id,
|
||||
nargs = nargs,
|
||||
args = args
|
||||
} )
|
||||
if result.op == 'return' then
|
||||
return unpack( result.values, 1, result.nvalues )
|
||||
elseif result.op == 'error' then
|
||||
-- Raise an error in the actual user code that called the function
|
||||
-- The level is 3 since our immediate caller is a closure
|
||||
error( result.value, 3 )
|
||||
else
|
||||
self:internalError( 'MWServer:call: unexpected result op' )
|
||||
end
|
||||
end
|
||||
|
||||
--- Handle a "call" message from PHP. Call the relevant function.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return A response message to send back to PHP
|
||||
function MWServer:handleCall( message )
|
||||
if not self.chunks[message.id] then
|
||||
return {
|
||||
op = 'error',
|
||||
value = 'function id ' .. message.id .. ' does not exist'
|
||||
}
|
||||
end
|
||||
|
||||
local n, result = self:listToCountAndTable( xpcall(
|
||||
function ()
|
||||
return self.chunks[message.id]( unpack( message.args, 1, message.nargs ) )
|
||||
end,
|
||||
function ( err )
|
||||
return MWServer:attachTrace( err )
|
||||
end
|
||||
) )
|
||||
|
||||
if result[1] then
|
||||
-- table.remove( result, 1 ) renumbers from 2 to #result. But #result
|
||||
-- is not necessarily "right" if result contains nils.
|
||||
result = { unpack( result, 2, n ) }
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = n - 1,
|
||||
values = result
|
||||
}
|
||||
else
|
||||
if result[2].value and result[2].trace then
|
||||
return {
|
||||
op = 'error',
|
||||
value = result[2].value,
|
||||
trace = result[2].trace,
|
||||
}
|
||||
else
|
||||
return {
|
||||
op = 'error',
|
||||
value = result[2]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- The xpcall() error handler for handleCall(). Modifies the error object
|
||||
-- to include a structured backtrace
|
||||
--
|
||||
-- @param err The error object
|
||||
-- @return The new error object
|
||||
function MWServer:attachTrace( err )
|
||||
return {
|
||||
value = err,
|
||||
trace = self:getStructuredTrace( 2 )
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "loadString" message from PHP.
|
||||
-- Load the function and return a chunk ID.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return A response message to send back to PHP
|
||||
function MWServer:handleLoadString( message )
|
||||
if string.find( message.text, '\27Lua', 1, true ) then
|
||||
return {
|
||||
op = 'error',
|
||||
value = 'cannot load code with a Lua binary chunk marker escape sequence in it'
|
||||
}
|
||||
end
|
||||
local chunk, errorMsg = loadstring( message.text, message.chunkName )
|
||||
if chunk then
|
||||
setfenv( chunk, self.baseEnv )
|
||||
local id = self:addChunk( chunk )
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 1,
|
||||
values = {id}
|
||||
}
|
||||
else
|
||||
return {
|
||||
op = 'error',
|
||||
value = errorMsg
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Add a function value to the list of tracked chunks and return its associated ID.
|
||||
-- Adding a chunk allows it to be referred to in messages from PHP.
|
||||
--
|
||||
-- @param chunk The function value
|
||||
-- @return The chunk ID
|
||||
function MWServer:addChunk( chunk )
|
||||
local id = self.nextChunkId
|
||||
self.nextChunkId = id + 1
|
||||
self.chunks[id] = chunk
|
||||
self.xchunks[chunk] = id
|
||||
return id
|
||||
end
|
||||
|
||||
--- Handle a "cleanupChunks" message from PHP.
|
||||
-- Remove any chunks no longer referenced by PHP code.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return A response message to send back to PHP
|
||||
function MWServer:handleCleanupChunks( message )
|
||||
for id, chunk in pairs( message.ids ) do -- XOWA: different from MW, in that message has chunks to cleanup, not chunks to preserv
|
||||
local chunk_key = self.chunks[id];
|
||||
-- dbg(id .. ':' .. tostring(chunk_key));
|
||||
self.chunks[id] = nil
|
||||
self.xchunks[chunk_key] = nil
|
||||
end
|
||||
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 0,
|
||||
values = {}
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "registerLibrary" message from PHP.
|
||||
-- Add the relevant functions to the base environment.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return The response message
|
||||
function MWServer:handleRegisterLibrary( message )
|
||||
local startPos = 1
|
||||
local component
|
||||
if not self.baseEnv[message.name] then
|
||||
self.baseEnv[message.name] = {}
|
||||
end
|
||||
local t = self.baseEnv[message.name]
|
||||
|
||||
for name, id in pairs( message.functions ) do
|
||||
t[name] = function( ... )
|
||||
return self:call( id, self:listToCountAndTable( ... ) )
|
||||
end
|
||||
-- Protect the function against setfenv()
|
||||
self.protectedFunctions[t[name]] = true
|
||||
end
|
||||
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 0,
|
||||
values = {}
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "wrapPhpFunction" message from PHP.
|
||||
-- Create an anonymous function
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return The response message
|
||||
function MWServer:handleWrapPhpFunction( message )
|
||||
local id = message.id
|
||||
local func = function( ... )
|
||||
return self:call( id, self:listToCountAndTable( ... ) )
|
||||
end
|
||||
-- Protect the function against setfenv()
|
||||
self.protectedFunctions[func] = true
|
||||
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 1,
|
||||
values = { func }
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "getStatus" message from PHP
|
||||
--
|
||||
-- @param message The request message
|
||||
-- @return The response message
|
||||
function MWServer:handleGetStatus( message )
|
||||
local nullRet = {
|
||||
op = 'return',
|
||||
nvalues = 0,
|
||||
values = {}
|
||||
}
|
||||
local file = io.open( '/proc/self/stat' )
|
||||
if not file then
|
||||
return nullRet
|
||||
end
|
||||
local s = file:read('*a')
|
||||
file:close()
|
||||
local t = {}
|
||||
for token in string.gmatch(s, '[^ ]+') do
|
||||
t[#t + 1] = token
|
||||
end
|
||||
if #t < 22 then
|
||||
return nullRet
|
||||
end
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 1,
|
||||
values = {{
|
||||
pid = tonumber(t[1]),
|
||||
time = tonumber(t[14]) + tonumber(t[15]) + tonumber(t[16]) + tonumber(t[17]),
|
||||
vsize = tonumber(t[23]),
|
||||
}}
|
||||
}
|
||||
end
|
||||
|
||||
--- The main request/response loop
|
||||
--
|
||||
-- Send a request message and return its matching reply message. Handle any
|
||||
-- intervening requests (i.e. re-entrant calls) by dispatching them to the
|
||||
-- relevant handler function.
|
||||
--
|
||||
-- The request message may optionally be omitted, to listen for request messages
|
||||
-- without first sending a request of its own. Such a dispatch() call will
|
||||
-- continue running until termination is requested by PHP. Typically, PHP does
|
||||
-- this with a SIGTERM signal.
|
||||
--
|
||||
-- @param msgToPhp The message to send to PHP. Optional.
|
||||
-- @return The matching response message
|
||||
function MWServer:dispatch( msgToPhp )
|
||||
if msgToPhp then
|
||||
self:sendMessage( msgToPhp )
|
||||
end
|
||||
while true do
|
||||
local msgFromPhp = self:receiveMessage()
|
||||
local msgToPhp
|
||||
local op = msgFromPhp.op
|
||||
if op == 'return' or op == 'error' then
|
||||
return msgFromPhp
|
||||
elseif op == 'call' then
|
||||
msgToPhp = self:handleCall( msgFromPhp )
|
||||
self:sendMessage( msgToPhp )
|
||||
elseif op == 'loadString' then
|
||||
msgToPhp = self:handleLoadString( msgFromPhp )
|
||||
self:sendMessage( msgToPhp )
|
||||
elseif op == 'registerLibrary' then
|
||||
msgToPhp = self:handleRegisterLibrary( msgFromPhp )
|
||||
self:sendMessage( msgToPhp )
|
||||
elseif op == 'wrapPhpFunction' then
|
||||
msgToPhp = self:handleWrapPhpFunction( msgFromPhp )
|
||||
self:sendMessage( msgToPhp )
|
||||
elseif op == 'cleanupChunks' then
|
||||
msgToPhp = self:handleCleanupChunks( msgFromPhp )
|
||||
self:sendMessage( msgToPhp )
|
||||
elseif op == 'getStatus' then
|
||||
msgToPhp = self:handleGetStatus( msgFromPhp )
|
||||
self:sendMessage( msgToPhp )
|
||||
elseif op == 'quit' then
|
||||
self:debug( 'MWServer:dispatch: quit message received' )
|
||||
os.exit(0)
|
||||
else
|
||||
self:internalError( "Invalid message operation" )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Write a message to the debug output stream.
|
||||
-- Some day this may be configurable, currently it just unconditionally writes
|
||||
-- the message to stderr. The PHP host will redirect those errors to /dev/null
|
||||
-- by default, but it can be configured to send them to a file.
|
||||
--
|
||||
-- @param s The message
|
||||
function MWServer:debug( s )
|
||||
if ( type(s) == 'string' ) then
|
||||
io.stderr:write( s .. '\n' )
|
||||
else
|
||||
io.stderr:write( self:serialize( s ) .. '\n' )
|
||||
end
|
||||
end
|
||||
|
||||
--- Raise an internal error
|
||||
-- Write a message to stderr and then exit with a failure status. This should
|
||||
-- be called for errors which cannot be allowed to be caught with pcall().
|
||||
--
|
||||
-- This must be used for protocol errors, or indeed any error from a context
|
||||
-- where a dispatch() call lies between the error source and a possible pcall()
|
||||
-- handler. If dispatch() were terminated by a regular error() call, the
|
||||
-- resulting protocol violation could lead to a deadlock.
|
||||
--
|
||||
-- @param msg The error message
|
||||
function MWServer:internalError( msg )
|
||||
io.stderr:write( debug.traceback( msg ) .. '\n' )
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
--- Raise an I/O error
|
||||
-- Helper function for errors from the io and file modules, which may optionally
|
||||
-- return an informative error message as their second return value.
|
||||
function MWServer:ioError( header, info )
|
||||
if type( info) == 'string' then
|
||||
self:internalError( header .. ': ' .. info )
|
||||
else
|
||||
self:internalError( header )
|
||||
end
|
||||
end
|
||||
|
||||
--- Send a message to PHP
|
||||
-- @param msg The message table
|
||||
function MWServer:sendMessage( msg )
|
||||
if not msg.op then
|
||||
self:internalError( "MWServer:sendMessage: invalid message", 2 )
|
||||
end
|
||||
self:debug('TX ==> ' .. msg.op)
|
||||
local encMsg = self:encodeMessage( msg )
|
||||
local success, errorMsg = io.stdout:write( encMsg )
|
||||
if not success then
|
||||
self:ioError( 'Write error', errorMsg )
|
||||
end
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
--- Wait for a message from PHP and then decode and return it as a table
|
||||
-- @return The received message
|
||||
function MWServer:receiveMessage()
|
||||
-- Read the header
|
||||
local header, errorMsg = io.stdin:read( 16 )
|
||||
if header == nil and errorMsg == nil then
|
||||
-- End of file on stdin, exit gracefully
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
if not header or #header ~= 16 then
|
||||
self:ioError( 'Read error', errorMsg )
|
||||
end
|
||||
local length = self:decodeHeader( header )
|
||||
|
||||
-- Read the body
|
||||
local body, errorMsg = io.stdin:read( length )
|
||||
if not body then
|
||||
self:ioError( 'Read error', errorMsg )
|
||||
end
|
||||
if #body ~= length then
|
||||
self:ioError( 'Read error', errorMsg )
|
||||
end
|
||||
|
||||
-- Unserialize it
|
||||
msg = self:unserialize( body )
|
||||
self:debug('RX <== ' .. msg.op)
|
||||
if msg.op == 'error' then
|
||||
self:debug( 'Error: ' .. tostring( msg.value ) )
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
--- Encode a message for sending to PHP
|
||||
function MWServer:encodeMessage( message )
|
||||
local serialized = self:serialize( message )
|
||||
local length = #serialized
|
||||
local check = length * 2 - 1
|
||||
return string.format( '%08x%08x', length, check ) .. serialized
|
||||
end
|
||||
|
||||
-- Faster to create the table once than for each call to MWServer:serialize()
|
||||
local serialize_replacements = {
|
||||
['\r'] = '\\r',
|
||||
['\n'] = '\\n',
|
||||
['\\'] = '\\\\',
|
||||
}
|
||||
|
||||
--- Convert a value to a string suitable for passing to PHP's unserialize().
|
||||
-- Note that the following replacements must be performed before calling
|
||||
-- unserialize:
|
||||
-- "\\r" => "\r"
|
||||
-- "\\n" => "\n"
|
||||
-- "\\\\" => "\\"
|
||||
--
|
||||
-- @param var The value.
|
||||
function MWServer:serialize( var )
|
||||
local done = {}
|
||||
local int_min = -2147483648
|
||||
local int_max = 2147483647
|
||||
|
||||
local function isInteger( var )
|
||||
return type(var) == 'number'
|
||||
and math.floor( var ) == var
|
||||
and var >= int_min
|
||||
and var <= int_max
|
||||
end
|
||||
|
||||
local function recursiveEncode( var, level )
|
||||
local t = type( var )
|
||||
if t == 'nil' then
|
||||
return 'N;'
|
||||
elseif t == 'number' then
|
||||
if isInteger(var) then
|
||||
return 'i:' .. var .. ';'
|
||||
elseif var < math.huge and var > -math.huge then
|
||||
return 'd:' .. var .. ';'
|
||||
elseif var == math.huge then
|
||||
return 'd:INF;'
|
||||
elseif var == -math.huge then
|
||||
return 'd:-INF;'
|
||||
else
|
||||
return 'd:NAN;'
|
||||
end
|
||||
elseif t == 'string' then
|
||||
return 's:' .. string.len( var ) .. ':"' .. var .. '";'
|
||||
elseif t == 'boolean' then
|
||||
if var then
|
||||
return 'b:1;'
|
||||
else
|
||||
return 'b:0;'
|
||||
end
|
||||
elseif t == 'table' then
|
||||
if done[var] then
|
||||
error("Cannot pass circular reference to PHP")
|
||||
end
|
||||
done[var] = true
|
||||
local buf = { '' }
|
||||
local tmpString
|
||||
local numElements = 0
|
||||
for key, value in pairs(var) do
|
||||
if (isInteger(key)) then
|
||||
buf[#buf + 1] = 'i:' .. key .. ';'
|
||||
else
|
||||
tmpString = tostring( key )
|
||||
buf[#buf + 1] = recursiveEncode( tostring( key ), level + 1 )
|
||||
end
|
||||
buf[#buf + 1] = recursiveEncode( value, level + 1 )
|
||||
numElements = numElements + 1
|
||||
end
|
||||
buf[1] = 'a:' .. numElements .. ':{'
|
||||
buf[#buf + 1] = '}'
|
||||
return table.concat(buf)
|
||||
elseif t == 'function' then
|
||||
local id
|
||||
if self.xchunks[var] then
|
||||
id = self.xchunks[var]
|
||||
else
|
||||
id = self:addChunk(var)
|
||||
end
|
||||
return 'O:42:"Scribunto_LuaStandaloneInterpreterFunction":1:{s:2:"id";i:' .. id .. ';}'
|
||||
elseif t == 'thread' then
|
||||
error("Cannot pass thread to PHP")
|
||||
elseif t == 'userdata' then
|
||||
error("Cannot pass userdata to PHP")
|
||||
else
|
||||
error("Cannot pass unrecognised type to PHP")
|
||||
end
|
||||
end
|
||||
|
||||
return recursiveEncode( var, 0 ):gsub( '[\r\n\\]', serialize_replacements )
|
||||
end
|
||||
|
||||
--- Convert a Lua expression string to its corresponding value.
|
||||
-- Convert any references of the form chunk[id] to the corresponding function
|
||||
-- values.
|
||||
function MWServer:unserialize( text )
|
||||
local func = loadstring( 'return ' .. text )
|
||||
if not func then
|
||||
self:internalError( "MWServer:unserialize: invalid chunk" )
|
||||
end
|
||||
-- Don't waste JIT cache space by storing every message in it
|
||||
if jit then
|
||||
jit.off( func )
|
||||
end
|
||||
setfenv( func, { chunks = self.chunks } )
|
||||
return func()
|
||||
end
|
||||
|
||||
--- Decode a message header.
|
||||
-- @param header The header string
|
||||
-- @return The body length
|
||||
function MWServer:decodeHeader( header )
|
||||
local length = string.sub( header, 1, 8 )
|
||||
local check = string.sub( header, 9, 16 )
|
||||
if not string.match( length, '^%x+$' ) or not string.match( check, '^%x+$' ) then
|
||||
self:internalError( "Error decoding message header: " .. length .. '/' .. check )
|
||||
end
|
||||
length = tonumber( length, 16 )
|
||||
check = tonumber( check, 16 )
|
||||
if length * 2 - 1 ~= check then
|
||||
self:internalError( "Error decoding message header" )
|
||||
end
|
||||
return length
|
||||
end
|
||||
|
||||
--- Get a traceback similar to the one from debug.traceback(), but as a table
|
||||
-- rather than formatted as a string
|
||||
--
|
||||
-- @param The level to start at: 1 for the function that called getStructuredTrace()
|
||||
-- @return A table with the backtrace information
|
||||
function MWServer:getStructuredTrace( level )
|
||||
level = level + 1
|
||||
local trace = {}
|
||||
while true do
|
||||
local rawInfo = debug.getinfo( level, 'nSl' )
|
||||
if rawInfo == nil then
|
||||
break
|
||||
end
|
||||
local info = {}
|
||||
for i, key in ipairs({'short_src', 'what', 'currentline', 'name', 'namewhat', 'linedefined'}) do
|
||||
info[key] = rawInfo[key]
|
||||
end
|
||||
if string.match( info['short_src'], '/MWServer.lua$' ) then
|
||||
info['short_src'] = 'MWServer.lua'
|
||||
end
|
||||
if string.match( rawInfo['short_src'], '/mw_main.lua$' ) then
|
||||
info['short_src'] = 'mw_main.lua'
|
||||
end
|
||||
table.insert( trace, info )
|
||||
level = level + 1
|
||||
end
|
||||
return trace
|
||||
end
|
||||
|
||||
--- Create a table to be used as a restricted environment, based on the current
|
||||
-- global environment.
|
||||
--
|
||||
-- @return The environment table
|
||||
function MWServer:newEnvironment()
|
||||
local allowedGlobals = {
|
||||
-- base
|
||||
"assert",
|
||||
"error",
|
||||
"getmetatable",
|
||||
"ipairs",
|
||||
"next",
|
||||
"pairs",
|
||||
"pcall",
|
||||
"rawequal",
|
||||
"rawget",
|
||||
"rawset",
|
||||
"select",
|
||||
"setmetatable",
|
||||
"tonumber",
|
||||
"type",
|
||||
"unpack",
|
||||
"xpcall",
|
||||
"_VERSION",
|
||||
-- libs
|
||||
"table",
|
||||
"math"
|
||||
}
|
||||
|
||||
local env = {}
|
||||
for i = 1, #allowedGlobals do
|
||||
env[allowedGlobals[i]] = mw.clone( _G[allowedGlobals[i]] )
|
||||
end
|
||||
|
||||
-- Cloning 'string' doesn't work right, because strings still use the old
|
||||
-- 'string' as the metatable. So just copy it.
|
||||
env.string = string
|
||||
|
||||
env._G = env
|
||||
env.tostring = function( val )
|
||||
return self:tostring( val )
|
||||
end
|
||||
env.string.dump = nil
|
||||
env.setfenv, env.getfenv = mw.makeProtectedEnvFuncs(
|
||||
self.protectedEnvironments, self.protectedFunctions )
|
||||
env.debug = {
|
||||
traceback = debug.traceback
|
||||
}
|
||||
env.os = {
|
||||
date = os.date,
|
||||
difftime = os.difftime,
|
||||
time = os.time,
|
||||
clock = os.clock
|
||||
}
|
||||
return env
|
||||
end
|
||||
|
||||
--- An implementation of tostring() which does not expose pointers.
|
||||
function MWServer:tostring(val)
|
||||
local mt = getmetatable( val )
|
||||
if mt and mt.__tostring then
|
||||
return mt.__tostring(val)
|
||||
end
|
||||
local typeName = type(val)
|
||||
local nonPointerTypes = {number = true, string = true, boolean = true, ['nil'] = true}
|
||||
if nonPointerTypes[typeName] then
|
||||
return tostring(val)
|
||||
else
|
||||
return typeName
|
||||
end
|
||||
end
|
||||
|
||||
return MWServer
|
||||
@@ -0,0 +1,8 @@
|
||||
package.path = arg[1] .. '/engines/LuaStandalone/?.lua;' ..
|
||||
arg[1] .. '/engines/LuaCommon/lualib/?.lua'
|
||||
|
||||
require('MWServer')
|
||||
require('mwInit')
|
||||
server = MWServer:new()
|
||||
server:execute()
|
||||
|
||||
635
res/bin/any/xowa/xtns/Scribunto/engines/Luaj/MWServer.lua
Normal file
635
res/bin/any/xowa/xtns/Scribunto/engines/Luaj/MWServer.lua
Normal file
@@ -0,0 +1,635 @@
|
||||
--require('MWClient')
|
||||
MWServer = {}
|
||||
|
||||
--- Create a new MWServer object
|
||||
function MWServer:new()
|
||||
obj = {
|
||||
nextChunkId = 1,
|
||||
chunks = {},
|
||||
xchunks = {},
|
||||
protectedFunctions = {},
|
||||
protectedEnvironments = {},
|
||||
baseEnv = {}
|
||||
}
|
||||
setmetatable( obj, self )
|
||||
self.__index = self
|
||||
|
||||
obj:init()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
--- Initialise a new MWServer object
|
||||
function MWServer:init()
|
||||
self.baseEnv = self:newEnvironment()
|
||||
for funcName, func in pairs( self ) do
|
||||
if type(func) == 'function' then
|
||||
self.protectedFunctions[func] = true
|
||||
end
|
||||
end
|
||||
self.protectedEnvironments[_G] = true
|
||||
end
|
||||
|
||||
--- Serve requests until exit is requested
|
||||
function MWServer:execute()
|
||||
-- self:dispatch( nil ) // starts do / loop; not needed
|
||||
self:debug( 'MWServer:execute: returning' )
|
||||
end
|
||||
|
||||
-- Convert a multiple-return-value or a ... into a count and a table
|
||||
function MWServer:listToCountAndTable( ... )
|
||||
return select( '#', ... ), { ... }
|
||||
end
|
||||
|
||||
--- Call a PHP function
|
||||
-- Raise an error if the PHP handler requests it. May return any number
|
||||
-- of values.
|
||||
--
|
||||
-- @param id The function ID, specified by a registerLibrary message
|
||||
-- @param nargs Count of function arguments
|
||||
-- @param args The function arguments
|
||||
-- @return The return values from the PHP function
|
||||
function MWServer:call( id, nargs, args )
|
||||
local result = self:server_send( {
|
||||
op = 'call',
|
||||
id = id,
|
||||
nargs = nargs,
|
||||
args = args
|
||||
} )
|
||||
if result.op == 'return' then
|
||||
return unpack( result.values, 1, result.nvalues )
|
||||
elseif result.op == 'error' then
|
||||
-- Raise an error in the actual user code that called the function
|
||||
-- The level is 3 since our immediate caller is a closure
|
||||
error( result.value, 3 )
|
||||
else
|
||||
self:internalError( 'MWServer:call: unexpected result op' )
|
||||
end
|
||||
end
|
||||
|
||||
--- Handle a "call" message from PHP. Call the relevant function.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return A response message to send back to PHP
|
||||
function MWServer:handleCall( message )
|
||||
if not self.chunks[message.id] then
|
||||
return {
|
||||
op = 'error',
|
||||
value = 'function id ' .. message.id .. ' does not exist'
|
||||
}
|
||||
end
|
||||
-- print(message.id .. '|' .. message.nargs);
|
||||
-- print(self.chunks[message.id]);
|
||||
-- print(message.args[1]);
|
||||
local n, result = self:listToCountAndTable( xpcall(
|
||||
function ()
|
||||
return self.chunks[message.id]( unpack( message.args, 1, message.nargs ) )
|
||||
end,
|
||||
function ( err )
|
||||
return MWServer:attachTrace( err )
|
||||
end
|
||||
) )
|
||||
|
||||
if result[1] then
|
||||
-- table.remove( result, 1 ) renumbers from 2 to #result. But #result
|
||||
-- is not necessarily "right" if result contains nils.
|
||||
result = { unpack( result, 2, n ) }
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = n - 1,
|
||||
values = result
|
||||
}
|
||||
else
|
||||
if result[2].value and result[2].trace then
|
||||
return {
|
||||
op = 'error',
|
||||
value = result[2].value,
|
||||
trace = result[2].trace,
|
||||
}
|
||||
else
|
||||
return {
|
||||
op = 'error',
|
||||
value = result[2]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- The xpcall() error handler for handleCall(). Modifies the error object
|
||||
-- to include a structured backtrace
|
||||
--
|
||||
-- @param err The error object
|
||||
-- @return The new error object
|
||||
function MWServer:attachTrace( err )
|
||||
return {
|
||||
value = err,
|
||||
trace = self:getStructuredTrace( 2 )
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "loadString" message from PHP.
|
||||
-- Load the function and return a chunk ID.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return A response message to send back to PHP
|
||||
function MWServer:handleLoadString( message )
|
||||
if string.find( message.text, '\27Lua', 1, true ) then
|
||||
return {
|
||||
op = 'error',
|
||||
value = 'cannot load code with a Lua binary chunk marker escape sequence in it'
|
||||
}
|
||||
end
|
||||
local chunk, errorMsg = loadstring( message.text, message.chunkName )
|
||||
if chunk then
|
||||
setfenv( chunk, self.baseEnv )
|
||||
local id = self:addChunk( chunk )
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 1,
|
||||
values = {id}
|
||||
}
|
||||
else
|
||||
return {
|
||||
op = 'error',
|
||||
value = errorMsg
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--- Add a function value to the list of tracked chunks and return its associated ID.
|
||||
-- Adding a chunk allows it to be referred to in messages from PHP.
|
||||
--
|
||||
-- @param chunk The function value
|
||||
-- @return The chunk ID
|
||||
function MWServer:addChunk( chunk )
|
||||
local id = self.nextChunkId
|
||||
self.nextChunkId = id + 1
|
||||
self.chunks[id] = chunk
|
||||
self.xchunks[chunk] = id
|
||||
return id
|
||||
end
|
||||
|
||||
--- Handle a "cleanupChunks" message from PHP.
|
||||
-- Remove any chunks no longer referenced by PHP code.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return A response message to send back to PHP
|
||||
function MWServer:handleCleanupChunks( message )
|
||||
for id, chunk in pairs( message.ids ) do -- XOWA: different from MW, in that message has chunks to cleanup, not chunks to preserv
|
||||
local chunk_key = self.chunks[id];
|
||||
-- dbg(id .. ':' .. tostring(chunk_key));
|
||||
self.chunks[id] = nil
|
||||
self.xchunks[chunk_key] = nil
|
||||
end
|
||||
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 0,
|
||||
values = {}
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "registerLibrary" message from PHP.
|
||||
-- Add the relevant functions to the base environment.
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return The response message
|
||||
function MWServer:handleRegisterLibrary( message )
|
||||
local startPos = 1
|
||||
local component
|
||||
if not self.baseEnv[message.name] then
|
||||
self.baseEnv[message.name] = {}
|
||||
end
|
||||
local t = self.baseEnv[message.name]
|
||||
|
||||
for name, id in pairs( message.functions ) do
|
||||
t[name] = function( ... )
|
||||
return self:call( id, self:listToCountAndTable( ... ) )
|
||||
end
|
||||
-- Protect the function against setfenv()
|
||||
self.protectedFunctions[t[name]] = true
|
||||
end
|
||||
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 0,
|
||||
values = {}
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "wrapPhpFunction" message from PHP.
|
||||
-- Create an anonymous function
|
||||
--
|
||||
-- @param message The message from PHP
|
||||
-- @return The response message
|
||||
function MWServer:handleWrapPhpFunction( message )
|
||||
local id = message.id
|
||||
local func = function( ... )
|
||||
return self:call( id, self:listToCountAndTable( ... ) )
|
||||
end
|
||||
-- Protect the function against setfenv()
|
||||
self.protectedFunctions[func] = true
|
||||
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 1,
|
||||
values = { func }
|
||||
}
|
||||
end
|
||||
|
||||
--- Handle a "getStatus" message from PHP
|
||||
--
|
||||
-- @param message The request message
|
||||
-- @return The response message
|
||||
function MWServer:handleGetStatus( message )
|
||||
local nullRet = {
|
||||
op = 'return',
|
||||
nvalues = 0,
|
||||
values = {}
|
||||
}
|
||||
local file = io.open( '/proc/self/stat' )
|
||||
if not file then
|
||||
return nullRet
|
||||
end
|
||||
local s = file:read('*a')
|
||||
file:close()
|
||||
local t = {}
|
||||
for token in string.gmatch(s, '[^ ]+') do
|
||||
t[#t + 1] = token
|
||||
end
|
||||
if #t < 22 then
|
||||
return nullRet
|
||||
end
|
||||
return {
|
||||
op = 'return',
|
||||
nvalues = 1,
|
||||
values = {{
|
||||
pid = tonumber(t[1]),
|
||||
time = tonumber(t[14]) + tonumber(t[15]) + tonumber(t[16]) + tonumber(t[17]),
|
||||
vsize = tonumber(t[23]),
|
||||
}}
|
||||
}
|
||||
end
|
||||
|
||||
--- The main request/response loop
|
||||
--
|
||||
-- Send a request message and return its matching reply message. Handle any
|
||||
-- intervening requests (i.e. re-entrant calls) by dispatching them to the
|
||||
-- relevant handler function.
|
||||
--
|
||||
-- The request message may optionally be omitted, to listen for request messages
|
||||
-- without first sending a request of its own. Such a dispatch() call will
|
||||
-- continue running until termination is requested by PHP. Typically, PHP does
|
||||
-- this with a SIGTERM signal.
|
||||
--
|
||||
-- @param msgToPhp The message to send to PHP. Optional.
|
||||
-- @return The matching response message
|
||||
function MWServer:server_send( send_msg )
|
||||
return self:sendMessage( send_msg )
|
||||
end
|
||||
|
||||
function MWServer:server_recv( recv_msg )
|
||||
local send_msg
|
||||
local op = recv_msg.op
|
||||
if op == 'return' or op == 'error' then
|
||||
return recv_msg
|
||||
elseif op == 'call' then
|
||||
return self:handleCall( recv_msg )
|
||||
elseif op == 'loadString' then
|
||||
return self:handleLoadString( recv_msg )
|
||||
elseif op == 'registerLibrary' then
|
||||
return self:handleRegisterLibrary( recv_msg )
|
||||
elseif op == 'wrapPhpFunction' then
|
||||
return self:handleWrapPhpFunction( recv_msg )
|
||||
elseif op == 'cleanupChunks' then
|
||||
return self:handleCleanupChunks( recv_msg )
|
||||
elseif op == 'getStatus' then
|
||||
return self:handleGetStatus( recv_msg )
|
||||
elseif op == 'quit' then
|
||||
self:debug( 'MWServer:dispatch: quit message received' )
|
||||
os.exit(0)
|
||||
else
|
||||
self:internalError( "Invalid message operation" )
|
||||
end
|
||||
end
|
||||
|
||||
--- Write a message to the debug output stream.
|
||||
-- Some day this may be configurable, currently it just unconditionally writes
|
||||
-- the message to stderr. The PHP host will redirect those errors to /dev/null
|
||||
-- by default, but it can be configured to send them to a file.
|
||||
--
|
||||
-- @param s The message
|
||||
function MWServer:debug( s )
|
||||
if ( type(s) == 'string' ) then
|
||||
io.stderr:write( s .. '\n' )
|
||||
else
|
||||
io.stderr:write( self:serialize( s ) .. '\n' )
|
||||
end
|
||||
end
|
||||
|
||||
--- Raise an internal error
|
||||
-- Write a message to stderr and then exit with a failure status. This should
|
||||
-- be called for errors which cannot be allowed to be caught with pcall().
|
||||
--
|
||||
-- This must be used for protocol errors, or indeed any error from a context
|
||||
-- where a dispatch() call lies between the error source and a possible pcall()
|
||||
-- handler. If dispatch() were terminated by a regular error() call, the
|
||||
-- resulting protocol violation could lead to a deadlock.
|
||||
--
|
||||
-- @param msg The error message
|
||||
function MWServer:internalError( msg )
|
||||
io.stderr:write( debug.traceback( msg ) .. '\n' )
|
||||
os.exit( 1 )
|
||||
end
|
||||
|
||||
--- Raise an I/O error
|
||||
-- Helper function for errors from the io and file modules, which may optionally
|
||||
-- return an informative error message as their second return value.
|
||||
function MWServer:ioError( header, info )
|
||||
if type( info) == 'string' then
|
||||
self:internalError( header .. ': ' .. info )
|
||||
else
|
||||
self:internalError( header )
|
||||
end
|
||||
end
|
||||
|
||||
--- Send a message to PHP
|
||||
-- @param msg The message table
|
||||
function MWServer:sendMessage( msg )
|
||||
if not msg.op then
|
||||
self:internalError( "MWServer:sendMessage: invalid message", 2 )
|
||||
end
|
||||
--self:debug('TX ==> ' .. msg.op)
|
||||
return MWClient.client_recv(msg)
|
||||
end
|
||||
|
||||
--- Wait for a message from PHP and then decode and return it as a table
|
||||
-- @return The received message
|
||||
function MWServer:receiveMessage()
|
||||
-- Read the header
|
||||
local header, errorMsg = io.stdin:read( 16 )
|
||||
if header == nil and errorMsg == nil then
|
||||
-- End of file on stdin, exit gracefully
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
if not header or #header ~= 16 then
|
||||
self:ioError( 'Read error', errorMsg )
|
||||
end
|
||||
local length = self:decodeHeader( header )
|
||||
|
||||
-- Read the body
|
||||
local body, errorMsg = io.stdin:read( length )
|
||||
if not body then
|
||||
self:ioError( 'Read error', errorMsg )
|
||||
end
|
||||
if #body ~= length then
|
||||
self:ioError( 'Read error', errorMsg )
|
||||
end
|
||||
|
||||
-- Unserialize it
|
||||
msg = self:unserialize( body )
|
||||
self:debug('RX <== ' .. msg.op)
|
||||
if msg.op == 'error' then
|
||||
self:debug( 'Error: ' .. tostring( msg.value ) )
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
--- Encode a message for sending to PHP
|
||||
function MWServer:encodeMessage( message )
|
||||
local serialized = self:serialize( message )
|
||||
local length = #serialized
|
||||
local check = length * 2 - 1
|
||||
return string.format( '%08x%08x', length, check ) .. serialized
|
||||
end
|
||||
|
||||
-- Faster to create the table once than for each call to MWServer:serialize()
|
||||
local serialize_replacements = {
|
||||
['\r'] = '\\r',
|
||||
['\n'] = '\\n',
|
||||
['\\'] = '\\\\',
|
||||
}
|
||||
|
||||
--- Convert a value to a string suitable for passing to PHP's unserialize().
|
||||
-- Note that the following replacements must be performed before calling
|
||||
-- unserialize:
|
||||
-- "\\r" => "\r"
|
||||
-- "\\n" => "\n"
|
||||
-- "\\\\" => "\\"
|
||||
--
|
||||
-- @param var The value.
|
||||
function MWServer:serialize( var )
|
||||
local done = {}
|
||||
local int_min = -2147483648
|
||||
local int_max = 2147483647
|
||||
|
||||
local function isInteger( var )
|
||||
return type(var) == 'number'
|
||||
and math.floor( var ) == var
|
||||
and var >= int_min
|
||||
and var <= int_max
|
||||
end
|
||||
|
||||
local function recursiveEncode( var, level )
|
||||
local t = type( var )
|
||||
if t == 'nil' then
|
||||
return 'N;'
|
||||
elseif t == 'number' then
|
||||
if isInteger(var) then
|
||||
return 'i:' .. var .. ';'
|
||||
elseif var < math.huge and var > -math.huge then
|
||||
return 'd:' .. var .. ';'
|
||||
elseif var == math.huge then
|
||||
return 'd:INF;'
|
||||
elseif var == -math.huge then
|
||||
return 'd:-INF;'
|
||||
else
|
||||
return 'd:NAN;'
|
||||
end
|
||||
elseif t == 'string' then
|
||||
return 's:' .. string.len( var ) .. ':"' .. var .. '";'
|
||||
elseif t == 'boolean' then
|
||||
if var then
|
||||
return 'b:1;'
|
||||
else
|
||||
return 'b:0;'
|
||||
end
|
||||
elseif t == 'table' then
|
||||
if done[var] then
|
||||
error("Cannot pass circular reference to PHP")
|
||||
end
|
||||
done[var] = true
|
||||
local buf = { '' }
|
||||
local tmpString
|
||||
local numElements = 0
|
||||
for key, value in pairs(var) do
|
||||
if (isInteger(key)) then
|
||||
buf[#buf + 1] = 'i:' .. key .. ';'
|
||||
else
|
||||
tmpString = tostring( key )
|
||||
buf[#buf + 1] = recursiveEncode( tostring( key ), level + 1 )
|
||||
end
|
||||
buf[#buf + 1] = recursiveEncode( value, level + 1 )
|
||||
numElements = numElements + 1
|
||||
end
|
||||
buf[1] = 'a:' .. numElements .. ':{'
|
||||
buf[#buf + 1] = '}'
|
||||
return table.concat(buf)
|
||||
elseif t == 'function' then
|
||||
local id
|
||||
if self.xchunks[var] then
|
||||
id = self.xchunks[var]
|
||||
else
|
||||
id = self:addChunk(var)
|
||||
end
|
||||
return 'O:42:"Scribunto_LuaStandaloneInterpreterFunction":1:{s:2:"id";i:' .. id .. ';}'
|
||||
elseif t == 'thread' then
|
||||
error("Cannot pass thread to PHP")
|
||||
elseif t == 'userdata' then
|
||||
error("Cannot pass userdata to PHP")
|
||||
else
|
||||
error("Cannot pass unrecognised type to PHP")
|
||||
end
|
||||
end
|
||||
|
||||
return recursiveEncode( var, 0 ):gsub( '[\r\n\\]', serialize_replacements )
|
||||
end
|
||||
|
||||
--- Convert a Lua expression string to its corresponding value.
|
||||
-- Convert any references of the form chunk[id] to the corresponding function
|
||||
-- values.
|
||||
function MWServer:unserialize( text )
|
||||
local func = loadstring( 'return ' .. text )
|
||||
if not func then
|
||||
self:internalError( "MWServer:unserialize: invalid chunk" )
|
||||
end
|
||||
-- Don't waste JIT cache space by storing every message in it
|
||||
if jit then
|
||||
jit.off( func )
|
||||
end
|
||||
setfenv( func, { chunks = self.chunks } )
|
||||
return func()
|
||||
end
|
||||
|
||||
--- Decode a message header.
|
||||
-- @param header The header string
|
||||
-- @return The body length
|
||||
function MWServer:decodeHeader( header )
|
||||
local length = string.sub( header, 1, 8 )
|
||||
local check = string.sub( header, 9, 16 )
|
||||
if not string.match( length, '^%x+$' ) or not string.match( check, '^%x+$' ) then
|
||||
self:internalError( "Error decoding message header: " .. length .. '/' .. check )
|
||||
end
|
||||
length = tonumber( length, 16 )
|
||||
check = tonumber( check, 16 )
|
||||
if length * 2 - 1 ~= check then
|
||||
self:internalError( "Error decoding message header" )
|
||||
end
|
||||
return length
|
||||
end
|
||||
|
||||
--- Get a traceback similar to the one from debug.traceback(), but as a table
|
||||
-- rather than formatted as a string
|
||||
--
|
||||
-- @param The level to start at: 1 for the function that called getStructuredTrace()
|
||||
-- @return A table with the backtrace information
|
||||
function MWServer:getStructuredTrace( level )
|
||||
level = level + 1
|
||||
local trace = {}
|
||||
while true do
|
||||
local rawInfo = debug.getinfo( level, 'nSl' )
|
||||
if rawInfo == nil then
|
||||
break
|
||||
end
|
||||
local info = {}
|
||||
for i, key in ipairs({'short_src', 'what', 'currentline', 'name', 'namewhat', 'linedefined'}) do
|
||||
info[key] = rawInfo[key]
|
||||
end
|
||||
if string.match( info['short_src'], '/MWServer.lua$' ) then
|
||||
info['short_src'] = 'MWServer.lua'
|
||||
end
|
||||
if string.match( rawInfo['short_src'], '/mw_main.lua$' ) then
|
||||
info['short_src'] = 'mw_main.lua'
|
||||
end
|
||||
table.insert( trace, info )
|
||||
level = level + 1
|
||||
end
|
||||
return trace
|
||||
end
|
||||
|
||||
--- Create a table to be used as a restricted environment, based on the current
|
||||
-- global environment.
|
||||
--
|
||||
-- @return The environment table
|
||||
function MWServer:newEnvironment()
|
||||
local allowedGlobals = {
|
||||
-- base
|
||||
"assert",
|
||||
"error",
|
||||
"getmetatable",
|
||||
"ipairs",
|
||||
"next",
|
||||
"pairs",
|
||||
"pcall",
|
||||
"rawequal",
|
||||
"rawget",
|
||||
"rawset",
|
||||
"select",
|
||||
"setmetatable",
|
||||
"tonumber",
|
||||
"type",
|
||||
"unpack",
|
||||
"xpcall",
|
||||
"_VERSION",
|
||||
"dbg", --for debugging
|
||||
-- libs
|
||||
"table",
|
||||
"math"
|
||||
}
|
||||
|
||||
local env = {}
|
||||
for i = 1, #allowedGlobals do
|
||||
env[allowedGlobals[i]] = mw.clone( _G[allowedGlobals[i]] )
|
||||
end
|
||||
|
||||
-- Cloning 'string' doesn't work right, because strings still use the old
|
||||
-- 'string' as the metatable. So just copy it.
|
||||
env.string = string
|
||||
|
||||
env._G = env
|
||||
env.tostring = function( val )
|
||||
return self:tostring( val )
|
||||
end
|
||||
env.string.dump = nil
|
||||
env.setfenv, env.getfenv = mw.makeProtectedEnvFuncs(
|
||||
self.protectedEnvironments, self.protectedFunctions )
|
||||
|
||||
-- env.debug = {
|
||||
-- traceback = debug.traceback
|
||||
-- }
|
||||
env.os = {
|
||||
date = os.date,
|
||||
difftime = os.difftime,
|
||||
time = os.time,
|
||||
clock = os.clock
|
||||
}
|
||||
return env
|
||||
end
|
||||
|
||||
--- An implementation of tostring() which does not expose pointers.
|
||||
function MWServer:tostring(val)
|
||||
local mt = getmetatable( val )
|
||||
if mt and mt.__tostring then
|
||||
return mt.__tostring(val)
|
||||
end
|
||||
local typeName = type(val)
|
||||
local nonPointerTypes = {number = true, string = true, boolean = true, ['nil'] = true}
|
||||
if nonPointerTypes[typeName] then
|
||||
return tostring(val)
|
||||
else
|
||||
return typeName
|
||||
end
|
||||
end
|
||||
|
||||
return MWServer
|
||||
9
res/bin/any/xowa/xtns/Scribunto/engines/Luaj/mw_main.lua
Normal file
9
res/bin/any/xowa/xtns/Scribunto/engines/Luaj/mw_main.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
require('MWServer')
|
||||
require('mwInit')
|
||||
_G.getfenv = require 'compat_env'.getfenv
|
||||
_G.setfenv = require 'compat_env'.setfenv
|
||||
_G.loadstring = load
|
||||
_G.unpack = table.unpack
|
||||
|
||||
server = MWServer:new()
|
||||
return server
|
||||
1
res/bin/any/xowa/xtns/Scribunto/engines/Luaj/readme.txt
Normal file
1
res/bin/any/xowa/xtns/Scribunto/engines/Luaj/readme.txt
Normal file
@@ -0,0 +1 @@
|
||||
The Luaj files are not part of the official Scribunto distribution. They were developed for XOWA and the Luaj jar
|
||||
Reference in New Issue
Block a user