Ftf libs

From GiderosMobile

Here are some longer code (classes) from the Gideros forum and some other places :-)

All samples are ready to use. Enjoy!

AUDIO

Producing Sound Without Resources @PaulH

Creates files on the fly using the .wav format for playback

-- https://forum.gideros.rocks/discussion/comment/68095/#Comment_68095
function make_wave(file_name, length, wave, freq, vol)
	-- We'll need to write some 32 bit values in binary. Helper function for that:
	local function to4Bytes(n) return n & 0xff, (n>>8) & 0xff, (n>>16) & 0xff, (n>>24) & 0xff end

	-- For a given point (sample number) in a wav file, return the 16 bit value for the specified sound wave and frequency
	local function sound_wave(wave,p,freq,vol)
		local sample_rate = 6000
		local n = 0 -- Default to silence
		if wave == "noise" then n = math.random(-255*128*vol, 255*128*vol)
		elseif wave == "sine" then n = math.floor(math.sin(p/sample_rate*freq*math.rad(180))*256*vol*127)
		elseif wave == "sawtooth" then n = math.floor((freq*p/sample_rate)*256*vol*256)
		end
		local c2, c1 = (n>>8) & 0xff, n & 0xff
		return string.char(c1, c2)
	end

	-- We're just dealing with a fixed format wav file:
	local sample_rate = 6000
	local samples = length*sample_rate
	local data_size = samples*2
	local total_file_length = 44+data_size
	local fl1, fl2, fl3, fl4 = to4Bytes(total_file_length)
	local ds1, ds2, ds3, ds4 = to4Bytes(data_size)
	local f = io.open(file_name, "wb")
	-- Write the WAV file header:
	f:write(
		string.char(
			82,73,70,70,--RIFF
			fl1, fl2, fl3, fl4,-- File size overall
			87,65,86,69,-- WAVE header
			102,109,116,32,--fmt chunk marker w/ trailing null
			16,0,0,0, -- Length of format data (16 bytes before this)
			1,0, -- Wave type. 1 == PCM
			1,0, -- Channels. 1 == mono
			112,23,0,0,-- Sample rate (Hertz)
			224,46,0,0, -- Bits per sample * channels * sample rate / 8
			2,0,
			16,0, -- Bytes per sample - this is 16 
			100,97,116,97, -- "data" - chunk header
			ds1,ds2,ds3,ds4-- size of data section
		)
	)
	-- Write the data:
	for l = 1, samples do
		f:write(sound_wave(wave,l,freq, vol or 1))
	end
	f:close()
end

-- Demo some white noise:
--make_wave("|D|noise.wav", 1, "noise") -- 0.1 second sine wave at 256 Herz (middle C) at full volume
--s=Sound.new("|D|noise.wav")
--s:play()

-- one note with a sawtooth wave:
--make_wave("|D|note.wav", 2, "sawtooth", 262, 1) -- 0.1 second sine wave at 256 Herz (middle C) at full volume
--s=Sound.new("|D|note.wav")
--s:play()

-- Make wave files for the chromatic scale.
-- The frequencies in Hertz of the notes in octave zero:
notes = { 16.35, 17.32, 18.35, 19.45, 20.60, 21.83, 23.12, 24.5, 25.96, 27.50, 29.14, 30.87 }
-- 3 letter names for those notes:
names = { "C0n", "C0s", "D0n", "D0s", "E0n", "F0n", "F0s", "G0n", "G0s", "A0n", "A0s", "B0n" }
-- Compute the ratio of each note to the C:
local note_multipliers = {}
for i = 1, #notes do
	note_multipliers[i] = notes[i]/notes[1]
end

-- Make a sound for each note in several octaves:
local sounds = {}
for octave = 3, 7 do
	for i = 1, #names do
		local original_note = names[i]
		local octave_note = string.gsub(original_note, "0", octave)
		local file_name = "|D|note_" .. octave_note .. ".wav"
		-- Compute the frequency for the note by applying the ratio of that note over C
		-- and then apply the octave multiplier. Each octave is double the frequency
		-- of the one below it:
		local fr = math.floor(notes[1] * note_multipliers[i] * math.pow(2, octave))
		-- We'll make each note 0.2 seconds, using a sine wave
		--function make_wave(file_name, length, wave, freq, vol)
		make_wave(file_name, 0.2, "sine", fr, 1)
		sounds[octave_note] = Sound.new(file_name)
	end
end

-- Given a string of 3 letter note names, play those notes in order:
function play_sequence(seq)
	for i = 1, string.len(seq), 3 do
		local note = string.sub(seq, i, i+2)
		if sounds[note] then
			Timer.delayedCall(60 * (i-1), function()
				sounds[note]:play()
			end)
		end
	end
end

local fur_elise =
	"E4nD4sE4nD4sE4nB3nD4nC4nA3n      "..
	"C3nE3nA3nB3n      "..
	"E3nG3sB3nC4n      "..
	"E3nE4nD4sE4nD4sE4nB3nD4nC4nA3n      "..
	"C3nE3nA3nB3n      "..
	"E3nC4nB3nA3n"
play_sequence(fur_elise)
-- To play an octave higher:
Timer.delayedCall(9000, function()
	play_sequence(string.gsub(string.gsub(fur_elise,"4","5"),"3","4"))
end)

STRING

Score Counting TextField @errorpi

--[[
	Score Counting TextField
	by: @errorpi
]]

Score = Core.class(Sprite)
 
function Score:init(xscore, xincrease)
	self.score = xscore or 0
	self.countTime = (xincrease or 5) * 100
	-- a textfield
	self.scoreText = TextField.new(nil, tostring(self.score))
	self.scoreText:setLayout({wid = 80, hei = 40, flags=FontBase.TLF_CENTER})
	self.scoreText:setTextColor(math.random(0xffffff))
	self.scoreText:setScale(5)
	self.scoreText:setPosition(64, 64)
	self:addChild(self.scoreText)
	-- a timer
	self.t = Timer.new(1) -- timer init
	self.t:addEventListener(Event.TIMER, self.updateText, self)
end
 
function Score:updateText()
	local displayScore = tonumber(self.scoreText:getText())
	if displayScore < self.score then
		self.scoreText:setText(tostring( displayScore + 1) )
	end
end

function Score:addScore(add)
	self.score += add
	local displayScore = tonumber(self.scoreText:getText())
	-- set timer properties (bigger add score means faster steps)
	local tRepeat = self.score - displayScore
	local tDelay =  self.countTime / tRepeat
	self.t:stop()
	self.t:reset()
	self.t:setDelay(tDelay)
	self.t:setRepeatCount(tRepeat)
	self.t:start()

	return self.score
end

--------------------
application:setBackgroundColor(0x4d4d4d)
-- demo
local initialscore = 500
local score = Score.new(initialscore, 6) -- initial score value, increase speed
stage:addChild(score)
local earnedpoints = 300

Timer.delayedCall(3000, function()
	score:addScore(earnedpoints) -- target score value
end)

GFX

Hex to Number / Number to Hex @hanzhao

--[[
Lua Hex v0.4
-------------------
Hex conversion lib for Lua.

How to use:
 lhex.to_hex(n) -- convert a number to a hex string
 lhex.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)
--]]

local function to_bits(n)
	-- checking not float
	if(n - (n//1) > 0) then error("trying to apply bitwise operation on non-integer!") end
	if(n < 0) then return to_bits(~(n//1) + 1) end -- negative
	-- to bits table
	local tbl = {}
	local cnt = 1
	while (n > 0) do
		local last = n%2
		if(last == 1) then tbl[cnt] = 1
		else tbl[cnt] = 0
		end
		n = (n-last)/2
		cnt += 1
	end
	return tbl
end

local function tbl_to_number(tbl)
	local n = #tbl
	local rslt = 0
	local power = 1
	for i = 1, n do
		rslt += tbl[i]*power
		power *= 2
	end
	return rslt
end

local function to_hex(n)
	if(type(n) ~= "number") then error("non-number type passed in.") end
	-- checking not float
	if(n - (n//1) > 0) then error("trying to apply bitwise operation on non-integer!") end
	if(n < 0) then -- negative
		n = to_bits(~(-n<>n) + 1)
		n = tbl_to_number(n)
	end
	local hex_tbl = { "A", "B", "C", "D", "E", "F" }
	local hex_str = ""
	while(n ~= 0) do
		local last = n%16
		if(last < 10) then hex_str = tostring(last) .. hex_str
		else hex_str = hex_tbl[last-10+1] .. hex_str
		end
		n = (n/16)//1 -- floor
	end
	if(hex_str == "") then hex_str = "0" end
	return "0x"..hex_str
end

local function to_dec(hexstring)
	if(type(hexstring) ~= "string") then error("non-string type passed in.") end
	local head = string.sub(hexstring, 1, 2)
	if(head ~= "0x" and head ~= "0X") then error("wrong hex format, should lead by 0x or 0X.") end
	return tonumber(hexstring:sub(3), 16) -- base 16
end

--------------------
-- lua hex lib interface
lhex = {
	to_hex=to_hex,
	to_dec=to_dec,
}

--------------------
-- examples
local dec = 4341688
local hex = lhex.to_hex(dec) -- number to hex
print(hex) -- 0x423FB8
local revdec = lhex.to_dec(hex) -- hex string (prefix with "0x" or "0X") to number
print(revdec) -- 4341688

print(lhex.to_dec("0x0")) -- 0, hex string (prefix with "0x" or "0X") to number
print(lhex.to_dec("0x00ff00")) -- 65280, hex string (prefix with "0x" or "0X") to number
print(lhex.to_hex(16777215)) -- 0xFFFFFF, number to hex

local pix = Pixel.new(lhex.to_hex(56816), 1, 32, 32)
stage:addChild(pix)

print("hex is", hex)

GTween Demo @rrraptor

require "easing"

application:setBackgroundColor(0x4c4c4c)

function animate(values) -- create a new GTween or use existing one
	local function NULL() end
	values.target.gtween = values.target.gtween or nil -- GTween
	values.duration = values.duration or 0
	values.values = values.values or {}
	values.props = values.props or { delay=0, ease=easing.linear, repeatCount=1, dispatchEvents=false, }
	values.target.callback = values.target.callback or NULL

	if values.target.gtween then -- use existing gtween
		print("using existing gtween", math.random(100))
		values.target.gtween:setValues(values.values)
	else -- create new gtween
		print("creating new gtween", math.random(100))
		--function GTween:init(target, duration, values, props)
		values.target.gtween = GTween.new(values.target, values.duration, values.values, values.props)
		values.target.gtween:addEventListener("complete", values.target.callback, values.target.gtween)
--		values.target.gtween:addEventListener("change", function() print("tween changing") end)
	end
	values.target.gtween:setPosition(0)
	values.target.gtween:setPaused(false)
end

local function followTween(data, e)
	if data.source == nil then
		print("you need to provide a source for the gtween!")
		return
	end

	data.source:setColor(math.random(0xffffff))

	local duration = 1
	local values = {} -- target values
	values.x = e.touch.x
	values.y = e.touch.y
	values.alpha = 1.2
	values.scaleX = data.source.sx * 1
	values.scaleY = data.source.sy * 1.5
	local properties = {}
	properties.delay = 64
	properties.ease = easing.outBack
	properties.repeatCount = 1
	properties.dispatchEvents = true

	animate( --function GTween:init(target, duration, values, props)
		{
			target = data.source, -- target = source + source.gtween + source.callback
			duration = duration,
			values = values,
			props = properties,
		}
	)
end

-- gtween playground
local pix = Pixel.new(math.random(0xffffff), 1, 64, 64)
pix:setAnchorPoint(0.5, 0)
pix:setPosition(application:getContentWidth()/2, application:getContentHeight()/2)

pix.gtween = nil
pix.sx = pix:getScaleX()
pix.sy = pix:getScaleY()
pix.callback = function()
	print("pix tween ended", math.random(100))
	pix.gtween.nextTween = GTween.new(
		pix, 0.3,
		{ -- target values
			alpha = 1,
			scaleX = pix.sx * 1.5,
			scaleY = pix.sy * 0.5,
		},
		{
			ease = easing.outBounce,
			repeatCount = 1,
		}
	)
end

stage:addChild(pix)
stage:addEventListener(Event.TOUCHES_END, followTween, { source=pix, })

2D Blue Noise points @ai

There are two main ways to generate "Blue Noise" in Lua, depending on what you need it for:

  • Poisson Disk Sampling (Bridson's Algorithm): Best for scattering objects (trees, stars, particles) evenly but randomly. This generates a list of coordinates.
  • Void and Cluster (Texture Generation): Best for creating a grid of pixel values (like a dithering texture). This is significantly more complex to implement from scratch.

Below is a robust, standard implementation of Poisson Disk Sampling in pure Lua. This is the most common approach for game development and procedural generation.

-- 2D Blue Noise points ai generated (https://chat.z.ai/c/efa8ba11-749f-4460-9342-b0928a65f40f)

local function generateBlueNoise(width, height, minDistance, k)
    -- Seed the random number generator
    math.randomseed(os.time())
    
    -- 1. Calculate cell size based on minimum distance
    -- The grid cell size ensures that we only check a small number of neighbors
    -- r / sqrt(2) is the maximum distance a point can be from the center of a cell
    local cellSize = minDistance / math.sqrt(2)
--    local gridWidth = math.ceil(width / cellSize)
--    local gridHeight = math.ceil(height / cellSize)

    -- 2. Initialize the Grid and Active List
    -- The grid acts as a spatial hash to speed up distance checks
    local grid = {} 
    local activeList = {}
    local points = {}

    -- Helper function to convert (x, y) to grid coordinates
    local function toGridCoords(x, y)
        return math.floor(x / cellSize) + 1, math.floor(y / cellSize) + 1
    end

    -- Helper to safely get a point from the grid
    local function getGridPoint(gx, gy)
        if grid[gx] then return grid[gx][gy] end
        return nil
    end

    -- Helper to put a point in the grid
    local function setGridPoint(gx, gy, point)
        if not grid[gx] then grid[gx] = {} end
        grid[gx][gy] = point
    end

    -- 3. Add the first random point
    local firstX = math.random() * width
    local firstY = math.random() * height
    local firstPoint = { x = firstX, y = firstY }
    
    table.insert(points, firstPoint)
    
    local gx, gy = toGridCoords(firstX, firstY)
    setGridPoint(gx, gy, firstPoint)
    table.insert(activeList, firstPoint)

    -- 4. Main Loop
    while #activeList > 0 do
        -- Pick a random index from the active list
        local randomIndex = math.random(1, #activeList)
        local parentPoint = activeList[randomIndex]
        local found = false

        -- Try to find a new point 'k' times (usually 30 is sufficient)
        for i = 1, k do
            -- Generate a random point in an annulus (donut) around the parent
            local angle = math.random() * 2 * math.pi
            -- Radius is between minDistance and 2 * minDistance
            local radius = math.random(minDistance, 2 * minDistance)
            
            local newX = parentPoint.x + math.cos(angle) * radius
            local newY = parentPoint.y + math.sin(angle) * radius

            -- Check if the point is within bounds
            if newX >= 0 and newX <= width and newY >= 0 and newY <= height then
                local ngx, ngy = toGridCoords(newX, newY)
                local isValid = true

                -- Check neighbors in the surrounding 3x3 grid cells
                for ix = -1, 1 do
                    for iy = -1, 1 do
                        local neighbor = getGridPoint(ngx + ix, ngy + iy)
                        if neighbor then
                            local dx = neighbor.x - newX
                            local dy = neighbor.y - newY
                            -- If distance is less than minDistance, it's too close
                            if math.sqrt(dx*dx + dy*dy) < minDistance then
                                isValid = false
                                break
                            end
                        end
                    end
                    if not isValid then break end
                end

                -- If valid, add to lists
                if isValid then
                    local newPoint = { x = newX, y = newY }
                    table.insert(points, newPoint)
                    setGridPoint(ngx, ngy, newPoint)
                    table.insert(activeList, newPoint)
                    found = true
                    break -- Found a valid point for this parent, stop trying
                end
            end
        end

        -- If we couldn't find a point after k tries, remove the parent from active list
        if not found then
            table.remove(activeList, randomIndex)
        end
    end

    return points
end

-- --- Usage Example ---
local w, h = 256, 256 -- 500, 500
local minDist = 16 -- 20
local noisePoints = generateBlueNoise(w, h, minDist, 30)

print("Generated " .. #noisePoints .. " blue noise points.")
for _, v in ipairs(noisePoints) do
--	print(v.x, v.y)
	local pixel = Pixel.new(math.random(0xffffff), 1, 4, 4)
	pixel:setPosition(v.x, v.y)
	stage:addChild(pixel)
end

3D Blue Noise points @ai

Generating 3D Blue Noise usually involves creating a "Poisson Disk Sampling" of points. This ensures that points are randomly distributed but evenly spaced (no two points are too close), which prevents clumping and creates that desirable "blue" frequency spectrum.

Here is a clean, efficient implementation in Lua using the Best Candidate Algorithm with a Spatial Hash Grid for optimization.

-- 3D Blue Noise points ai generated (https://chat.z.ai/c/efa8ba11-749f-4460-9342-b0928a65f40f)

local math = math
local random = math.random

--- Generate 3D Blue Noise points
--- @param count number: The number of points to generate
--- @param minDist number: The minimum distance between points (radius)
--- @param width number: Width of the volume
--- @param height number: Height of the volume
--- @param depth number: Depth of the volume
--- @return table: Array of points {x, y, z}
local function generate3DBlueNoise(count, minDist, width, height, depth)
    local points = {}
    local grid = {}
    
    -- Optimization: Cell size determines the resolution of our spatial grid.
    -- If points are at least minDist apart, we only need to check adjacent cells.
    local cellSize = minDist / 1.414 -- Slightly smaller to be safe
    local cellSizeInv = 1 / cellSize

    -- Helper: Get a unique key for a grid cell
    local function getGridKey(x, y, z)
        local gx = math.floor(x * cellSizeInv)
        local gy = math.floor(y * cellSizeInv)
        local gz = math.floor(z * cellSizeInv)
        return string.format("%d|%d|%d", gx, gy, gz)
    end

    -- Helper: Check if a point is valid (not too close to existing points)
    local function isValid(p)
        local gx = math.floor(p.x * cellSizeInv)
        local gy = math.floor(p.y * cellSizeInv)
        local gz = math.floor(p.z * cellSizeInv)
        
        -- Check the 3x3x3 neighborhood around the candidate
        for ix = -1, 1 do
            for iy = -1, 1 do
                for iz = -1, 1 do
                    local key = string.format("%d|%d|%d", gx + ix, gy + iy, gz + iz)
                    local existing = grid[key]
                    
                    if existing then
                        local dx = p.x - existing.x
                        local dy = p.y - existing.y
                        local dz = p.z - existing.z
                        
                        -- Optimization: compare squared distance to avoid square root
                        if (dx*dx + dy*dy + dz*dz) < (minDist * minDist) then
                            return false 
                        end
                    end
                end
            end
        end
        return true
    end

    -- Helper: Find approximate distance to nearest existing point
    -- (Used to pick the "best" candidate among the random tries)
    local function getNearestDist(p)
        local closestSq = minDist * minDist -- Initialize to max allowed
        
        local gx = math.floor(p.x * cellSizeInv)
        local gy = math.floor(p.y * cellSizeInv)
        local gz = math.floor(p.z * cellSizeInv)

        for ix = -1, 1 do
            for iy = -1, 1 do
                for iz = -1, 1 do
                    local key = string.format("%d|%d|%d", gx + ix, gy + iy, gz + iz)
                    local existing = grid[key]
                    if existing then
                        local dx = p.x - existing.x
                        local dy = p.y - existing.y
                        local dz = p.z - existing.z
                        local dSq = dx*dx + dy*dy + dz*dz
                        if dSq < closestSq then
                            closestSq = dSq
                        end
                    end
                end
            end
        end
        return closestSq
    end

    -- 1. Add the first point completely randomly
    local firstPoint = {
        x = random() * width,
        y = random() * height,
        z = random() * depth
    }
    table.insert(points, firstPoint)
    grid[getGridKey(firstPoint.x, firstPoint.y, firstPoint.z)] = firstPoint

    -- 2. Generate subsequent points
    local k = 30 -- The number of candidates to try per point (Standard is 30)
    
    for i = 2, count do
        local bestCandidate = nil
        local maxDistToNeighbor = -1

        -- Try 'k' times to find a valid point
        for attempt = 1, k do
            local candidate = {
                x = random() * width,
                y = random() * height,
                z = random() * depth
            }

            if isValid(candidate) then
                -- Among valid candidates, pick the one furthest from others
                local d = getNearestDist(candidate)
                if d > maxDistToNeighbor then
                    maxDistToNeighbor = d
                    bestCandidate = candidate
                end
            end
        end

        if bestCandidate then
            table.insert(points, bestCandidate)
            grid[getGridKey(bestCandidate.x, bestCandidate.y, bestCandidate.z)] = bestCandidate
        else
            -- If we fail to find a point after 'k' tries, the volume might be full.
            -- We can stop early or just print a warning.
            print("Warning: Could not place point " .. i .. ". Density limit reached?")
            break
        end
    end

    return points
end

-- ==========================================
-- EXAMPLE USAGE
-- ==========================================

math.randomseed(os.time()) -- Don't forget to seed!

-- Generate 500 points in a 100x100x100 cube
-- Points must be at least 5 units apart
local noisePoints = generate3DBlueNoise(500, 5.0, 100, 100, 100)

print("Generated " .. #noisePoints .. " points.")

-- Print first 5 points to console
for i = 1, 5 do
    local p = noisePoints[i]
    print(string.format("Point %d: x=%.2f, y=%.2f, z=%.2f", i, p.x, p.y, p.z))
end

FILES

Get Files in Folder @xxx

note: requires the Lfs plugin

Gets all the files in a folder matching a file extension and store them in a list.

local myos = application:getDeviceInfo()
local iswin32 = if myos == "win32" then true else false
--print(iswin32)

function getFilesInFolder(xpath, xlist)
	local lfs = require "lfs"
	for file in lfs.dir(xpath) do
		if file ~= "." and file ~= ".." then
			if file:match(".png$") then -- choose your file extension here
				if iswin32 then
					local f = xpath.."\\"..file
--					print(f)
					xlist[#xlist + 1] = f:gsub("/", "\\")
				else
					local f = xpath.."/"..file
--					print(f)
					xlist[#xlist + 1] = f:gsub("\\", "/")
				end
			end
		end
	end
	lfs = nil
end

Demo code using ButtonMonster

local myos = application:getDeviceInfo()
local iswin32 = if myos == "win32" then true else false
--print(iswin32)

local folderimages = {} -- list all images in user selected folder
local images = {} -- list all images with valid extension
local btnimagefolder = ButtonMonster.new({
	pixelcolorup=0xffaa00, text="import images", textcolorup=0xaa0000, textscalex=3,
	isautoscale=true,
})
btnimagefolder:setPosition(16, 16)
stage:addChild(btnimagefolder)
btnimagefolder:addEventListener("clicked", function()
	local path
	if iswin32 then path = application:get("openDirectoryDialog", "Select Folder", "C:\\tmp\\")
	else path = application:get("openDirectoryDialog", "Select Folder", "C:/tmp/")
	end -- print(path)
	if path then getFilesInFolder(path, folderimages) end
	if folderimages and #folderimages > 0 then
		for i = 1, #folderimages do
			images[i] = Bitmap.new(Texture.new(folderimages[i]), true)
			images[i]:setAnchorPoint(0.5, 1)
			images[i]:setPosition(8, 8)
			-- ...
		end
	end
end)

Xml Parser @Alexander Makeev

-----------------------------------------------------------------------------------------
-- LUA only XmlParser from Alexander Makeev
-----------------------------------------------------------------------------------------
XmlParser = {}

function XmlParser:ToXmlString(value)
	value = string.gsub(value, "&", "&amp;")		-- '&' -> "&amp;"
	value = string.gsub(value, "<", "&lt;")		-- '<' -> "&lt;"
	value = string.gsub(value, ">", "&gt;")		-- '>' -> "&gt;"
	--value = string.gsub(value, "'", "&apos;")	-- '\'' -> "&apos;"
	value = string.gsub(value, "\"", "&quot;")	-- '"' -> "&quot;"
	-- replace non printable char -> "&#xD;"
	value = string.gsub(value, "([^%w%&%;%p%\t% ])", function(c)
		return string.format("&#x%X;", string.byte(c))
		--return string.format("&#x%02X;", string.byte(c))
		--return string.format("&#%02d;", string.byte(c))
	end)
	return value
end

function XmlParser:FromXmlString(value)
	value = string.gsub(value, "&#x([%x]+)%;", function(h)
		return string.char(tonumber(h,16))
	end)
	value = string.gsub(value, "&#([0-9]+)%;", function(h)
		return string.char(tonumber(h,10))
	end)
	value = string.gsub(value, "&quot;", "\"")
	value = string.gsub(value, "&apos;", "'")
	value = string.gsub(value, "&gt;", ">")
	value = string.gsub(value, "&lt;", "<")
	value = string.gsub(value, "&amp;", "&")
	return value
end
   
function XmlParser:ParseArgs(s)
	local arg = {}
	string.gsub(s, "(%w+)=([\"'])(.-)%2", function(w, _, a)
		arg[w] = self:FromXmlString(a)
	end)
	return arg
end

function XmlParser:ParseXmlText(xmlText)
	local stack = {}
	local top = { Name=nil, Value=nil, Attributes={}, ChildNodes={} }
	table.insert(stack, top)
	local ni, c, label, xarg, empty
	local i, j = 1, 1
	while true do
		ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w:]+)(.-)(%/?)>", i)
		if not ni then break end
		local text = string.sub(xmlText, i, ni-1)
		if not string.find(text, "^%s*$") then
			top.Value=(top.Value or "")..self:FromXmlString(text)
		end
		if empty == "/" then -- empty element tag
			table.insert(top.ChildNodes,
				{
					Name=label, Value=nil, Attributes=self:ParseArgs(xarg), ChildNodes={}
				}
			)
		elseif c == "" then -- start tag
			top = { Name=label, Value=nil, Attributes=self:ParseArgs(xarg), ChildNodes={} }
			table.insert(stack, top) -- new level
			--log("openTag ="..top.Name)
		else -- end tag
			local toclose = table.remove(stack) -- remove top
			--log("closeTag="..toclose.Name)
			top = stack[#stack]
			if #stack < 1 then
				error("XmlParser: nothing to close with "..label)
			end
			if toclose.Name ~= label then
				error("XmlParser: trying to close "..toclose.Name.." with "..label)
			end
			table.insert(top.ChildNodes, toclose)
		end
		i = j + 1
	end
	local text = string.sub(xmlText, i)
	if not string.find(text, "^%s*$") then
		stack[#stack].Value=(stack[#stack].Value or "")..self:FromXmlString(text)
	end
	if #stack > 1 then
		error("XmlParser: unclosed "..stack[stack.n].Name)
	end
	return stack[1].ChildNodes[1]
end

function XmlParser:ParseXmlFile(xmlFileName)
	local hFile, err = io.open(xmlFileName,"r")
	if (not err) then
		local xmlText=hFile:read("*a") -- read file content
		io.close(hFile)
		return self:ParseXmlText(xmlText),nil
	else
		return nil, err
	end
end

--[[
-- Usage:
-- parse xml
local xml = XmlParser:ParseXmlText(myxmlfile)
]]

TABLE

Var Dump @xxx

function vardump(value, depth, key)
	local linePrefix = ""
	local spaces = ""

	if key ~= nil then
		linePrefix = "["..key.."] = "
	end

	if depth == nil then
		depth = 0
	else
		depth = depth + 1
		for i = 1, depth do
			spaces = spaces .. "  "
		end
	end

	if type(value) == 'table' then
		local mTable = getmetatable(value)
		if mTable == nil then
			print(spaces ..linePrefix.."(table) ")
		else
			print(spaces .."(metatable) ")
			value = mTable
		end		
		for tableKey, tableValue in pairs(value) do
			vardump(tableValue, depth, tableKey)
		end
	elseif type(value) == 'function' or
		type(value)	== 'thread' or
		type(value)	== 'userdata' or
		value == nil
		then
		print(spaces..tostring(value))
	else
		print(spaces..linePrefix.."("..type(value)..") "..tostring(value))
	end
end

--[[
Usage:
foo="Hello world"
vardump(foo)
vardump( { print, 12, nil, io.stdin, math } )

foo = {
	"zero",1,2,3,{1,{1,2,3,4,{1,2,{1,"cool",2},4},6},3,vardump,5,6},          5,{Mary=10, Paul="10"},"last value"
}
vardump(foo)
--]]




More to come God's willing...