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 10:27, 16 July 2015 (fix bad variable name). 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

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