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 03:16, 16 July 2015 (fix some bugs in parseQueryString). 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 key, val
		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

		local iKey = 1
		local jKey = string.len( key )
		local prevqs = qs
		while true do
			local lb = string.find( key, '[', iKey, true )
			local rb, subKey
			if lb and lb <= jKey - 1 then
				rb = string.find( key, ']', lb, true )
				if rb and rb <= jKey then
					if lb == iKey then
						-- This is something like [bar]
						subKey = string.sub( key, lb + 1, rb - 1 )
						iKey = rb + 1
					else
						-- This is something like foo[bar]
						subKey = string.sub( key, 1, lb - 1 )
						iKey = lb
					end
				end
			end
			if not subKey then
				subKey = key
				iKey = jKey + 1
			end
			if subKey == '' then
				-- Find the index for implicit array keys like foo[]
				subKey = #prevqs + 1
			elseif tonumber( subKey ) then
				-- Adjust numbered keys for compatibility with Lua. Query array
				-- keys start from 0, but Lua tables start at 1.
				subKey = tonumber( subKey ) + 1
			end
			if iKey <= jKey then
				-- We have more sub-keys to process.
				if type( prevqs[subKey] ) ~= 'table' then
					local tmp = prevqs[subKey]
					prevqs[subKey] = makeQueryTable()
					table.insert( prevqs[subKey], tmp )
				end
				prevqs = prevqs[subKey]
			elseif prevqs[subKey] then
				-- We have no more sub-keys to process, and the key already
				-- exists in the query table.
				if type( prevqs[subKey] ) == 'table' then
					table.insert( prevqs[subKey], val )
				else
					prevqs[subKey] = { prevqs[subKey], val }
				end
				break
			else
				-- We have no more sub-keys to process, and we are adding a new
				-- key.
				prevqs[subKey] = val
				break
			end
		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