| 1 |
-------------------------------------------------------------------------------- |
|---|
| 2 |
-- Title: XML.lua |
|---|
| 3 |
-- Description: Like a square peg in a round hole |
|---|
| 4 |
-- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ |
|---|
| 5 |
-- Creation Date: January 30, 2007 |
|---|
| 6 |
-- Legal: Copyright (C) 2007 Raphaël Szwarc |
|---|
| 7 |
-- Under the terms of the MIT License |
|---|
| 8 |
-- http://www.opensource.org/licenses/mit-license.html |
|---|
| 9 |
-------------------------------------------------------------------------------- |
|---|
| 10 |
|
|---|
| 11 |
-- Follows David Sklar's BadgerFish convention for translating an XML document into a Lua table. |
|---|
| 12 |
-- |
|---|
| 13 |
-- http://badgerfish.ning.com/ |
|---|
| 14 |
-- |
|---|
| 15 |
-- 1. Element names become table keys. |
|---|
| 16 |
-- |
|---|
| 17 |
-- 2. Text content of elements goes in the '$' key of a table. |
|---|
| 18 |
-- <alice>bob</alice> |
|---|
| 19 |
-- becomes |
|---|
| 20 |
-- { alice = { [ '$' ] = 'bob' } } |
|---|
| 21 |
-- |
|---|
| 22 |
-- 3. Nested elements become nested tables. |
|---|
| 23 |
-- <alice><bob>charlie</bob><david>edgar</david></alice> |
|---|
| 24 |
-- becomes |
|---|
| 25 |
-- { alice = { bob = { [ '$' ] = 'charlie' }, david = { [ '$' ] = 'edgar' } } } |
|---|
| 26 |
-- |
|---|
| 27 |
-- 4. Multiple elements at the same level become list elements. |
|---|
| 28 |
-- <alice><bob>charlie</bob><bob>david</bob></alice> |
|---|
| 29 |
-- becomes |
|---|
| 30 |
-- { alice = { bob = { { [ '$' ] = 'charlie' }, { [ '$' ] = 'david' } } } } |
|---|
| 31 |
-- |
|---|
| 32 |
-- 5. Attributes go in keys whose names begin with '@'. |
|---|
| 33 |
-- <alice charlie="david">bob</alice> |
|---|
| 34 |
-- becomes |
|---|
| 35 |
-- { alice: { [ '$' ] = 'bob', [ '@charlie' ] = 'david' } } |
|---|
| 36 |
-- |
|---|
| 37 |
|
|---|
| 38 |
-- import dependencies |
|---|
| 39 |
local string = require( 'string' ) |
|---|
| 40 |
|
|---|
| 41 |
local assert = assert |
|---|
| 42 |
local getmetatable = getmetatable |
|---|
| 43 |
local pairs = pairs |
|---|
| 44 |
local setmetatable = setmetatable |
|---|
| 45 |
local tonumber = tonumber |
|---|
| 46 |
local tostring = tostring |
|---|
| 47 |
local type = type |
|---|
| 48 |
local unpack = unpack |
|---|
| 49 |
|
|---|
| 50 |
-------------------------------------------------------------------------------- |
|---|
| 51 |
-- XML |
|---|
| 52 |
-------------------------------------------------------------------------------- |
|---|
| 53 |
|
|---|
| 54 |
module( 'XML' ) |
|---|
| 55 |
_VERSION = '1.0' |
|---|
| 56 |
|
|---|
| 57 |
local self = setmetatable( _M, {} ) |
|---|
| 58 |
local meta = getmetatable( self ) |
|---|
| 59 |
|
|---|
| 60 |
local function Encode( aTable ) |
|---|
| 61 |
-- to do |
|---|
| 62 |
end |
|---|
| 63 |
|
|---|
| 64 |
local function GFind( aString, aPattern, anIndex ) |
|---|
| 65 |
local anIndex = anIndex or 1 |
|---|
| 66 |
|
|---|
| 67 |
return function() |
|---|
| 68 |
local aResult = { aString:find( aPattern, anIndex ) } |
|---|
| 69 |
|
|---|
| 70 |
if #aResult > 0 then |
|---|
| 71 |
anIndex = aResult[ 2 ] + 1 |
|---|
| 72 |
|
|---|
| 73 |
return unpack( aResult ) |
|---|
| 74 |
end |
|---|
| 75 |
end |
|---|
| 76 |
end |
|---|
| 77 |
|
|---|
| 78 |
local function Trim( aValue ) |
|---|
| 79 |
return ( aValue:gsub( '^[%c%s]+', '' ):gsub( '[%s%c]+$', '' ) ) |
|---|
| 80 |
end |
|---|
| 81 |
|
|---|
| 82 |
local entities = { lt = '<', gt = '>', amp = '&', apos = '\'', quot = '"' } |
|---|
| 83 |
|
|---|
| 84 |
local function DecodeEntity( aType, anEntity ) |
|---|
| 85 |
if aType == '#' then |
|---|
| 86 |
local aCode = tonumber( ( anEntity:gsub( '^x', '0x' ) ) ) |
|---|
| 87 |
|
|---|
| 88 |
if aCode ~= nil and aCode < 256 then |
|---|
| 89 |
return string.char( aCode ) |
|---|
| 90 |
end |
|---|
| 91 |
else |
|---|
| 92 |
local aValue = entities[ anEntity ] |
|---|
| 93 |
|
|---|
| 94 |
if aValue ~= nil then |
|---|
| 95 |
return aValue |
|---|
| 96 |
end |
|---|
| 97 |
end |
|---|
| 98 |
|
|---|
| 99 |
return ( '&%s%s;' ):format( aType, anEntity ) |
|---|
| 100 |
end |
|---|
| 101 |
|
|---|
| 102 |
local function DecodeValue( aValue ) |
|---|
| 103 |
return Trim( aValue:gsub( '&(#?)(%w+);', DecodeEntity ) ) |
|---|
| 104 |
end |
|---|
| 105 |
|
|---|
| 106 |
local function Name( aValue ) |
|---|
| 107 |
if not aValue:find( '^xml' ) then |
|---|
| 108 |
local anIndex = aValue:find( ':', 1, true ) |
|---|
| 109 |
|
|---|
| 110 |
if anIndex then |
|---|
| 111 |
aValue = aValue:sub( anIndex + 1 ) |
|---|
| 112 |
end |
|---|
| 113 |
end |
|---|
| 114 |
|
|---|
| 115 |
return aValue |
|---|
| 116 |
end |
|---|
| 117 |
|
|---|
| 118 |
local function Text( aContent, aStart, anEnd ) |
|---|
| 119 |
local aText = aContent:sub( aStart, anEnd ) |
|---|
| 120 |
|
|---|
| 121 |
if not aText:find( '^%s*$' ) then |
|---|
| 122 |
return DecodeValue( aText ) |
|---|
| 123 |
end |
|---|
| 124 |
end |
|---|
| 125 |
|
|---|
| 126 |
local function Node( aValue ) |
|---|
| 127 |
local aNode = {} |
|---|
| 128 |
local aDecoder = function( aName, _, aValue ) |
|---|
| 129 |
aName = Name( aName ) |
|---|
| 130 |
aName = '@' .. aName |
|---|
| 131 |
aNode[ aName ] = DecodeValue( aValue ) |
|---|
| 132 |
end |
|---|
| 133 |
|
|---|
| 134 |
aValue:gsub( '([%w%.%-%_%:]+)=([\"\'])(.-)%2', aDecoder ) |
|---|
| 135 |
|
|---|
| 136 |
return aNode |
|---|
| 137 |
end |
|---|
| 138 |
|
|---|
| 139 |
local function Merge( aNode ) |
|---|
| 140 |
for aKey, aValue in pairs( aNode ) do |
|---|
| 141 |
if type( aValue ) == 'table' and #aValue == 1 then |
|---|
| 142 |
aNode[ aKey ] = aValue[ 1 ] |
|---|
| 143 |
end |
|---|
| 144 |
end |
|---|
| 145 |
|
|---|
| 146 |
return aNode |
|---|
| 147 |
end |
|---|
| 148 |
|
|---|
| 149 |
local function Decode( aString ) |
|---|
| 150 |
local aStack = {} |
|---|
| 151 |
local anIndex = 1 |
|---|
| 152 |
|
|---|
| 153 |
aStack[ #aStack + 1 ] = {} |
|---|
| 154 |
|
|---|
| 155 |
for aStart, anEnd, isClose, aName, aContent, isEmpty in GFind( aString, '<(%/?)([%w%.%-%_%:]+)(.-)(%/?)>' ) do |
|---|
| 156 |
if isClose == '' then |
|---|
| 157 |
local aName = Name( aName ) |
|---|
| 158 |
local aParent = assert( aStack[ #aStack ] ) |
|---|
| 159 |
local someNodes = aParent[ aName ] or {} |
|---|
| 160 |
local aNode = Node( aContent ) |
|---|
| 161 |
|
|---|
| 162 |
someNodes[ #someNodes + 1 ] = aNode |
|---|
| 163 |
aParent[ aName ] = someNodes |
|---|
| 164 |
|
|---|
| 165 |
if isEmpty == '' then |
|---|
| 166 |
aStack[ #aStack + 1 ] = aNode |
|---|
| 167 |
end |
|---|
| 168 |
else |
|---|
| 169 |
local aNode = assert( aStack[ #aStack ] ) |
|---|
| 170 |
local aText = Text( aString, anIndex, aStart - 1 ) |
|---|
| 171 |
|
|---|
| 172 |
aNode[ '$' ] = aText |
|---|
| 173 |
Merge( aNode ) |
|---|
| 174 |
|
|---|
| 175 |
aStack[ #aStack ] = nil |
|---|
| 176 |
end |
|---|
| 177 |
|
|---|
| 178 |
anIndex = anEnd + 1 |
|---|
| 179 |
end |
|---|
| 180 |
|
|---|
| 181 |
return Merge( assert( aStack[ 1 ] ) ) |
|---|
| 182 |
end |
|---|
| 183 |
|
|---|
| 184 |
function meta:__call( anObject ) |
|---|
| 185 |
if type( anObject ) == 'table' then |
|---|
| 186 |
return Encode( anObject ) |
|---|
| 187 |
end |
|---|
| 188 |
|
|---|
| 189 |
return Decode( tostring( anObject ) ) |
|---|
| 190 |
end |
|---|