Module:User:Mr. Stradivarius/sandbox
Appearance
![]() | This is the module sandbox page for Module:User:Mr. Stradivarius. |
local checkType = require( 'libraryUtil' ).checkType
local uri = {}
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
-- 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
local function normalizeKeyPart( part, qs )
if part == '' then
-- Find the index for implicit array keys like foo[]
part = #qs + 1
elseif tonumber( part ) then
-- Adjust numbered keys for compatibility with Lua. Query array
-- keys start from 0, but Lua tables start from 1.
part = tonumber( part ) + 1
end
return part
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
-- Find the key and the value.
local key, val
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 )
if not eq or eq > amp then
key = rawdecode( string.sub( s, i, amp - 1 ) )
val = false
else
key = rawdecode( string.sub( s, i, eq - 1 ) )
val = rawdecode( string.sub( s, eq + 1, amp - 1 ) )
end
i = amp + 1
end
-- Try to parse array keys like foo[], foo[0] or foo[bar].
-- Keys like this can have any number of sub-keys, e.g. foo[0][bar][baz].
-- First check for the key prefix foo, and then iterate over the rest
-- of the string to find the sub-keys.
local keyParts
do
local _, lb, part = string.find( key, '^(.-)%[' )
if part then
-- The key contains square brackets, so try to parse it.
keyParts = { part }
local len = string.len( key )
while lb <= len do
local rb = string.find( key, ']', lb, true )
if not rb or string.sub( key, lb, lb ) ~= '[' then
-- The key has an invalid array syntax (something like
-- "foo[bar][baz" or "foo[bar]baz") so treat it as one
-- entity.
keyParts = { key }
break
end
table.insert( keyParts, string.sub( key, lb + 1, rb - 1 ) )
lb = rb + 1
end
else
-- The key doesn't contain square brackets, so treat it as one
-- entity.
keyParts = { key }
end
end
-- Insert the key parts into the query table.
do
local prevqs = qs
local len = #keyParts
-- Make sure we have subtables in place for all of the key parts
-- except the last one, which will correspond to the actual value.
for i = 1, len - 1 do
local part = normalizeKeyPart( keyParts[i], prevqs )
if type( prevqs[part] ) ~= 'table' then
local tmp = prevqs[part]
prevqs[part] = makeQueryTable()
table.insert( prevqs[part], tmp )
end
prevqs = prevqs[part]
end
-- Insert the value into the last subtable.
local lastPart = normalizeKeyPart( keyParts[len], prevqs )
if prevqs[lastPart] then
if type( prevqs[lastPart] ) == 'table' then
table.insert( prevqs[lastPart], val )
else
prevqs[lastPart] = { prevqs[lastPart], val }
end
else
prevqs[lastPart] = val
end
end
end
return qs
end
local function buildQueryStringRecursive( val, key, ret, errorLevel )
ret = ret or {}
errorLevel = errorLevel or 3
local tp = type( val )
if tp == 'table' then
for k, v in pairs( val ) do
local ktp = type( k )
local subKey
if ktp == 'number' then
subKey = k - 1 -- Start array keys at zero, as that's what PHP does
elseif ktp == 'string' then
subKey = k
else
error( string.format(
"query keys must be strings or numbers (%s detected)",
ktp
), errorLevel )
end
local newKey
if key then
newKey = key .. '[' .. subKey .. ']'
else
newKey = subKey
end
buildQueryStringRecursive( v, newKey, ret, errorLevel + 1 )
end
elseif tp == 'string' or tp == 'number' or tp == 'boolean' then
ret[#ret+1] = '&'
ret[#ret+1] = rawencode( key, '+' )
if val then
ret[#ret+1] = '='
ret[#ret+1] = rawencode( val, '+' )
end
else
error( string.format(
"query values must be of type string, number, boolean or table (%s detected)",
tp
), errorLevel )
end
return table.concat( ret, '', 2 )
end
--[[
{ foo = { 'bar', baz = { 'a', 'b' } }, normal = 'val' }
foo[0]=bar&foo[baz][0]=a&foo[baz][1]=b&normal=val
--]]
function uri.buildQueryString( qs )
checkType( 'buildQueryString', 1, qs, 'table' )
return buildQueryStringRecursive( qs )
end
return uri