diff --git a/src/main/scala/com/codecommit/antixml/NodeSeqSAXHandler.scala b/src/main/scala/com/codecommit/antixml/NodeSeqSAXHandler.scala index bdf083a..64c345b 100644 --- a/src/main/scala/com/codecommit/antixml/NodeSeqSAXHandler.scala +++ b/src/main/scala/com/codecommit/antixml/NodeSeqSAXHandler.scala @@ -59,7 +59,13 @@ class NodeSeqSAXHandler extends DefaultHandler2 { } override def startPrefixMapping(prefix: String, namespace: String) { - scopes ::= (scopes.headOption map { _ + (prefix -> namespace) } getOrElse Map()) + // This is an optimization to not generate a new map if the mapping exists + // already. + val parentScope = scopes.headOption getOrElse Map() + scopes ::= (if (parentScope.get(prefix) == Some(namespace)) + parentScope + else + parentScope + (prefix -> namespace) ) } override def endPrefixMapping(prefix: String) { diff --git a/src/main/scala/com/codecommit/antixml/StAXParser.scala b/src/main/scala/com/codecommit/antixml/StAXParser.scala index 10a85d8..7d7697c 100644 --- a/src/main/scala/com/codecommit/antixml/StAXParser.scala +++ b/src/main/scala/com/codecommit/antixml/StAXParser.scala @@ -92,9 +92,13 @@ class StAXParser extends XMLParser { var prefixes = prefixMapping.headOption getOrElse Map() while (i < xmlReader.getNamespaceCount) { val ns = xmlReader.getNamespaceURI(i) - val prefix = xmlReader.getNamespacePrefix(i) - // TODO: Only change if mapping doesn't exists already - prefixes = prefixes + (prefix -> ns) + val rawPrefix = xmlReader.getNamespacePrefix(i) + val prefix = if (rawPrefix != null) rawPrefix else "" + + // To conserve memory, only save prefix if changed + if (prefixes.get(prefix) != Some(ns)) { + prefixes = prefixes + (prefix -> ns) + } i = i + 1 } prefixMapping ::= prefixes diff --git a/src/main/scala/com/codecommit/antixml/XMLSerializer.scala b/src/main/scala/com/codecommit/antixml/XMLSerializer.scala new file mode 100644 index 0000000..c911c61 --- /dev/null +++ b/src/main/scala/com/codecommit/antixml/XMLSerializer.scala @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011, Daniel Spiewak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * - Neither the name of "Anti-XML" nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.codecommit +package antixml + +import java.io.Writer +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter + +class XMLSerializer(val encoding : String, val outputDeclaration : Boolean) { + def serializeDocument(elem : Elem, w : Writer) { + if (outputDeclaration) { + w.append("") + } + serialize(elem, w); + } + + def serializeDocument(elem : Elem, outputFile : java.io.File) { + serializeDocument(elem, new OutputStreamWriter(new FileOutputStream(outputFile), encoding)) + } + + def serialize(elem : Elem, w : Writer) { + var scopes : List[Map[String, String]] = Nil; + + def doSerialize(node : Node, w : Writer) { + + node match { + case Elem(prefix, name, attrs, scope, children) => { + val parentScope = scopes.headOption getOrElse Map() + scopes = scope :: scopes + val attrStr = if (attrs.isEmpty) + "" + else + " " + (attrs map { case (key, value) => key.toString + "=\"" + Node.escapeText(value) + '"' } mkString " ") + + val scopeChange = scope filter { case (key, value) => parentScope.get(key) != Some(value) } + val prefixesStr = if (scopeChange.isEmpty) + "" + else + " " + (scopeChange map { + case (key, value) => (if (key == "") "xmlns" else "xmlns:" + key) + "=\"" + Node.escapeText(value) + '"' } mkString " ") + + val qname = (prefix map { _ + ":" } getOrElse "") + name + val partial = "<" + qname + attrStr + prefixesStr + if (children.isEmpty) { + w append partial + w append "/>" + } else { + w append partial + w append '>' + children.foreach(doSerialize(_, w)) + w append "' + } + scopes = scopes.tail + } + case node => w.append(node.toString()) + } + } + doSerialize(elem, w) + } +} + +object XMLSerializer { + def apply(encoding : String = "UTF-8", outputDeclaration : Boolean = false) : XMLSerializer = { + new XMLSerializer(encoding, outputDeclaration); + } +} \ No newline at end of file diff --git a/src/main/scala/com/codecommit/antixml/node.scala b/src/main/scala/com/codecommit/antixml/node.scala index d5dcd8b..a311103 100644 --- a/src/main/scala/com/codecommit/antixml/node.scala +++ b/src/main/scala/com/codecommit/antixml/node.scala @@ -118,24 +118,10 @@ case class Elem(prefix: Option[String], name: String, attrs: Attributes, scope: def canonicalize = copy(children=children.canonicalize) override def toString = { - import Node._ - - val attrStr = if (attrs.isEmpty) - "" - else - " " + (attrs map { case (key, value) => key.toString + "=\"" + escapeText(value) + '"' } mkString " ") - - val prefixesStr = if (scope.isEmpty) - "" - else - " " + (scope map { case (key, value) => (if (key == "") "xmlns" else "xmlns:" + escapeText(key)) + "=\"" + escapeText(value) + '"' } mkString " ") - - val qname = (prefix map { _ + ":" } getOrElse "") + name - val partial = "<" + qname + attrStr + prefixesStr - if (children.isEmpty) - partial + "/>" - else - partial + '>' + children.toString + "' + val sw = new java.io.StringWriter() + val xs = XMLSerializer() + xs.serialize(this, sw) + sw.toString } def toGroup = Group(this) diff --git a/src/test/scala/com/codecommit/antixml/SAXSpecs.scala b/src/test/scala/com/codecommit/antixml/SAXSpecs.scala index a6e1126..d51285f 100644 --- a/src/test/scala/com/codecommit/antixml/SAXSpecs.scala +++ b/src/test/scala/com/codecommit/antixml/SAXSpecs.scala @@ -42,6 +42,10 @@ class SAXSpecs extends Specification { SAXParser.fromString("") mustEqual Elem(Some("pf"), "a", Attributes(), Map("pf" -> "urn:a"), Group()) } + "parse a simpleString with an non-prefixed namespace" in { + SAXParser.fromString("") mustEqual Elem(None, "a", Attributes(), Map("" -> "urn:a"), Group()) + } + "parse a String and generate an Elem" in { SAXParser.fromString("hi there") mustEqual Elem(Some("p"), "a", Attributes(), Map("p"->"ns"), Group(Text("hi"), Elem(None, "b", Attributes("attr" -> "value"), Map("p"->"ns"), Group()), Text(" there"))) } diff --git a/src/test/scala/com/codecommit/antixml/StAXSpecs.scala b/src/test/scala/com/codecommit/antixml/StAXSpecs.scala index af1b2b0..f6d4ce8 100644 --- a/src/test/scala/com/codecommit/antixml/StAXSpecs.scala +++ b/src/test/scala/com/codecommit/antixml/StAXSpecs.scala @@ -38,5 +38,10 @@ class StAXSpecs extends Specification { StAXParser.fromString("hi there") mustEqual Elem(Some("a"), "a", Attributes(), Map("a" -> "a"), Group(Text("hi"), Elem(None, "b", Attributes("attr" -> "value"), Map("a" -> "a"), Group()), Text(" there"))) } + + "parse a simpleString with an non-prefixed namespace" in { + StAXParser.fromString("") mustEqual Elem(None, "a", Attributes(), Map("" -> "urn:a"), Group()) + } + } } diff --git a/src/test/scala/com/codecommit/antixml/XMLSpecs.scala b/src/test/scala/com/codecommit/antixml/XMLSpecs.scala index 6c23c66..449677a 100644 --- a/src/test/scala/com/codecommit/antixml/XMLSpecs.scala +++ b/src/test/scala/com/codecommit/antixml/XMLSpecs.scala @@ -68,15 +68,14 @@ class XMLSpecs extends Specification { fromString("").name mustEqual "test" } - "serialize prefixes" in { - fromString("\n\n\t\n").toString mustEqual "\n\n\t\n" - } - "serialize prefixes minimally" in { - // fromString("\n\n\t\n").toString mustEqual "\n\n\t\n" - Pending("not implemented yet") + fromString("\n\n\t\n").toString mustEqual "\n\n\t\n" } - } + + "serialize unprefixed elements correctly" in { + fromString("\n\n\t\n").toString mustEqual "\n\n\t\n" + } +} "fromSource" should { import scala.io.Source