-------------------------------------------------------------------------------- -- Title: XML.lua -- Description: Like a square peg in a round hole -- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ -- Creation Date: January 30, 2007 -- Legal: Copyright (C) 2007 Raphaël Szwarc -- Under the terms of the MIT License -- http://www.opensource.org/licenses/mit-license.html -------------------------------------------------------------------------------- -- Follows David Sklar's BadgerFish convention for translating an XML document into a Lua table. -- -- http://badgerfish.ning.com/ -- -- 1. Element names become table keys. -- -- 2. Text content of elements goes in the '$' key of a table. -- bob -- becomes -- { alice = { [ '$' ] = 'bob' } } -- -- 3. Nested elements become nested tables. -- charlieedgar -- becomes -- { alice = { bob = { [ '$' ] = 'charlie' }, david = { [ '$' ] = 'edgar' } } } -- -- 4. Multiple elements at the same level become list elements. -- charliedavid -- becomes -- { alice = { bob = { { [ '$' ] = 'charlie' }, { [ '$' ] = 'david' } } } } -- -- 5. Attributes go in keys whose names begin with '@'. -- bob -- becomes -- { alice: { [ '$' ] = 'bob', [ '@charlie' ] = 'david' } } -- -- import dependencies local string = require( 'string' ) local assert = assert local getmetatable = getmetatable local pairs = pairs local setmetatable = setmetatable local tonumber = tonumber local tostring = tostring local type = type local unpack = unpack -------------------------------------------------------------------------------- -- XML -------------------------------------------------------------------------------- module( 'XML' ) _VERSION = '1.0' local self = setmetatable( _M, {} ) local meta = getmetatable( self ) local function Encode( aTable ) -- to do end local function GFind( aString, aPattern, anIndex ) local anIndex = anIndex or 1 return function() local aResult = { aString:find( aPattern, anIndex ) } if #aResult > 0 then anIndex = aResult[ 2 ] + 1 return unpack( aResult ) end end end local function Trim( aValue ) return ( aValue:gsub( '^[%c%s]+', '' ):gsub( '[%s%c]+$', '' ) ) end local entities = { lt = '<', gt = '>', amp = '&', apos = '\'', quot = '"' } local function DecodeEntity( aType, anEntity ) if aType == '#' then local aCode = tonumber( ( anEntity:gsub( '^x', '0x' ) ) ) if aCode ~= nil and aCode < 256 then return string.char( aCode ) end else local aValue = entities[ anEntity ] if aValue ~= nil then return aValue end end return ( '&%s%s;' ):format( aType, anEntity ) end local function DecodeValue( aValue ) return Trim( aValue:gsub( '&(#?)(%w+);', DecodeEntity ) ) end local function Name( aValue ) if not aValue:find( '^xml' ) then local anIndex = aValue:find( ':', 1, true ) if anIndex then aValue = aValue:sub( anIndex + 1 ) end end return aValue end local function Text( aContent, aStart, anEnd ) local aText = aContent:sub( aStart, anEnd ) if not aText:find( '^%s*$' ) then return DecodeValue( aText ) end end local function Node( aValue ) local aNode = {} local aDecoder = function( aName, _, aValue ) aName = Name( aName ) aName = '@' .. aName aNode[ aName ] = DecodeValue( aValue ) end aValue:gsub( '([%w%.%-%_%:]+)=([\"\'])(.-)%2', aDecoder ) return aNode end local function Merge( aNode ) for aKey, aValue in pairs( aNode ) do if type( aValue ) == 'table' and #aValue == 1 then aNode[ aKey ] = aValue[ 1 ] end end return aNode end local function Decode( aString ) local aStack = {} local anIndex = 1 aStack[ #aStack + 1 ] = {} for aStart, anEnd, isClose, aName, aContent, isEmpty in GFind( aString, '<(%/?)([%w%.%-%_%:]+)(.-)(%/?)>' ) do if isClose == '' then local aName = Name( aName ) local aParent = assert( aStack[ #aStack ] ) local someNodes = aParent[ aName ] or {} local aNode = Node( aContent ) someNodes[ #someNodes + 1 ] = aNode aParent[ aName ] = someNodes if isEmpty == '' then aStack[ #aStack + 1 ] = aNode end else local aNode = assert( aStack[ #aStack ] ) local aText = Text( aString, anIndex, aStart - 1 ) aNode[ '$' ] = aText Merge( aNode ) aStack[ #aStack ] = nil end anIndex = anEnd + 1 end return Merge( assert( aStack[ 1 ] ) ) end function meta:__call( anObject ) if type( anObject ) == 'table' then return Encode( anObject ) end return Decode( tostring( anObject ) ) end