root/HTTP/XML.lua

Revision 1131 (checked in by rsz, 7 months ago)

cleanup

Line 
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
Note: See TracBrowser for help on using the browser.