Skip to content

Commit

Permalink
Fixes #5: namespace prefixes are applied on the element/attribute dec…
Browse files Browse the repository at this point in the history
…laring them
  • Loading branch information
Gavin Kistner committed Apr 18, 2014
1 parent 0ea144d commit f39193a
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 25 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ local myxml = io.open('my.xml'):read('*all')

-- Specify as many/few of these as you like
parser = SLAXML:parser{
startElement = function(name,nsURI) end, -- When "<foo" or <x:foo is seen
attribute = function(name,value,nsURI) end, -- attribute found on current element
closeElement = function(name,nsURI) end, -- When "</foo>" or </x:foo> or "/>" is seen
text = function(text) end, -- text and CDATA nodes
comment = function(content) end, -- comments
pi = function(target,content) end, -- processing instructions e.g. "<?yes mon?>"
startElement = function(name,nsURI,nsPrefix) end, -- When "<foo" or <x:foo is seen
attribute = function(name,value,nsURI,nsPrefix) end, -- attribute found on current element
closeElement = function(name,nsURI) end, -- When "</foo>" or </x:foo> or "/>" is seen
text = function(text) end, -- text and CDATA nodes
comment = function(content) end, -- comments
pi = function(target,content) end, -- processing instructions e.g. "<?yes mon?>"
}

-- Ignore whitespace-only text nodes and strip leading/trailing whitespace from text
Expand Down Expand Up @@ -86,8 +86,8 @@ The returned table is a 'document' comprised of tables for elements, attributes,
* <strong>`someAttr.type`</strong> : the string `"attribute"`
* <strong>`someAttr.name`</strong> : the name of the attribute (without any namespace prefix)
* <strong>`someAttr.value`</strong> : the string value of the attribute (with XML and numeric entities unescaped)
* <strong>`someEl.nsURI`</strong> : the namespace URI for the attribute; `nil` if no namespace is applied
* <strong>`someEl.parent`</strong> : reference to the the parent element table
* <strong>`someAttr.nsURI`</strong> : the namespace URI for the attribute; `nil` if no namespace is applied
* <strong>`someAttr.parent`</strong> : reference to the the owning element table
* **Text** - for both CDATA and normal text nodes
* <strong>`someText.type`</strong> : the string `"text"`
* <strong>`someText.name`</strong> : the string `"#text"`
Expand Down Expand Up @@ -157,6 +157,10 @@ In this case no table will have a `parent` attribute, elements will not have the

## History

### v0.6 2014-Apr-18
+ Fixes Issue #5 (and more): Namespace prefixes defined on element are now properly applied to the element itself and any attributes using them when the definitions appear later in source than the prefix usage.
+ The streaming parser now supplies the namespace prefix for elements and attributes.

### v0.5.3 2014-Feb-12
+ Fixes Issue #3: The [reserved `xml` prefix](http://www.w3.org/TR/xml-names/#ns-decl) may be used without pre-declaring it. (Thanks David Durkee.)

Expand Down
42 changes: 29 additions & 13 deletions slaxml.lua
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
--[=====================================================================[
v0.5.3 Copyright © 2013 Gavin Kistner <[email protected]>; MIT Licensed
v0.6 Copyright © 2013-2014 Gavin Kistner <[email protected]>; MIT Licensed
See http://github.com/Phrogz/SLAXML for details.
--]=====================================================================]
local SLAXML = {
VERSION = "0.5.3",
VERSION = "0.6",
_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 ""))
startElement = function(name,nsURI,nsPrefix)
io.write("<")
if nsPrefix then io.write(nsPrefix,":") end
io.write(name)
if nsURI then io.write(" (ns='",nsURI,"')") end
print(">")
end,
attribute = function(name,value,nsURI)
print(string.format(" %s=%q%s",name,value,nsURI and (" ("..nsURI..")") or ""))
attribute = function(name,value,nsURI,nsPrefix)
io.write(' ')
if nsPrefix then io.write(nsPrefix,":") end
io.write(name,'=',string.format('%q',value))
if nsURI then io.write(" (ns='",nsURI,"')") end
io.write("\n")
end,
text = function(text)
print(string.format(" text: %q",text))
end,
closeElement = function(name,nsURI)
closeElement = function(name,nsURI,nsPrefix)
print(string.format("</%s>",name))
end,
}
Expand All @@ -42,7 +50,7 @@ function SLAXML:parse(xml,options)
local textStart = 1
local currentElement={}
local currentAttributes={}
local currentAttributeCt
local currentAttributeCt -- manually track length since the table is re-used
local nsStack = {}

local entityMap = { ["lt"]="<", ["gt"]=">", ["amp"]="&", ["quot"]='"', ["apos"]="'" }
Expand Down Expand Up @@ -94,13 +102,14 @@ function SLAXML:parse(xml,options)
anyElement = true
first, last, match1 = find( xml, '^<([%a_][%w_.-]*)', pos )
if first then
currentElement[2] = nil
currentElement[2] = nil -- reset the nsURI, since this table is re-used
currentElement[3] = nil -- reset the nsPrefix, since this table is re-used
finishText()
pos = last+1
first,last,match2 = find(xml, '^:([%a_][%w_.-]*)', pos )
if first then
currentElement[1] = match2
currentElement[2] = nsForPrefix(match1)
currentElement[3] = match1 -- Save the prefix for later resolution
match1 = match2
pos = last+1
else
Expand Down Expand Up @@ -137,12 +146,12 @@ function SLAXML:parse(xml,options)
nsStack[#nsStack][name] = match2
else
currentAttribute[1] = name
currentAttribute[3] = nsForPrefix(prefix)
currentAttribute[4] = prefix
end
else
if match1=='xmlns' then
nsStack[#nsStack]['!'] = match2
currentElement[2] = match2
currentElement[2] = match2
end
end
currentAttributeCt = currentAttributeCt + 1
Expand All @@ -169,8 +178,15 @@ function SLAXML:parse(xml,options)
pos = last+1
textStart = pos

-- Resolve namespace prefixes AFTER all new/redefined prefixes have been parsed
if currentElement[3] then currentElement[2] = nsForPrefix(currentElement[3]) end
if self._call.startElement then self._call.startElement(unpack(currentElement)) end
if self._call.attribute then for i=1,currentAttributeCt do self._call.attribute(unpack(currentAttributes[i])) end end
if self._call.attribute then
for i=1,currentAttributeCt do
if currentAttributes[i][4] then currentAttributes[i][3] = nsForPrefix(currentAttributes[i][4]) end
self._call.attribute(unpack(currentAttributes[i]))
end
end

if match1=="/" then
pop(nsStack)
Expand Down
13 changes: 12 additions & 1 deletion test/files/namespace_declare_and_use.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
<r>
<z:el xmlns:z="zoo">declaring and using a namespace on the same element is legal</z:el>
<cat xmlns="cat" xmlns:a="dog">
<cat />
<a:dog>
<cat />
<a:hog a:hog="yes" xmlns:a="hog" xmlns:b="bog" b:bog="yes">
<a:hog />
<b:bog />
</a:hog>
<a:dog />
</a:dog>
</cat>
<c:cog xmlns:c="cog" />
</r>
35 changes: 32 additions & 3 deletions test/test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,38 @@ end

function test_xml_namespace_immediate_use()
local doc = SLAXML:dom(XML['namespace_declare_and_use'])
assertEqual(#doc.root.el,1)
local z = doc.root.el[1]
assertEqual(z.nsURI,'zoo')
local cat1 = doc.root.el[1]
assertEqual(cat1.name,'cat')
assertEqual(cat1.nsURI,'cat')
local cat2 = cat1.el[1]
assertEqual(cat2.name, 'cat')
assertEqual(cat2.nsURI,'cat')
local dog1 = cat1.el[2]
assertEqual(dog1.name, 'dog')
assertEqual(dog1.nsURI,'dog')
local cat3 = dog1.el[1]
assertEqual(cat3.name, 'cat')
assertEqual(cat3.nsURI,'cat')
local hog1 = dog1.el[2]
assertEqual(hog1.name, 'hog')
assertEqual(hog1.nsURI,'hog')
for _,attr in ipairs(hog1.attr) do
if attr.value=='yes' then
assertEqual(attr.nsURI,attr.name)
end
end
local hog2 = hog1.el[1]
assertEqual(hog2.name, 'hog')
assertEqual(hog2.nsURI,'hog')
local bog1 = hog1.el[2]
assertEqual(bog1.name, 'bog')
assertEqual(bog1.nsURI,'bog')
local dog2 = dog1.el[3]
assertEqual(dog2.name, 'dog')
assertEqual(dog2.nsURI,'dog')
local cog2 = doc.root.el[2]
assertEqual(cog2.name, 'cog')
assertEqual(cog2.nsURI,'cog')
end

function test_dom_namespaces()
Expand Down

0 comments on commit f39193a

Please sign in to comment.