Skip to content

Commit ebf1c57

Browse files
committed
Issue #119 - Extend the StAX Handling (again).
More work to come
1 parent b00258b commit ebf1c57

File tree

10 files changed

+178
-31
lines changed

10 files changed

+178
-31
lines changed

core/src/java/org/jdom2/input/StAXStreamWriter.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
7979
* this class is written to (it's an XMLStreamWriter implementation) to create a JDOM
8080
* Document whereas the StAXStreamBuilder <strong>reads from</strong> a user-supplied
8181
* XMLStreamReader. It is the difference between a 'push' concept and a 'pull' concept.
82+
* <p>
83+
* An interesting read for people using this class:
84+
* <a href="http://ws.apache.org/axiom/devguide/ch05.html">Apache Axiom notes on setPrefix()</a>.
8285
*
8386
* @author gordon burgett https://github.com/gburgett
8487
* @author Rolf Lear
@@ -219,6 +222,9 @@ public void setPrefix(final String prefix, final String uri)
219222
if (prefix == null) {
220223
throw new IllegalArgumentException("prefix may not be null");
221224
}
225+
if (prefix.equals(JDOMConstants.NS_PREFIX_XMLNS)) {
226+
return;
227+
}
222228
if (document == null || done) {
223229
throw new IllegalStateException(
224230
"Attempt to set prefix at an illegal stream state.");
@@ -265,7 +271,12 @@ public void writeDTD(final String dtd) throws XMLStreamException {
265271

266272
@Override
267273
public void writeStartElement(final String localName) throws XMLStreamException {
268-
writeStartElement("", localName);
274+
final int pos = localName.indexOf(':');
275+
if (pos >= 0) {
276+
writeStartElement(localName.substring(0, pos), localName.substring(pos + 1));
277+
} else {
278+
writeStartElement("", localName);
279+
}
269280
}
270281

271282
@Override
@@ -277,7 +288,12 @@ public void writeStartElement(final String namespaceURI, final String localName)
277288
if (localName == null) {
278289
throw new XMLStreamException("Cannot have a null localname");
279290
}
280-
this.buildElement("", localName, namespaceURI, false, false);
291+
final int pos = localName.indexOf(':');
292+
if (pos >= 0) {
293+
this.buildElement(localName.substring(0, pos), localName.substring(pos + 1), namespaceURI, false, false);
294+
} else {
295+
this.buildElement("", localName, namespaceURI, false, false);
296+
}
281297
}
282298

283299
@Override
@@ -346,6 +362,14 @@ public void writeNamespace(final String prefix, final String namespaceURI)
346362
}
347363
if (prefix == null || JDOMConstants.NS_PREFIX_XMLNS.equals(prefix)) {
348364
// recurse with the "" prefix.
365+
// yet another special case....
366+
// if the element itself was written out without a prefix, and without a namespace
367+
// then we update the element to have the same namespace as we have here.
368+
// this is required to support some native Java Transform engines that do not
369+
// supply content in the right order.
370+
if ("".equals(activeelement.getNamespacePrefix())) {
371+
activeelement.setNamespace(Namespace.getNamespace("", namespaceURI));
372+
}
349373
writeNamespace("", namespaceURI);
350374
} else {
351375
pendingns.add(Namespace.getNamespace(prefix, namespaceURI));
@@ -514,6 +538,7 @@ public Object getProperty(String name) throws IllegalArgumentException {
514538
* @param prefix The namespace prefix (may be null).
515539
* @param localName The Element tag
516540
* @param namespaceURI The namespace URI (may be null).
541+
* @param withpfx whether the prefix is user-specified
517542
* @param empty Is this an Empty element (expecting children?)
518543
* @throws XMLStreamException If the stream is not in an appropriate state for a new Element.
519544
*/
@@ -558,8 +583,21 @@ private Namespace resolveElementNamespace(String prefix, String namespaceURI,
558583
final Namespace defns = boundstack.getNamespaceForPrefix("");
559584
if(Namespace.NO_NAMESPACE != defns) {
560585
// inconsistency in XMLStreamWriter specification....
561-
// In theory the repairing code should create a generated prefic for unbound
586+
// In theory the repairing code should create a generated prefix for unbound
562587
// namespace URI, but you can't create a prefixed ""-URI namespace.
588+
//
589+
// It next makes sense to throw an exception for this, and insist that the
590+
// "" URI must be explicitly bound using a prior setPrefix("","") call,
591+
// but, broken though it is, the better option is to replicate the undocumented
592+
// special-case handling that the BEA Reference implementation does ....
593+
// if you have the prefix (in this case "") bound already, and now you are trying
594+
// to bind the "" URI against the "" prefix, we let you do that as a special case
595+
// but be warned that "" is no longer bound to the previous URI in this Element's
596+
// scope.
597+
// http://svn.stax.codehaus.org/browse/stax/trunk/dev/src/com/bea/xml/stream/XMLWriterBase.java?r=4&r=4&r=124#to278
598+
if (repairnamespace) {
599+
return Namespace.NO_NAMESPACE;
600+
}
563601
throw new XMLStreamException("This attempt to use the empty URI \"\" as an " +
564602
"Element Namespace is illegal because the default Namespace is already " +
565603
"bound to the URI '" + defns.getURI() + "'. You must call " +

core/src/java/org/jdom2/output/StAXStreamReader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
8787
* your needs if all you want to do is tweak some details.
8888
*
8989
* @author Rolf Lear
90+
* @since JDOM 2.1.0
9091
*/
9192

9293
public final class StAXStreamReader implements Cloneable {

core/src/java/org/jdom2/output/support/AbstractStAXStreamProcessor.java

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
6666
import org.jdom2.CDATA;
6767
import org.jdom2.Comment;
6868
import org.jdom2.Content;
69+
import org.jdom2.JDOMConstants;
6970
import org.jdom2.Content.CType;
7071
import org.jdom2.DocType;
7172
import org.jdom2.Document;
@@ -113,7 +114,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
113114
* {@link #printContent(XMLStreamWriter, FormatStack, NamespaceStack, Walker)} methods, but
114115
* the FormatStack is pushed through to all print* Methods.
115116
* <p>
116-
*
117+
* An interesting read for people using this class:
118+
* <a href="http://ws.apache.org/axiom/devguide/ch05.html">Apache Axiom notes on setPrefix()</a>.
119+
*
117120
* @see StAXStreamOutputter
118121
* @see StAXStreamProcessor
119122
* @since JDOM2
@@ -554,6 +557,13 @@ protected void printElement(final XMLStreamWriter out, final FormatStack fstack,
554557

555558
nstack.push(element);
556559
try {
560+
for (Namespace nsa : nstack.addedForward()) {
561+
if (JDOMConstants.NS_PREFIX_DEFAULT.equals(nsa.getPrefix())) {
562+
out.setDefaultNamespace(nsa.getURI());
563+
} else {
564+
out.setPrefix(nsa.getPrefix(), nsa.getURI());
565+
}
566+
}
557567

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

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

614+
final Namespace ns = element.getNamespace();
604615
if (expandit) {
605-
Namespace ns = element.getNamespace();
606-
// out.setPrefix(ns.getPrefix(), ns.getURI());
607616
out.writeStartElement(ns.getPrefix(), element.getName(), ns.getURI());
608-
// if (ns == Namespace.NO_NAMESPACE) {
609-
// out.writeStartElement(element.getName());
610-
// } else if ("".equals(ns.getPrefix())) {
611-
// out.writeStartElement(ns.getURI(), element.getName());
612-
// } else {
613-
// out.writeStartElement(ns.getPrefix(), element.getName(), ns.getURI());
614-
// }
615617

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

630+
// This neatens up the output stream for some reason - bug in standard StAX
631+
// implementation requires us to close off the Element start tag before we
632+
// start adding new Namespaces to child contexts...
633+
out.writeCharacters("");
634+
628635
// OK, now we print out the meat of the Element
629636
if (walker != null) {
630637
// we need to re-create the walker/fstack.
@@ -651,7 +658,6 @@ else if ("preserve".equals(space)) {
651658

652659
out.writeEndElement();
653660

654-
655661
} else {
656662
// implies:
657663
// fstack.isExpandEmpty... is false
@@ -660,16 +666,7 @@ else if ("preserve".equals(space)) {
660666
// and preserve == false
661667
// and whiteonly == true
662668

663-
Namespace ns = element.getNamespace();
664-
// out.setPrefix(ns.getPrefix(), ns.getURI());
665669
out.writeEmptyElement(ns.getPrefix(), element.getName(), ns.getURI());
666-
// if (ns == Namespace.NO_NAMESPACE) {
667-
// out.writeEmptyElement(element.getName());
668-
// } else if ("".equals(ns.getPrefix())) {
669-
// out.writeEmptyElement("", element.getName(), ns.getURI());
670-
// } else {
671-
// out.writeEmptyElement(ns.getPrefix(), element.getName(), ns.getURI());
672-
// }
673670

674671
// Print the element's namespace, if appropriate
675672
for (final Namespace nsd : nstack.addedForward()) {
@@ -680,12 +677,21 @@ else if ("preserve".equals(space)) {
680677
for (final Attribute attribute : element.getAttributes()) {
681678
printAttribute(out, fstack, attribute);
682679
}
683-
684680
// This neatens up the output stream for some reason.
685681
out.writeCharacters("");
686682
}
687-
683+
688684
} finally {
685+
for (Namespace nsr : nstack.addedForward()) {
686+
Namespace nsa = nstack.getRebound(nsr.getPrefix());
687+
if (nsa != null) {
688+
if (JDOMConstants.NS_PREFIX_DEFAULT.equals(nsa.getPrefix())) {
689+
out.setDefaultNamespace(nsa.getURI());
690+
} else {
691+
out.setPrefix(nsa.getPrefix(), nsa.getURI());
692+
}
693+
}
694+
}
689695
nstack.pop();
690696
}
691697
}

core/src/java/org/jdom2/output/support/AbstractStAXStreamReader.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,11 @@ public void require(int type, String namespaceURI, String localName) throws XMLS
338338
public QName getName() {
339339
switch (currentEvt) {
340340
case START_ELEMENT:
341+
final Element emts = emtstack[depth];
342+
return new QName(emts.getNamespaceURI(), emts.getName(), emts.getNamespacePrefix());
341343
case END_ELEMENT:
342-
final Element emt = emtstack[depth];
343-
return new QName(emt.getNamespaceURI(), emt.getName(), emt.getNamespacePrefix());
344+
final Element emte = emtstack[depth + 1];
345+
return new QName(emte.getNamespaceURI(), emte.getName(), emte.getNamespacePrefix());
344346
default:
345347
throw new IllegalStateException("getName not supported for event " + currentEvt);
346348
}

core/src/java/org/jdom2/util/NamespaceStack.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ public boolean isInScope(Namespace ns) {
656656
* Get the Namespace in the current scope with the specified prefix.
657657
* @param prefix The prefix to get the namespace for (null is treated the same as "").
658658
* @return The Namespace with the specified prefix, or null if the prefix is not in scope.
659+
* @since JDOM 2.1.0
659660
*/
660661
public Namespace getNamespaceForPrefix(final String prefix) {
661662
if (prefix == null) {
@@ -674,6 +675,7 @@ public Namespace getNamespaceForPrefix(final String prefix) {
674675
* Get the <strong>first</strong> Namespace in the current scope that is bound to the specified URI.
675676
* @param uri The URI to get the first prefix for (null is treated the same as "").
676677
* @return The first bound Namespace for the specified URI, or null if the URI is not bound.
678+
* @since JDOM 2.1.0
677679
*/
678680
public Namespace getFirstNamespaceForURI(final String uri) {
679681
if (uri == null) {
@@ -691,6 +693,7 @@ public Namespace getFirstNamespaceForURI(final String uri) {
691693
* Get all prefixes in the current scope that are bound to the specified URI.
692694
* @param uri The URI to get the first prefix for (null is treated the same as "").
693695
* @return All bound prefixes for the specified URI, or an empty array if the URI is not bound.
696+
* @since JDOM 2.1.0
694697
*/
695698
public Namespace[] getAllNamespacesForURI(final String uri) {
696699
if (uri == null) {
@@ -705,4 +708,29 @@ public Namespace[] getAllNamespacesForURI(final String uri) {
705708
return al.toArray(new Namespace[al.size()]);
706709
}
707710

711+
/**
712+
* If the specified prefix was bound in the previous bind level, and has
713+
* been rebound to a different URI in the current level, then return the
714+
* Namespace the the prefix <strong>was bound to</strong> before.
715+
* @param prefix The prefix to check for re-binding
716+
* @return the previous binding for the specified prefix, or null if the prefix was
717+
* not previously bound, or was not changed in this level of the stack.
718+
* @since JDOM 2.1.0
719+
*/
720+
public Namespace getRebound(final String prefix) {
721+
if (depth <= 0) {
722+
return null;
723+
}
724+
for (Namespace nsa : added[depth]) {
725+
if (nsa.getPrefix().equals(prefix)) {
726+
for (Namespace nsp : scope[depth - 1]) {
727+
if (nsp.getPrefix().equals(prefix)) {
728+
return nsp;
729+
}
730+
}
731+
return null;
732+
}
733+
}
734+
return null;
735+
}
708736
}

test/src/java/org/jdom2/test/cases/output/AbstractTestRoundTrip.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ public abstract class AbstractTestRoundTrip {
1919

2020
abstract Document roundTrip(Document doc);
2121

22-
private final void checkRoundTrip(final Document doc) {
22+
abstract Document prepare(Document doc);
23+
24+
private final void checkRoundTrip(final Document indoc) {
25+
final Document doc = prepare(indoc);
2326
final Document rtdoc = roundTrip(doc);
2427
assertTrue(rtdoc != null);
2528
try {
@@ -40,6 +43,14 @@ public void testBasic() {
4043
Document doc = new Document(new Element("root"));
4144
checkRoundTrip(doc);
4245
}
46+
47+
@Test
48+
public void testDefaultNamespace() {
49+
Element emt = new Element("root", "ns:1");
50+
emt.addContent(new Element("child")); // note, no namespace.
51+
Document doc = new Document(emt);
52+
checkRoundTrip(doc);
53+
}
4354

4455

4556
@Test

test/src/java/org/jdom2/test/cases/output/TestStAXReaderWriter.java renamed to test/src/java/org/jdom2/test/cases/output/TestStAXOutputter2Writer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
import org.jdom2.output.StAXStreamOutputter;
88

99
@SuppressWarnings("javadoc")
10-
public class TestStAXReaderWriter extends AbstractTestRoundTrip {
10+
public class TestStAXOutputter2Writer extends AbstractTestRoundTrip {
11+
12+
@Override
13+
Document prepare(Document doc) {
14+
return doc;
15+
}
1116

1217
@Override
1318
Document roundTrip(final Document doc) {

test/src/java/org/jdom2/test/cases/output/TestStAXWriterReader.java renamed to test/src/java/org/jdom2/test/cases/output/TestStAXReader2Builder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
import org.jdom2.output.StAXStreamReader;
77

88
@SuppressWarnings("javadoc")
9-
public class TestStAXWriterReader extends AbstractTestRoundTrip {
9+
public class TestStAXReader2Builder extends AbstractTestRoundTrip {
10+
11+
@Override
12+
Document prepare(Document doc) {
13+
return doc;
14+
}
1015

1116
@Override
1217
Document roundTrip(final Document doc) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.jdom2.test.cases.output;
2+
3+
import java.util.Iterator;
4+
5+
import javax.xml.transform.Transformer;
6+
import javax.xml.transform.TransformerFactory;
7+
import javax.xml.transform.stax.StAXResult;
8+
import javax.xml.transform.stax.StAXSource;
9+
10+
import org.jdom2.Content;
11+
import org.jdom2.Document;
12+
import org.jdom2.Element;
13+
import org.jdom2.input.StAXStreamWriter;
14+
import org.jdom2.output.StAXStreamReader;
15+
16+
@SuppressWarnings("javadoc")
17+
public class TestStAXReader2Writer extends AbstractTestRoundTrip {
18+
19+
@Override
20+
Document prepare(Document doc) {
21+
Document ret = doc.clone();
22+
for (Iterator<Content> it = ret.getContent().iterator(); it.hasNext(); ) {
23+
Content c = it.next();
24+
if (!(c instanceof Element)) {
25+
it.remove();
26+
}
27+
}
28+
return ret;
29+
}
30+
31+
@Override
32+
Document roundTrip(final Document doc) {
33+
try {
34+
final StAXStreamWriter sw = new StAXStreamWriter();
35+
final StAXStreamReader sr = new StAXStreamReader();
36+
final TransformerFactory tf = TransformerFactory.newInstance(
37+
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl",
38+
this.getClass().getClassLoader());
39+
final Transformer t = tf.newTransformer();
40+
StAXSource source = new StAXSource(sr.output(doc));
41+
StAXResult result = new StAXResult(sw);
42+
t.transform(source, result);
43+
return sw.getDocument();
44+
} catch (Exception e) {
45+
throw new IllegalStateException("Failed to identity-trasform...", e);
46+
}
47+
}
48+
49+
}

test/src/java/org/jdom2/test/util/UnitTestUtil.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,9 @@ public static final void compare(final NamespaceAware ap, final NamespaceAware b
411411
Document b = (Document)bp;
412412
assertEquals(a.getBaseURI(), b.getBaseURI());
413413
final int sz = a.getContentSize();
414-
assertTrue(sz == b.getContentSize());
414+
if (sz != b.getContentSize()) {
415+
fail (String.format("We expected %d members in the Document content, but got %d", sz, b.getContentSize()));
416+
}
415417
for (int i = 0; i < sz; i++) {
416418
compare(a.getContent(i), b.getContent(i));
417419
}

0 commit comments

Comments
 (0)