mirror of
https://github.com/JamesonHuang/OpenWrt_Luci_Lua.git
synced 2024-11-24 06:10:11 +00:00
222 lines
7.4 KiB
Lua
222 lines
7.4 KiB
Lua
|
local string = require "string"
|
||
|
local table = require "table"
|
||
|
local base = _G
|
||
|
|
||
|
module "slaxml"
|
||
|
|
||
|
local SLAXML = {
|
||
|
VERSION = "0.5.1",
|
||
|
_call = {
|
||
|
pi = function(target,content)
|
||
|
print(string.format("<?%s %s?>",target,content))
|
||
|
end,
|
||
|
comment = function(content)
|
||
|
print(string.format("<!-- %s -->",content))
|
||
|
end,
|
||
|
startElement = function(name,nsURI)
|
||
|
print(string.format("<%s%s>",name,nsURI and (" ("..nsURI..")") or ""))
|
||
|
end,
|
||
|
attribute = function(name,value,nsURI)
|
||
|
print(string.format(" %s=%q%s",name,value,nsURI and (" ("..nsURI..")") or ""))
|
||
|
end,
|
||
|
text = function(text)
|
||
|
print(string.format(" text: %q",text))
|
||
|
end,
|
||
|
closeElement = function(name,nsURI)
|
||
|
print(string.format("</%s>",name))
|
||
|
end,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function SLAXML:parser(callbacks)
|
||
|
return { _call=callbacks or self._call, parse=SLAXML.parse }
|
||
|
end
|
||
|
|
||
|
function SLAXML:parse(xml,options)
|
||
|
if not options then options = { stripWhitespace=false } end
|
||
|
|
||
|
-- Cache references for maximum speed
|
||
|
local find, sub, gsub, char, push, pop = string.find, string.sub, string.gsub, string.char, table.insert, table.remove
|
||
|
local first, last, match1, match2, match3, pos2, nsURI
|
||
|
local pos = 1
|
||
|
local state = "text"
|
||
|
local textStart = 1
|
||
|
local currentElement={}
|
||
|
local currentAttributes={}
|
||
|
local currentAttributeCt
|
||
|
local nsStack = {}
|
||
|
|
||
|
local entityMap = { ["lt"]="<", ["gt"]=">", ["amp"]="&", ["quot"]='"', ["apos"]="'" }
|
||
|
local entitySwap = function(orig,n,s) return entityMap[s] or n=="#" and char(s) or orig end
|
||
|
local function unescape(str) return gsub( str, '(&(#?)([%d%a]+);)', entitySwap ) end
|
||
|
|
||
|
local function finishText()
|
||
|
if first>textStart and self._call.text then
|
||
|
local text = sub(xml,textStart,first-1)
|
||
|
if options.stripWhitespace then
|
||
|
text = gsub(text,'^%s+','')
|
||
|
text = gsub(text,'%s+$','')
|
||
|
if #text==0 then text=nil end
|
||
|
end
|
||
|
if text then self._call.text(unescape(text)) end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function findPI()
|
||
|
first, last, match1, match2 = find( xml, '^<%?([:%a_][:%w_.-]*) ?(.-)%?>', pos )
|
||
|
if first then
|
||
|
finishText()
|
||
|
if self._call.pi then self._call.pi(match1,match2) end
|
||
|
pos = last+1
|
||
|
textStart = pos
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function findComment()
|
||
|
first, last, match1 = find( xml, '^<!%-%-(.-)%-%->', pos )
|
||
|
if first then
|
||
|
finishText()
|
||
|
if self._call.comment then self._call.comment(match1) end
|
||
|
pos = last+1
|
||
|
textStart = pos
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function nsForPrefix(prefix)
|
||
|
for i=#nsStack,1,-1 do if nsStack[i][prefix] then return nsStack[i][prefix] end end
|
||
|
error(("Cannot find namespace for prefix %s"):format(prefix))
|
||
|
end
|
||
|
|
||
|
local function startElement()
|
||
|
first, last, match1 = find( xml, '^<([%a_][%w_.-]*)', pos )
|
||
|
if first then
|
||
|
currentElement[2] = nil
|
||
|
finishText()
|
||
|
pos = last+1
|
||
|
first,last,match2 = find(xml, '^:([%a_][%w_.-]*)', pos )
|
||
|
if first then
|
||
|
currentElement[1] = match2
|
||
|
currentElement[2] = nsForPrefix(match1)
|
||
|
match1 = match2
|
||
|
pos = last+1
|
||
|
else
|
||
|
currentElement[1] = match1
|
||
|
for i=#nsStack,1,-1 do if nsStack[i]['!'] then currentElement[2] = nsStack[i]['!']; break end end
|
||
|
end
|
||
|
currentAttributeCt = 0
|
||
|
push(nsStack,{})
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function findAttribute()
|
||
|
first, last, match1 = find( xml, '^%s+([:%a_][:%w_.-]*)%s*=%s*', pos )
|
||
|
if first then
|
||
|
pos2 = last+1
|
||
|
first, last, match2 = find( xml, '^"([^<"]*)"', pos2 ) -- FIXME: disallow non-entity ampersands
|
||
|
if first then
|
||
|
pos = last+1
|
||
|
match2 = unescape(match2)
|
||
|
else
|
||
|
first, last, match2 = find( xml, "^'([^<']*)'", pos2 ) -- FIXME: disallow non-entity ampersands
|
||
|
if first then
|
||
|
pos = last+1
|
||
|
match2 = unescape(match2)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
if match1 and match2 then
|
||
|
local currentAttribute = {match1,match2}
|
||
|
local prefix,name = string.match(match1,'^([^:]+):([^:]+)$')
|
||
|
if prefix then
|
||
|
if prefix=='xmlns' then
|
||
|
nsStack[#nsStack][name] = match2
|
||
|
else
|
||
|
currentAttribute[1] = name
|
||
|
currentAttribute[3] = nsForPrefix(prefix)
|
||
|
end
|
||
|
else
|
||
|
if match1=='xmlns' then
|
||
|
nsStack[#nsStack]['!'] = match2
|
||
|
currentElement[2] = match2
|
||
|
end
|
||
|
end
|
||
|
currentAttributeCt = currentAttributeCt + 1
|
||
|
currentAttributes[currentAttributeCt] = currentAttribute
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function findCDATA()
|
||
|
first, last, match1 = find( xml, '^<!%[CDATA%[(.-)%]%]>', pos )
|
||
|
if first then
|
||
|
finishText()
|
||
|
if self._call.text then self._call.text(match1) end
|
||
|
pos = last+1
|
||
|
textStart = pos
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function closeElement()
|
||
|
first, last, match1 = find( xml, '^%s*(/?)>', pos )
|
||
|
if first then
|
||
|
state = "text"
|
||
|
pos = last+1
|
||
|
textStart = pos
|
||
|
|
||
|
if self._call.startElement then self._call.startElement(base.unpack(currentElement)) end
|
||
|
if self._call.attribute then
|
||
|
for i=1,currentAttributeCt do self._call.attribute(base.unpack(currentAttributes[i])) end end
|
||
|
|
||
|
if match1=="/" then
|
||
|
pop(nsStack)
|
||
|
if self._call.closeElement then self._call.closeElement(base.unpack(currentElement)) end
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function findElementClose()
|
||
|
first, last, match1, match2 = find( xml, '^</([%a_][%w_.-]*)%s*>', pos )
|
||
|
if first then
|
||
|
nsURI = nil
|
||
|
for i=#nsStack,1,-1 do if nsStack[i]['!'] then nsURI = nsStack[i]['!']; break end end
|
||
|
else
|
||
|
first, last, match2, match1 = find( xml, '^</([%a_][%w_.-]*):([%a_][%w_.-]*)%s*>', pos )
|
||
|
if first then nsURI = nsForPrefix(match2) end
|
||
|
end
|
||
|
if first then
|
||
|
finishText()
|
||
|
if self._call.closeElement then self._call.closeElement(match1,nsURI) end
|
||
|
pos = last+1
|
||
|
textStart = pos
|
||
|
pop(nsStack)
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
while pos<#xml do
|
||
|
if state=="text" then
|
||
|
if not (findPI() or findComment() or findCDATA() or findElementClose()) then
|
||
|
if startElement() then
|
||
|
state = "attributes"
|
||
|
else
|
||
|
first, last = find( xml, '^[^<]+', pos )
|
||
|
pos = (first and last or pos) + 1
|
||
|
end
|
||
|
end
|
||
|
elseif state=="attributes" then
|
||
|
if not findAttribute() then
|
||
|
if not closeElement() then
|
||
|
error("Was in an element and couldn't find attributes or the close.")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return SLAXML
|