-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxml.lua
More file actions
140 lines (129 loc) · 4.11 KB
/
Copy pathxml.lua
File metadata and controls
140 lines (129 loc) · 4.11 KB
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
local xml = {}
local function decode_entities(value)
return (value:gsub("<", "<")
:gsub(">", ">")
:gsub("&", "&")
:gsub(""", '"')
:gsub("'", "'"))
end
local function parse_attributes(attr_str)
local attrs = {}
for name, value in attr_str:gmatch('([%w_-]+)%s*=%s*"([^"]*)"') do
attrs[name] = decode_entities(value)
end
for name, value in attr_str:gmatch("([%w_-]+)%s*=%s*'([^']*)'") do
attrs[name] = decode_entities(value)
end
return attrs
end
local function trim(s)
return s:match("^%s*(.-)%s*$")
end
local function find_tag_end(text, start_pos)
local quote = nil
local i = start_pos + 1
while i <= #text do
local c = text:sub(i, i)
if quote then
if c == quote then
quote = nil
end
elseif c == '"' or c == "'" then
quote = c
elseif c == ">" then
return i
end
i = i + 1
end
return nil
end
function xml.parse(text, handlers)
handlers = handlers or {}
local pos = 1
local len = #text
while pos <= len do
local start_pos = text:find("<", pos)
if not start_pos then break end
if start_pos > pos then
local content = trim(text:sub(pos, start_pos - 1))
if content ~= "" and handlers.text then
handlers.text(decode_entities(content))
end
end
if text:sub(start_pos, start_pos + 3) == "<!--" then
local end_pos = text:find("-->", start_pos + 4)
if end_pos then
pos = end_pos + 3
else
break
end
elseif text:sub(start_pos, start_pos + 1) == "<?" then
local end_pos = text:find("?>", start_pos + 2)
if end_pos then
pos = end_pos + 2
else
break
end
elseif text:sub(start_pos, start_pos + 8) == "<!DOCTYPE" then
local end_pos = text:find(">", start_pos + 9)
if end_pos then
pos = end_pos + 1
else
break
end
elseif text:sub(start_pos, start_pos + 8) == "<![CDATA[" then
local end_pos = text:find("]]>", start_pos + 9)
if end_pos then
local cdata = text:sub(start_pos + 9, end_pos - 1)
if handlers.text then
handlers.text(cdata)
end
pos = end_pos + 3
else
break
end
elseif text:sub(start_pos + 1, start_pos + 1) == "/" then
local end_pos = find_tag_end(text, start_pos)
if end_pos then
local tag = trim(text:sub(start_pos + 2, end_pos - 1))
if handlers.end_element then
handlers.end_element(tag)
end
pos = end_pos + 1
else
break
end
else
local end_pos = find_tag_end(text, start_pos)
if end_pos then
local tag_content = text:sub(start_pos + 1, end_pos - 1)
local self_closing = tag_content:sub(-1) == "/"
if self_closing then
tag_content = tag_content:sub(1, -2)
end
local tag, attr_str = tag_content:match("^([%w_:-]+)%s*(.*)")
if tag then
local attrs = parse_attributes(attr_str)
if handlers.start_element then
handlers.start_element(tag, attrs)
end
if self_closing and handlers.end_element then
handlers.end_element(tag)
end
end
pos = end_pos + 1
else
break
end
end
end
end
function xml.parse_file(path, handlers)
local f = io.open(path, "r")
if not f then return nil, "Could not open file: " .. path end
local content = f:read("*a")
f:close()
xml.parse(content, handlers)
return true
end
return xml