Creating namespace prefixed XML nodes in Java DOM - java

I am creating several XML files via Java and up to this point everything worked fine, but now I've run into a problem when trying to create a file with namespace prefixed nodes, i.e, stuff like <tns:node> ... </tns:node> using a refactored version of my code that's already working for normal xml files without namespaces.
The error getting thrown is:
org.w3c.dom.DOMException: INVALID_CHARACTER_ERR: Ungültiges XML-Zeichen angegeben.
Sorry for the German in there, it says "invalid XML-sign specified".
The codeline where the error occurs:
Element mainRootElement = doc.createElement("tns:cmds xmlns:tns=\"http://abc.de/x/y/z\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://abc.de/x/y/z xyzschema.xsd\"");
To eliminate the possibility of the error resulting in escaping that rather long string or something among those lines I also tried just using Element mainRootElement = doc.createElement("tns:cmds");, however, this results in the same error.
That's why I figure it has something to do with the namespace declaration, i.e., the : used to do it, as that's the only "invalid" character I could think of in that string.
Can anyone confirm this is the source of the problem? If so, is there an easy solution to it? Can Java DOM use namespaced tags at all?
Edit: Whole method for reference
private void generateScriptXML()
{
DocumentBuilderFactory icFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder icBuilder;
try
{
icBuilder = icFactory.newDocumentBuilder();
Document doc = icBuilder.newDocument();
Element mainRootElement = doc.createElement("tns:cmds xmlns:tns=\"http://abc.de/x/y/z\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://abc.de/x/y/z xyzschema.xsd\"");
doc.appendChild(mainRootElement);
mainRootElement.appendChild(getAttributes(doc,"xxx", "yyy", "zzz"));
mainRootElement.appendChild(getAttributes(doc,"aaa", "bbb", "ccc"));
mainRootElement.appendChild(getAttributes(doc,"ddd", "eee", "fff"));
...
...
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(doc);
StreamResult streamResult = new StreamResult(new File(vfsPath));
transformer.transform(source, streamResult);
}
catch (Exception e)
{
e.printStackTrace();
}
}

Wrong method, try the *NS variants:
Element mainRootElement = doc.createElementNS(
"http://abc.de/x/y/z", // namespace
"tns:cmds" // node name including prefix
);
First argument is the namespace, second the node name including the prefix/alias. Namespace definitions will be added automatically for the namespace if needed. It works to set them as attributes, too.
The namespace in your original source is http://abc.de/x/y/z. With the attribute xmlns:tns="http://abc.de/x/y/z" the alias/prefix tns is defined for the namespace. The DOM api will implicitly add namespaces for nodes created with the *NS methods.
xmlns and xml are reserved/default namespace prefixes for specific namespaces. The namespace for xmlns (namespace definitions) is http://www.w3.org/2000/xmlns/.
To add an xmlns:* attribute with setAttributeNS() use the xmlns namespace:
mainRootElement.setAttributeNS(
"http://www.w3.org/2000/xmlns/", // namespace
"xmlns:xsi", // node name including prefix
"http://www.w3.org/2001/XMLSchema-instance" // value
);
But even that is not needed. Just like for elements, the namespace definition will be added implicitly if you add an attribute node using it.
mainRootElement.setAttributeNS(
"http://www.w3.org/2001/XMLSchema-instance", // namespace
"xsi:schemaLocation", // node name including prefix
"http://abc.de/x/y/z xyzschema.xsd" // value
);
Namespaces Prefixes
If you see a nodename like xsi:schemaLocation you can resolve by looking for the xmlns:xsi attribute. This attribute is the namepace definition. The value is the actual namespace. So if you have an attribute xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" the node name can be resolved to {http://www.w3.org/2001/XMLSchema-instance}schemaLocation (Clark notation).
If you want to create the node you need 3 values:
the namespace: http://www.w3.org/2001/XMLSchema-instance
the local node name: schemaLocation
the prefix: xsi
The prefix is optional for element nodes, but mandatory for attribute nodes. The following three XMLs resolve all to the element node name {http://abc.de/x/y/z}cmds:
<tns:cmds xmlns:tns="http://abc.de/x/y/z"/>
<cmds xmlns="http://abc.de/x/y/z"/>
<other:cmds xmlns:other="http://abc.de/x/y/z"/>

Related

How to add a default name space with dom4j?

I would like to add a default name space to the root element of a XML document using dom4j, like in the following snippet. What is the correct way to do this with dom4j?
<?xml version="1.0" encoding="utf-8" ?>
<animals xmlns="http://zoo.domain.org/schema/animals" >
<animal id="1">
<elephant>
<name>Jumbo</name>
</elephant>
</animal>
</animals>
The dom4j API does provide the method Element#addNamespace but the javadoc tells that the prefix may not be blank.
The following code will result in the expected namespace for animals though:
Document document = DocumentHelper.createDocument();
Element animals = document.addElement("animals")
.addNamespace("", "http://zoo.domain.org/schema/animals");
Element animal = animals.addElement("animal")
.addAttribute("id", "1");
animal.addElement("elephant")
.addElement("name")
.addText("Jumbo");
// write document to file etc.
...
... but the child element animal gets an empty string as default namespace, which is not what I want:
<?xml version="1.0" encoding="UTF-8"?>
<animals xmlns="http://zoo.domain.org/schema/animals">
<animal xmlns="" id="1">
<elephant>
<name>Jumbo</name>
</elephant>
</animal>
</animals>
The method Document#addElement (but also Element#addElement) accepts a second parameter namespaceURI. That does the trick, adding the default namespace to the XML element.
The following code will result in the expected XML.
Document document = DocumentHelper.createDocument();
Element animals = document.addElement("animals", "http://zoo.domain.org/schema/animals");
Element animal = animals.addElement("animal")
.addAttribute("id", "1");
animal.addElement("elephant")
.addElement("name")
.addText("Jumbo");
// write document to file etc.
...
Also worth mentioning is that in case you would like to create an Element on its own DocumentFactory#createElement has an overloaded version that accepts a namespaceURI as well. DocumentHelper#createElement does not have such an overloaded method.
You are not creating the document (root) element.
Document document = DocumentHelper.createDocument();
Element root = document.addElement( "animals" );
// TODO the rest of your code
Eventually, you'll pass your document to an XML writer to write (save) the document. Also, from Javadoc:
Element addNamespace​(java.lang.String prefix, java.lang.String uri)
Adds a namespace to this element for use by its child content
Parameters:
prefix - is the prefix to use, which should not be null or blank
One thing you could try is create an instance of Namespace passing a empty String to the constructor and use the Element.add() method instead. The Javadoc of Namespace doesn't indicate that the prefix can't be blank.

XML with different namespaces drilling down to needed value

I am trying to figure out how to go about getting the value of jxdm:ID from the following XML file:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<My:Message
xmlns:Abcd="http://...."
xmlns:box-1="http://...."
xmlns:bulb="http://...."
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xsi:schemaLocation="http://....stores.xsd">
<Abcd:StoreDataSection>
<Abcd:DataSection>
<Abcd:FirstStore>
<box-1:Response>
<box-1:DataSection>
<box-1:Release>
<box-1:Activity>
<bulb:Date>2017-04-29</bulb:Date>
<bulb:Store xsi:type="TPIR:Organization">
<bulb:StoreID>
<bulb:ID>D79G2102</bulb:ID>
</bulb:StoreID>
</bulb:Store>
</box-1:Activity>
</box-1:Release>
</box-1:DataSection>
</box-1:Response>
</Abcd:FirstStore>
</Abcd:DataSection>
</Abcd:StoreDataSection>
</ My:Message>
I keep getting "null" as the value of node
Node node = (Node) xPath.evaluate(expression, document, XPathConstants.NODE);
This is my current Java code:
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document document = builder.parse(new File("c:/temp/testingNamespace.xml"));
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "//My/Message//Abcd/StoreDataSection/DataSection/FirstStore//box-1/Response/DataSection/Release/Activity//bulb/Store/StoreID/ID";
Node node = (Node) xPath.evaluate(expression, document, XPathConstants.NODE);
node.setTextContent("changed ID");
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(new DOMSource(document), new StreamResult(new File("C:/temp/test-updated.xml")));
} catch (Exception e) {
System.out.println(e.getMessage());
}
How would the correct XPath be formatted in order for me to get that value and change it?
Update 1
So something like this?
String expression = "/My:Message/Abcd:StoreDataSection/Abcd:DataSection/Abcd:FirstStore/box-1:Response/box-1:DataSection/box-1:Release/box-1:Activity/bulb:Store/bulb:StoreID/bulb:ID";
The problem is that you should access to Node by prefix (if you want to) but in a different way, like: //bulb:StoreID if you want to access StorID for example.
Then again it would still not work because you need to tell XPath how to resolve namspaces prefixes.
You should check this answer : How to query XML using namespaces in Java with XPath?
for details on how to implement and use a NamespaceContext.
The bottom line is that you need to implement a javax.xml.namespace.NamespaceContext and set it to the XPath.
XPath xpath = XPathFactory.newInstance().newXPath();
NamespaceContext context = new MyNamespaceContext();
xpath.setNamespaceContext(context);
Two things wrong here:
Your XML is not namespace-well-formed; it does not declare the used namespace prefixes.
Once namespace prefixes are properly declared in the XML and in your Java code, you use them in XPath via : not via /. So, it'd be not /Abcd/StoreDataSection but rather /Abcd:StoreDataSection (and so on for the rest of the steps in your XPath).
See also How does XPath deal with XML namespaces?
I am unable to change anything in the XML so I have to go with it as-is sadly.
Technically you might be able to use some XML tools with undeclared namespaces because this omission only renders the XML only namespace-not-well-formed. Many tools expect not only well-formed but also namespace-well-formed XML. (See Namespace-Well-Formed
for the difference)
Otherwise, see How to parse invalid (bad / not well-formed) XML? to repair your XML.

How to avoid xmlns="" to be added to a manipulated XML root element?

I'm changing the jta-data-source value of a persistence.xml as follows:
JavaArchive jarArchive = Maven.configureResolver().workOffline().resolve("richtercloud:project1-jar:jar:1.0-SNAPSHOT").withoutTransitivity().asSingle(JavaArchive.class);
Node persistenceXml = jarArchive.get("META-INF/persistence.xml");
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document persistenceXmlDocument = documentBuilder.parse(persistenceXml.getAsset().openStream());
//asked
//https://stackoverflow.com/questions/46771622/how-to-create-a-shrinkwrap-persistencedescriptor-from-an-existing-persistence-xm
//for how to manipulate persistence.xml more easily with
//ShrinkWrap's PersistenceDescriptor
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("//persistence-unit/jta-data-source");
org.w3c.dom.Node persistenceXmlDataSourceNode = (org.w3c.dom.Node) expr.evaluate(persistenceXmlDocument,
XPathConstants.NODE);
persistenceXmlDataSourceNode.setTextContent("jdbc/project1-test-db");
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
//transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
//was there before, but unclear why
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(persistenceXmlDocument), new StreamResult(writer));
String persistenceUnit = writer.toString();
(since How to create a ShrinkWrap PersistenceDescriptor from an existing persistence.xml? has not been answered, yet).
That works fine, except for a xmlns="" attribute added to the persistence-unit under the root persistence element which seems to cause:
java.io.IOException: org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 108; Deployment descriptor file META-INF/persistence.xml in archive [project1-jar-1.0-SNAPSHOT.jar]. cvc-complex-type.2.4.a: Invalid content was found starting with element 'persistence-unit'. One of '{"http://xmlns.jcp.org/xml/ns/persistence":persistence-unit}' is expected.
I'm not adhering to the idea to use Transformer and related classes.
No idea why I can't reproduce this in Java SE, but the problem is that javax.xml.parsers.DocumentBuilder by default isn't namespace aware so that the namespace information gets lost during manipulation of the document and consequently an empty xmlns is added by Transformer.
Now that DocumentBuilder is namespace aware, XPath resolution doesn't work and queries return null, see XPath returning null for "Node" when isNameSpaceAware and isValidating are "true" for a detailed description and more details on the XPath namespace awareness issue (I couldn't get the solution to build a custom NamespaceContext to work).
In order to avoid this I finally adjusted my XPath query to use the local-name function as described at How to ignore namespace when selecting XML nodes with XPath. i.e. //*[local-name()='jta-data-source'].
It's still necessary to use Document.createElementNS instead of createElement in order to avoid empty xmlns attribute on newly created elements, see Empty default XML namespace xmlns="" attribute being added? for an explanation.

Adding a namespace to root node is causing it to add the namespace to add the child nodes as well

String retVal = null;
Document document = DocumentHelper.createDocument();
Element documentRoot = document.addElement(LOAD_EVENT);
Element header = documentRoot.addElement(HEADER);
Element body = documentRoot.addElement(DOCUMENTS);
Namespace namespace =
new Namespace("", "http://www.acme.com/LoadEvent");
documentRoot.add(namespace);
This is causing the xmlns to be added to the header and body nodes as well. How do I only have the xmlns added to the root node which is loadEvent ?
Yes, this can be confusing. When a document is represented as a tree of element and other node types, each element belongs to a specific namespace, independently of any other element node, even of its ancestors. This is true for all the "major" XML node oriented API:s (DOM, JDOM, DOM4J and XOM).
Since you created the LOAD_EVENT element to be in no namespace, but later added a default namespace declaration to the node, DOM4J didn't have a choice but to change the namespace for the LOAD_EVENT (this is where DOM4J allows a very confusing action, IMHO). However, the child nodes are still in no namespace (or the empty namespace).
When you later serialize the tree to XML, the default namespace declared at the root node muste be "undeclared" for each child.
My guess is that you want something like this:
String NS = "http://www.acme.com/LoadEvent";
Document document = DocumentHelper.createDocument();
Element documentRoot = document.addElement("load", NS);
Element header = documentRoot.addElement("header", NS);
Element body = documentRoot.addElement("documents", NS);
That is, every element belongs to the NS namespace.
In other words: in tree oriented API:s, an element node does not inherit the namespace of its parent. Each element must be created with the proper namespace, if it has one.

TransformerHandler to output CDATA sections

I am trying to output CDATA section using the below code. While other declarations are being honoured the CDATA sections still comes out as plain text without its enclosing tags (CDATA). What am i doing wrong?
private TransformerHandler getHandler(StringWriter sw) {
SAXTransformerFactory stf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
TransformerHandler th = null;
th = stf.newTransformerHandler();
th.getTransformer().setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "{ns1}elem");
th.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
th.getTransformer().setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
th.setResult(new StreamResult(sw));
}
Try re-reading the JavaDoc section for OutputKeys.CDATA_SECTION_ELEMENTS: http://docs.oracle.com/javase/6/docs/api/javax/xml/transform/OutputKeys.html#CDATA_SECTION_ELEMENTS
... as well as the referenced explanation of how to specify a literal QName http://docs.oracle.com/javase/6/docs/api/javax/xml/transform/package-summary.html#qname-delimiter
The parameter value that you specify "{ns1}elem", does NOT look like it includes a namespace URI to me, but rather looks like a namespace prefix (ns1). Find out what the "xmlns:ns1" declaration is, and include the namespace URI in the literal QName.
Example (assuming the namespace declaration for the ns1 prefix looks like xmlns:ns1="http://softee.org" you should specify;
setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "{http://softee.org}elem");

Categories

Resources