Skip to content

Commit

Permalink
Issue #119 - Extend the StAX Handling (again).
Browse files Browse the repository at this point in the history
More work to come
  • Loading branch information
rolfl committed Apr 28, 2013
1 parent b00258b commit ebf1c57
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 31 deletions.
44 changes: 41 additions & 3 deletions core/src/java/org/jdom2/input/StAXStreamWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* this class is written to (it's an XMLStreamWriter implementation) to create a JDOM
* Document whereas the StAXStreamBuilder <strong>reads from</strong> a user-supplied
* XMLStreamReader. It is the difference between a 'push' concept and a 'pull' concept.
* <p>
* An interesting read for people using this class:
* <a href="http://ws.apache.org/axiom/devguide/ch05.html">Apache Axiom notes on setPrefix()</a>.
*
* @author gordon burgett https://github.com/gburgett
* @author Rolf Lear
Expand Down Expand Up @@ -219,6 +222,9 @@ public void setPrefix(final String prefix, final String uri)
if (prefix == null) {
throw new IllegalArgumentException("prefix may not be null");
}
if (prefix.equals(JDOMConstants.NS_PREFIX_XMLNS)) {
return;
}
if (document == null || done) {
throw new IllegalStateException(
"Attempt to set prefix at an illegal stream state.");
Expand Down Expand Up @@ -265,7 +271,12 @@ public void writeDTD(final String dtd) throws XMLStreamException {

@Override
public void writeStartElement(final String localName) throws XMLStreamException {
writeStartElement("", localName);
final int pos = localName.indexOf(':');
if (pos >= 0) {
writeStartElement(localName.substring(0, pos), localName.substring(pos + 1));
} else {
writeStartElement("", localName);
}
}

@Override
Expand All @@ -277,7 +288,12 @@ public void writeStartElement(final String namespaceURI, final String localName)
if (localName == null) {
throw new XMLStreamException("Cannot have a null localname");
}
this.buildElement("", localName, namespaceURI, false, false);
final int pos = localName.indexOf(':');
if (pos >= 0) {
this.buildElement(localName.substring(0, pos), localName.substring(pos + 1), namespaceURI, false, false);
} else {
this.buildElement("", localName, namespaceURI, false, false);
}
}

@Override
Expand Down Expand Up @@ -346,6 +362,14 @@ public void writeNamespace(final String prefix, final String namespaceURI)
}
if (prefix == null || JDOMConstants.NS_PREFIX_XMLNS.equals(prefix)) {
// recurse with the "" prefix.
// yet another special case....
// if the element itself was written out without a prefix, and without a namespace
// then we update the element to have the same namespace as we have here.
// this is required to support some native Java Transform engines that do not
// supply content in the right order.
if ("".equals(activeelement.getNamespacePrefix())) {
activeelement.setNamespace(Namespace.getNamespace("", namespaceURI));
}
writeNamespace("", namespaceURI);
} else {
pendingns.add(Namespace.getNamespace(prefix, namespaceURI));
Expand Down Expand Up @@ -514,6 +538,7 @@ public Object getProperty(String name) throws IllegalArgumentException {
* @param prefix The namespace prefix (may be null).
* @param localName The Element tag
* @param namespaceURI The namespace URI (may be null).
* @param withpfx whether the prefix is user-specified
* @param empty Is this an Empty element (expecting children?)
* @throws XMLStreamException If the stream is not in an appropriate state for a new Element.
*/
Expand Down Expand Up @@ -558,8 +583,21 @@ private Namespace resolveElementNamespace(String prefix, String namespaceURI,
final Namespace defns = boundstack.getNamespaceForPrefix("");
if(Namespace.NO_NAMESPACE != defns) {
// inconsistency in XMLStreamWriter specification....
// In theory the repairing code should create a generated prefic for unbound
// In theory the repairing code should create a generated prefix for unbound
// namespace URI, but you can't create a prefixed ""-URI namespace.
//
// It next makes sense to throw an exception for this, and insist that the
// "" URI must be explicitly bound using a prior setPrefix("","") call,
// but, broken though it is, the better option is to replicate the undocumented
// special-case handling that the BEA Reference implementation does ....
// if you have the prefix (in this case "") bound already, and now you are trying
// to bind the "" URI against the "" prefix, we let you do that as a special case
// but be warned that "" is no longer bound to the previous URI in this Element's
// scope.
// http://svn.stax.codehaus.org/browse/stax/trunk/dev/src/com/bea/xml/stream/XMLWriterBase.java?r=4&r=4&r=124#to278
if (repairnamespace) {
return Namespace.NO_NAMESPACE;
}
throw new XMLStreamException("This attempt to use the empty URI \"\" as an " +
"Element Namespace is illegal because the default Namespace is already " +
"bound to the URI '" + defns.getURI() + "'. You must call " +
Expand Down
1 change: 1 addition & 0 deletions core/src/java/org/jdom2/output/StAXStreamReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* your needs if all you want to do is tweak some details.
*
* @author Rolf Lear
* @since JDOM 2.1.0
*/

public final class StAXStreamReader implements Cloneable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
import org.jdom2.CDATA;
import org.jdom2.Comment;
import org.jdom2.Content;
import org.jdom2.JDOMConstants;
import org.jdom2.Content.CType;
import org.jdom2.DocType;
import org.jdom2.Document;
Expand Down Expand Up @@ -113,7 +114,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* {@link #printContent(XMLStreamWriter, FormatStack, NamespaceStack, Walker)} methods, but
* the FormatStack is pushed through to all print* Methods.
* <p>
*
* An interesting read for people using this class:
* <a href="http://ws.apache.org/axiom/devguide/ch05.html">Apache Axiom notes on setPrefix()</a>.
*
* @see StAXStreamOutputter
* @see StAXStreamProcessor
* @since JDOM2
Expand Down Expand Up @@ -554,6 +557,13 @@ protected void printElement(final XMLStreamWriter out, final FormatStack fstack,

nstack.push(element);
try {
for (Namespace nsa : nstack.addedForward()) {
if (JDOMConstants.NS_PREFIX_DEFAULT.equals(nsa.getPrefix())) {
out.setDefaultNamespace(nsa.getURI());
} else {
out.setPrefix(nsa.getPrefix(), nsa.getURI());
}
}

final List<Content> content = element.getContent();

Expand Down Expand Up @@ -601,17 +611,9 @@ else if ("preserve".equals(space)) {
// then we must expand.
boolean expandit = walker != null || fstack.isExpandEmptyElements();

final Namespace ns = element.getNamespace();
if (expandit) {
Namespace ns = element.getNamespace();
// out.setPrefix(ns.getPrefix(), ns.getURI());
out.writeStartElement(ns.getPrefix(), element.getName(), ns.getURI());
// if (ns == Namespace.NO_NAMESPACE) {
// out.writeStartElement(element.getName());
// } else if ("".equals(ns.getPrefix())) {
// out.writeStartElement(ns.getURI(), element.getName());
// } else {
// out.writeStartElement(ns.getPrefix(), element.getName(), ns.getURI());
// }

// Print the element's namespace, if appropriate
for (final Namespace nsd : nstack.addedForward()) {
Expand All @@ -625,6 +627,11 @@ else if ("preserve".equals(space)) {
}
}

// This neatens up the output stream for some reason - bug in standard StAX
// implementation requires us to close off the Element start tag before we
// start adding new Namespaces to child contexts...
out.writeCharacters("");

// OK, now we print out the meat of the Element
if (walker != null) {
// we need to re-create the walker/fstack.
Expand All @@ -651,7 +658,6 @@ else if ("preserve".equals(space)) {

out.writeEndElement();


} else {
// implies:
// fstack.isExpandEmpty... is false
Expand All @@ -660,16 +666,7 @@ else if ("preserve".equals(space)) {
// and preserve == false
// and whiteonly == true

Namespace ns = element.getNamespace();
// out.setPrefix(ns.getPrefix(), ns.getURI());
out.writeEmptyElement(ns.getPrefix(), element.getName(), ns.getURI());
// if (ns == Namespace.NO_NAMESPACE) {
// out.writeEmptyElement(element.getName());
// } else if ("".equals(ns.getPrefix())) {
// out.writeEmptyElement("", element.getName(), ns.getURI());
// } else {
// out.writeEmptyElement(ns.getPrefix(), element.getName(), ns.getURI());
// }

// Print the element's namespace, if appropriate
for (final Namespace nsd : nstack.addedForward()) {
Expand All @@ -680,12 +677,21 @@ else if ("preserve".equals(space)) {
for (final Attribute attribute : element.getAttributes()) {
printAttribute(out, fstack, attribute);
}

// This neatens up the output stream for some reason.
out.writeCharacters("");
}

} finally {
for (Namespace nsr : nstack.addedForward()) {
Namespace nsa = nstack.getRebound(nsr.getPrefix());
if (nsa != null) {
if (JDOMConstants.NS_PREFIX_DEFAULT.equals(nsa.getPrefix())) {
out.setDefaultNamespace(nsa.getURI());
} else {
out.setPrefix(nsa.getPrefix(), nsa.getURI());
}
}
}
nstack.pop();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,11 @@ public void require(int type, String namespaceURI, String localName) throws XMLS
public QName getName() {
switch (currentEvt) {
case START_ELEMENT:
final Element emts = emtstack[depth];
return new QName(emts.getNamespaceURI(), emts.getName(), emts.getNamespacePrefix());
case END_ELEMENT:
final Element emt = emtstack[depth];
return new QName(emt.getNamespaceURI(), emt.getName(), emt.getNamespacePrefix());
final Element emte = emtstack[depth + 1];
return new QName(emte.getNamespaceURI(), emte.getName(), emte.getNamespacePrefix());
default:
throw new IllegalStateException("getName not supported for event " + currentEvt);
}
Expand Down
28 changes: 28 additions & 0 deletions core/src/java/org/jdom2/util/NamespaceStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ public boolean isInScope(Namespace ns) {
* Get the Namespace in the current scope with the specified prefix.
* @param prefix The prefix to get the namespace for (null is treated the same as "").
* @return The Namespace with the specified prefix, or null if the prefix is not in scope.
* @since JDOM 2.1.0
*/
public Namespace getNamespaceForPrefix(final String prefix) {
if (prefix == null) {
Expand All @@ -674,6 +675,7 @@ public Namespace getNamespaceForPrefix(final String prefix) {
* Get the <strong>first</strong> Namespace in the current scope that is bound to the specified URI.
* @param uri The URI to get the first prefix for (null is treated the same as "").
* @return The first bound Namespace for the specified URI, or null if the URI is not bound.
* @since JDOM 2.1.0
*/
public Namespace getFirstNamespaceForURI(final String uri) {
if (uri == null) {
Expand All @@ -691,6 +693,7 @@ public Namespace getFirstNamespaceForURI(final String uri) {
* Get all prefixes in the current scope that are bound to the specified URI.
* @param uri The URI to get the first prefix for (null is treated the same as "").
* @return All bound prefixes for the specified URI, or an empty array if the URI is not bound.
* @since JDOM 2.1.0
*/
public Namespace[] getAllNamespacesForURI(final String uri) {
if (uri == null) {
Expand All @@ -705,4 +708,29 @@ public Namespace[] getAllNamespacesForURI(final String uri) {
return al.toArray(new Namespace[al.size()]);
}

/**
* If the specified prefix was bound in the previous bind level, and has
* been rebound to a different URI in the current level, then return the
* Namespace the the prefix <strong>was bound to</strong> before.
* @param prefix The prefix to check for re-binding
* @return the previous binding for the specified prefix, or null if the prefix was
* not previously bound, or was not changed in this level of the stack.
* @since JDOM 2.1.0
*/
public Namespace getRebound(final String prefix) {
if (depth <= 0) {
return null;
}
for (Namespace nsa : added[depth]) {
if (nsa.getPrefix().equals(prefix)) {
for (Namespace nsp : scope[depth - 1]) {
if (nsp.getPrefix().equals(prefix)) {
return nsp;
}
}
return null;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public abstract class AbstractTestRoundTrip {

abstract Document roundTrip(Document doc);

private final void checkRoundTrip(final Document doc) {
abstract Document prepare(Document doc);

private final void checkRoundTrip(final Document indoc) {
final Document doc = prepare(indoc);
final Document rtdoc = roundTrip(doc);
assertTrue(rtdoc != null);
try {
Expand All @@ -40,6 +43,14 @@ public void testBasic() {
Document doc = new Document(new Element("root"));
checkRoundTrip(doc);
}

@Test
public void testDefaultNamespace() {
Element emt = new Element("root", "ns:1");
emt.addContent(new Element("child")); // note, no namespace.
Document doc = new Document(emt);
checkRoundTrip(doc);
}


@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import org.jdom2.output.StAXStreamOutputter;

@SuppressWarnings("javadoc")
public class TestStAXReaderWriter extends AbstractTestRoundTrip {
public class TestStAXOutputter2Writer extends AbstractTestRoundTrip {

@Override
Document prepare(Document doc) {
return doc;
}

@Override
Document roundTrip(final Document doc) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
import org.jdom2.output.StAXStreamReader;

@SuppressWarnings("javadoc")
public class TestStAXWriterReader extends AbstractTestRoundTrip {
public class TestStAXReader2Builder extends AbstractTestRoundTrip {

@Override
Document prepare(Document doc) {
return doc;
}

@Override
Document roundTrip(final Document doc) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.jdom2.test.cases.output;

import java.util.Iterator;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stax.StAXSource;

import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.StAXStreamWriter;
import org.jdom2.output.StAXStreamReader;

@SuppressWarnings("javadoc")
public class TestStAXReader2Writer extends AbstractTestRoundTrip {

@Override
Document prepare(Document doc) {
Document ret = doc.clone();
for (Iterator<Content> it = ret.getContent().iterator(); it.hasNext(); ) {
Content c = it.next();
if (!(c instanceof Element)) {
it.remove();
}
}
return ret;
}

@Override
Document roundTrip(final Document doc) {
try {
final StAXStreamWriter sw = new StAXStreamWriter();
final StAXStreamReader sr = new StAXStreamReader();
final TransformerFactory tf = TransformerFactory.newInstance(
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl",
this.getClass().getClassLoader());
final Transformer t = tf.newTransformer();
StAXSource source = new StAXSource(sr.output(doc));
StAXResult result = new StAXResult(sw);
t.transform(source, result);
return sw.getDocument();
} catch (Exception e) {
throw new IllegalStateException("Failed to identity-trasform...", e);
}
}

}
4 changes: 3 additions & 1 deletion test/src/java/org/jdom2/test/util/UnitTestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,9 @@ public static final void compare(final NamespaceAware ap, final NamespaceAware b
Document b = (Document)bp;
assertEquals(a.getBaseURI(), b.getBaseURI());
final int sz = a.getContentSize();
assertTrue(sz == b.getContentSize());
if (sz != b.getContentSize()) {
fail (String.format("We expected %d members in the Document content, but got %d", sz, b.getContentSize()));
}
for (int i = 0; i < sz; i++) {
compare(a.getContent(i), b.getContent(i));
}
Expand Down

0 comments on commit ebf1c57

Please sign in to comment.