1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
-- originally from http://lua-users.org/wiki/LuaXml
-- modified by me a bit
local function parseargs(s)
local arg = {}
string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function (w, _, a)
arg[w] = a
end)
return arg
end
local function tag(x)
return setmetatable(x, {__call=function(x, label)
-- search for child with given label
for _,c in ipairs(x) do
if c.label == label then
return c
end
end
return nil
end})
end
local psingle
local function pmulti(s, i, parent)
::again::
local nexti, child = psingle(s, i)
if child.close and child.label == parent.label then
return nexti, parent
else
table.insert(parent, child)
i = nexti
goto again
end
end
psingle = function(s, i)
i = i or 1
local ts,j,c,label,xarg,empty = s:find("<(%/?)([%w:]+)(.-)(%/?)>", i)
if not ts then
local rest = s:sub(i)
if rest:find("<",i) then
error('ill formed (eof?)')
elseif #rest == 0 then
error('empty string')
else
return i+#rest, rest
end
end
local nexti = j+1
local pretext = s:sub(i, ts-1)
if not pretext:find("^%s*$") then -- not entirely whitespace
return ts, pretext
end
if empty == "/" then
return nexti, tag{label=label, xarg=parseargs(xarg), empty=true}
elseif c == "" then -- start tag
return pmulti(s, nexti, tag{label=label, xarg=parseargs(xarg)})
else -- end tag
return nexti, tag{label=label, close=true}
end
end
local wrap
do
local _, cqa = pcall(require, 'cqueues.auxlib')
wrap = cqa and cqa.wrap or coroutine.wrap
end
local function stanzae(getmore)
return wrap(function()
local buf = ''
while true do
local ok, ni, el = pcall(psingle, buf)
if ok then
coroutine.yield(el)
buf = buf:sub(ni)
else
local more = getmore()
buf = buf .. more
end
end
end)
end
local entity_escapes = {
["<"]="<",
[">"]=">",
["&"]="&",
['"']=""",
["'"]="'"
}
local function escape(s)
return s:gsub("[<>&'\"]",entity_escapes)
end
local safestr_mt = {name='SAFESTR', __tostring=function(x) return x.s end}
local function safestr(s) return setmetatable({s=s}, safestr_mt) end
local function _xmlify(x)
if getmetatable(x) == safestr_mt then
return x
elseif type(x) == 'string' then
return safestr(escape(x))
elseif type(x) == 'table' then -- must be a tag
local function argstr(t)
local argstring = ''
for k,v in pairs(t) do
argstring = argstring .. (" %s='%s'"):format(k, _xmlify(v))
end
return argstring
end
if x.empty then
return safestr(("<%s%s/>"):format(x.label, argstr(x.xarg)))
else
local open = ("<%s%s>"):format(x.label, argstr(x.xarg))
local close = ("</%s>"):format(x.label)
local children = {}
for _,child in ipairs(x) do
table.insert(children, tostring(_xmlify(child)))
end
return safestr(open .. table.concat(children, '') .. close)
end
end
end
local function xmlify(...)
local n = select('#',...)
if n <= 1 then return _xmlify(...) end
local out = {}
for i = 1, n do
out[i] = tostring(_xmlify(select(i, ...)))
end
return table.concat(out, '\n')
end
-- dsl for input to xmlify. doesn't do escaping or anything.
-- THERE IS NO ESCAPE
local X = setmetatable({}, {__index=function(_,label)
return function(tab)
local out = {}
local xarg = {}
local empty = true
for k, v in pairs(tab) do
if type(k) == 'number' then -- child
empty = nil
out[k] = v
elseif type(k) == 'string' then -- attrib
xarg[k] = v
end
end
out.label = label
out.xarg = xarg
out.empty = empty
return out
end
end})
return { psingle = psingle, stanzae = stanzae, wrap=wrap, xmlify=xmlify, X=X }
|