Jump to content

Module:User:Mr. Stradivarius/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mr. Stradivarius (talk | contribs) at 14:53, 15 July 2015 (finish recursive buildQueryString). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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

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

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