Reducing code redundancy while creating XML with XOM - java

I am using XOM as my XML parsing library. And i am using this for creating XML also. Below is the scenario described with example.
Scenario:
Code:
Element root = new Element("atom:entry", "http://www.w3c.org/Atom");
Element city = new Element("info:city", "http://www.myinfo.com/Info");
city.appendChild("My City");
root.appendChild(city);
Document d = new Document(root);
System.out.println(d.toXML());
Generated XML:
<?xml version="1.0"?>
<atom:entry xmlns:atom="http://www.w3c.org/Atom">
<info:city xmlns:info="http://www.myinfo.com/Info">
My City
</info:city>
</atom:entry>
Notice in the XML that here info namespace is added with the node itself. But I need this to be added in root element. like below
<?xml version="1.0"?>
<atom:entry xmlns:atom="http://www.w3c.org/Atom" xmlns:info="http://www.myinfo.com/Info">
<info:city>
My City
</info:city>
</atom:entry>
And to do that, i just need following piece of code
Element root = new Element("atom:entry", "http://www.w3c.org/Atom");
=> root.addNamespaceDeclaration("info", "http://www.myinfo.com/Info");
Element city = new Element("info:city", "http://www.myinfo.com/Info");
... ... ...
Problem is here i had to add http://www.myinfo.com/Info twice. And in my case there are hundreds of namespaces. So there will so too much redendancy. Is there any way to get rid of this redundancy?

No, there is no way to get rid of this redundancy and that's a deliberate decision. In XOM the namespace is a fundamental part of the element itself, not a function of its position in the document.
Of course you could always declare a named constant for the namespace URI.

Related

adding mutiple namespace for a element in xml file using dom4j

The XML file which need to generate:
<?xml version="1.0" encoding="UTF-8" ?>
<wrapper:MMSRMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wrapper="urn:iso:std:iso:20022:tech:xsd:head.003.001.01" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:head.003.001.01 MMSR_head.003.001.01_Wrapper.xsd">
<header:AppHdr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:header="urn:iso:std:iso:20022:tech:xsd:head.001.001.01">
<header:Fr>
...
</header:Fr>
</header:AppHdr>
</wrapper:MMSRMessage>
Two namespaces were added for the root element "wrapper:MMSRMessage",It has no problem.
The following is the Java code for it:
Document document = DocumentHelper.createDocument();
Element wrapper = document.addElement("wrapper:MMSRMessage");
wrapper.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
.addNamespace("wrapper", "urn:iso:std:iso:20022:tech:xsd:head.003.001.01")
.addAttribute("xsi:schemaLocation", "urn:iso:std:iso:20022:tech:xsd:head.003.001.01 MMSR_head.003.001.01_Wrapper.xsd");
However, when I add two namespaces for element "header:AppHdr", I get the error message:
Exception in thread "main" org.dom4j.IllegalAddException: No such namespace prefix
using java code:
Element headerApp = wrapper.addElement("header:AppHdr");
headerApp.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
.addNamespace("header", "urn:iso:std:iso:20022:tech:xsd:head.001.001.01");
I also have tried so:
Element headerApp = wrapper.addElement("header:AppHdr","urn:iso:std:iso:20022:tech:xsd:head.001.001.01")
.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
in this way the error does not occur, but the namespace "xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" can not be added for the element "header:AppHdr".
That's my first question at Stackoverflow. I hope I can get an answer hier :-)
DOM4J generally offers too many ways to skin the cat. In this area, the confusion is increased because Document#addElement(String, String) and Element#addElement(String, String) do very different validations: In the first case you can add an element with a qualified name without having the prefix bound to a namespace and the element ends up having no namespace (this is a bug). In the second case you must have the prefix bound (correct).
All in all, I reccomend not using qualified element and attribute names (prefix:local-name) if you can avoid it. Instead, strictly separate the local name of the element or attribute and use properly declared Namespace and QName constructs. In your case:
Document document = DocumentHelper.createDocument();
Namespace xsi = Namespace.get("xsi", "http://www.w3.org/2001/XMLSchema-instance");
Namespace wrapper = Namespace.get("wrapper", "urn:iso:std:iso:20022:tech:xsd:head.003.001.01");
Namespace header = Namespace.get("header", "urn:iso:std:iso:20022:tech:xsd:head.001.001.01");
Element wrapperElement = document
.addElement(new QName("MMSRMessage", wrapper))
.addAttribute(new QName("schemaLocation", xsi), "urn:iso:std:iso:20022:tech:xsd:head.003.001.01 MMSR_head.003.001.01_Wrapper.xsd");
Element headerApp = wrapperElement.addElement(new QName("AppHdr", header));
headerApp.addElement(new QName("Fr", header));

The same Namespace in Child and Sub Child (JDOM2)

I need set a namespace in a child and in its child too, but, when I attribute the same namespace, the sub child comes with no namespace.
I need something like that:
<?xml version="1.0" encoding="UTF-8"?>
<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="2.00">
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
<infNFe versao="2.00" Id="NFe35120810609770000190550010000011151000011155">
...
But my code is generating only this:
<?xml version="1.0" encoding="UTF-8"?>
<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="2.00">
<NFe>
<infNFe versao="2.00" Id="NFe35120810609770000190550010000011151000011155">
...
The code that generates this part of the XML is:
Document doc = new Document();
Namespace portal = Namespace.getNamespace( "http://www.portalfiscal.inf.br/nfe" );
Element tagNfeProc = new Element( "nfeProc", portal );
tagNfeProc.setAttribute( "versao", "2.00" );
Element tagNFe = new Element( "NFe", portal );
...
tagNfeProc.getChildren().add( tagNFe );
doc.setRootElement( tagNfeProc );
If you really want to add it, you have to do it manually, use the 'setAttribute' method mentioned before me.
Otherwise:
The 'NFe' tag doesn't require the namespace attribute again, because that namespace is already declared on a higher level (top level in your example), and it will be used (inherited) within the entire block just where you declared it.
After a little search, a brief description about
XML namespaces :)

Groovy: how to parse xml and preserve namespaces and schemaLocations

I'm trying to use groovy to simply add a node to a at a particular location. My source schema looks like this
<s1:RootNode
xmlns:s1="http://localhost/s1schema"
xmlns:s2="http://localhost/s2schema"
xsi:schemaLocation="http://localhost/s1schema s1schema.xsd
http://localhost/s2schema s2schema.xsd">
<s1:aParentNode>
<s2:targetNode>
<s2:childnode1 />
<s2:childnode2 />
<s2:childnode3 />
<s2:childnode4 />
</s2:targetNode>
</s1:aParentNode>
</s1:RootNode>
I'd like to simply add a new child node inline with the other ones to make the output
<s1:RootNode
xmlns:s1="http://localhost/s1schema"
xmlns:s2="http://localhost/s2schema"
xsi:schemaLocation="http://localhost/s1schema s1schema.xsd
http://localhost/s2schema s2schema.xsd">
<s1:aParentNode>
<s2:targetNode>
<s2:childnode1 />
<s2:childnode2 />
<s2:childnode3 />
<s2:childnode4 />
<s2:childnode5 >value</s2:childnode5>
</s2:targetNode>
</s1:aParentNode>
</s1:RootNode>
To do this i have the following simple groovy script
def data = 'value'
def root = new XmlSlurper(false,true).parseText( sourceXML )
root.'aParentNode'.'topNode'.appendNode{
's2:childnode5' data
}
groovy.xml.XmlUtil.serialize(root);
however when i do this the namespaces and schemaLocations that are applied to the root node are being removed. and the namespace, but not the schema location is being added to each of the child nodes.
this is causing validation issues downstream.
How do i simply process this xml. perform no validation and leave the xml as is and add a single node of a namespace i specify?
One note: we process many messages and i won't know in advance the outer most namespace (s1 in the above example) but even with that, i'm really just lookign for a technique that is a "dumber" processing of xml
Thanks!
First, I had to add xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" to define your xsi namespace. Without it I would receive a SAXParseException for the unbound xsi prefix.
Additionally, I consulted this question on successfully appending a namespaced xml node to an existing document.
Finally, we had to utilize the StreamingMarkupBuilder to work around the moving of the namespaces. Bascially, by default the serializer moves the referenced namespaces to the first node that actually uses the namespace. In your case it was moving your s2 namespace attribute to the "targetNode" tag. The following code produces the results you want, but you will still have to know the correct namespaces to use to instantiate the StreamingMarkupBuilder.
def root = new XmlSlurper(false, true).parseText( sourceXML )
def data = '<s2:childnode5 xmlns:s2="http://localhost/s2schema">value</s2:childnode5>'
def xmlFragment = new XmlSlurper(false, true).parseText(data)
root.'aParentNode'.'targetNode'.appendNode(xmlFragment);
def outputBuilder = new StreamingMarkupBuilder()
String result = XmlUtil.serialize(outputBuilder.bind {
mkp.declareNamespace('s1':"http://localhost/s1schema")
mkp.declareNamespace('s2':"http://localhost/s2schema")
mkp.yield root }
)
XMLSlurper (or XMLParser) does not handle namespaces if you set the second parameter of the constructor:
XmlSlurper (boolean validating, boolean namespaceAware)
to false:
def root = new XmlSlurper(false, false).parseText( sourceXML )
Without setting namespaceAware to false, I also faced strange bahavior of the parser. After setting to false, it leaves the XML as is, with no namespace changes.

Retrieve value of attribute using XPath

I am trying to retrieve the value of an attribute from an xmel file using XPath and I am not sure where I am going wrong..
This is the XML File
<soapenv:Envelope>
<soapenv:Header>
<common:TestInfo testID="PI1" />
</soapenv:Header>
</soapenv:Envelope>
And this is the code I am using to get the value. Both of these return nothing..
XPathBuilder getTestID = new XPathBuilder("local-name(/*[local-name(.)='Envelope']/*[local-name(.)='Header']/*[local-name(.)='TestInfo'])");
XPathBuilder getTestID2 = new XPathBuilder("Envelope/Header/TestInfo/#testID");
Object doc2 = getTestID.evaluate(context, sourceXML);
Object doc3 = getTestID2.evaluate(context, sourceXML);
How can I retrieve the value of testID?
However you're iterating within the java, your context node is probably not what you think, so remove the "." specifier in your local-name(.) like so:
/*[local-name()='Header']/*[local-name()='TestInfo']/#testID worked fine for me with your XML, although as akaIDIOT says, there isn't an <Envelope> tag to be seen.
The XML file you provided does not contain an <Envelope> element, so an expression that requires it will never match.
Post-edit edit
As can be seen from your XML snippet, the document uses a specific namespace for the elements you're trying to match. An XPath engine is namespace-aware, meaning you'll have to ask it exactly what you need. And, keep in mind that a namespace is defined by its uri, not by its abbreviation (so, /namespace:element doesn't do much unless you let the XPath engine know what the namespace namespace refers to).
Your first XPath has an extra local-name() wrapped around the whole thing:
local-name(/*[local-name(.)='Envelope']/*[local-name(.)='Header']
/*[local-name(.)='TestInfo'])
The result of this XPath will either be the string value "TestInfo" if the TestInfo node is found, or a blank string if it is not.
If your XML is structured like you say it is, then this should work:
/*[local-name()='Envelope']/*[local-name()='Header']/*[local-name()='TestInfo']/#testID
But preferably, you should be working with namespaces properly instead of (ab)using local-name(). I have a post here that shows how to do this in Java.
If you don't care for the namespaces and use an XPath 2.0 compatible engine, use * for it.
//*:Header/*:TestInfo/#testID
will return the desired input.
It will probably be more elegant to register the needed namespaces (not covered here, depends on your XPath engine) and query using these:
//soapenv:Header/common:TestInfo/#testID

Java appending XML docs to existing docs

I have two XML docs that I've created and I want to combine these two inside of a new envelope. So I have
<alert-set>
<warning>National Weather Service...</warning>
<start-date>5/19/2009</start-date>
<end-date>5/19/2009</end-date>
</alert-set>
and
<weather-set>
<chance-of-rain type="percent">31</chance-of-rain>
<conditions>Partly Cloudy</conditions>
<temperature type="Fahrenheit">78</temperature>
</weather-set>
What I'd like to do is combine the two inside a root node: < DataSet> combined docs < /DataSet>
I've tried creating a temporary doc and replacing children with the root nodes of the documents:
<DataSet>
<blank/>
<blank/>
</DataSet>
And I was hoping to replace the two blanks with the root elements of the two documents but I get "WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it." I tried adopting and importing the root nodes but I get the same error.
Is there not some easy way of combining documents without having to read through and create new elements for each node?
EDIT: Sample code snippets
Just trying to move one to the "blank" document for now... The importNode and adoptNode functions cannot import/adopt Document nodes, but they can't import the element node and its subtree... or if it does, it does not seem to work for appending/replacing still.
Document xmlDoc; //created elsewhere
Document weather = getWeather(latitude, longitude);
Element weatherRoot = weather.getDocumentElement();
Node root = xmlDoc.getDocumentElement();
Node adopt = weather.adoptNode(weatherRoot);
Node imported = weather.importNode(weatherRoot, true);
Node child = root.getFirstChild();
root.replaceChild(adopt, child); //initially tried replacing the <blank/> elements
root.replaceChild(imported, child);
root.appendChild(adopt);
root.appendChild(imported);
root.appendChild(adopt.cloneNode(true));
All of these throw the DOMException: WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it.
I think I'll have to figure out how to use stax or just reread the documents and create new elements... That kinda seems like too much work just to combine documents, though.
It's a bit tricky, but the following example runs:
public static void main(String[] args) {
DocumentImpl doc1 = new DocumentImpl();
Element root1 = doc1.createElement("root1");
Element node1 = doc1.createElement("node1");
doc1.appendChild(root1);
root1.appendChild(node1);
DocumentImpl doc2 = new DocumentImpl();
Element root2 = doc2.createElement("root2");
Element node2 = doc2.createElement("node2");
doc2.appendChild(root2);
root2.appendChild(node2);
DocumentImpl doc3 = new DocumentImpl();
Element root3 = doc3.createElement("root3");
doc3.appendChild(root3);
// root3.appendChild(root1); // Doesn't work -> DOMException
root3.appendChild(doc3.importNode(root1, true));
// root3.appendChild(root2); // Doesn't work -> DOMException
root3.appendChild(doc3.importNode(root2, true));
}
I know you got the issue solved already, but I still wanted to take a stab at this problem using the XOM library that I'm currently testing out (related to this question), and while doing that, offer a different approach than that of Andreas_D's answer.
(To simplify this example, I put your <alert-set> and <weather-set> into separate files, which I read into nu.xom.Document instances.)
import nu.xom.*;
[...]
Builder builder = new Builder();
Document alertDoc = builder.build(new File("src/xomtest", "alertset.xml"));
Document weatherDoc = builder.build(new File("src/xomtest", "weatherset.xml"));
Document mainDoc = builder.build("<DataSet><blank/><blank/></DataSet>", "");
Element root = mainDoc.getRootElement();
root.replaceChild(
root.getFirstChildElement("blank"), alertDoc.getRootElement().copy());
root.replaceChild(
root.getFirstChildElement("blank"), weatherDoc.getRootElement().copy());
The key is to make a copy of the elements to be inserted into mainDoc; otherwise you'll get a complain that "child already has a parent".
Outputting mainDoc now gives:
<?xml version="1.0" encoding="UTF-8"?>
<DataSet>
<alert-set>
<warning>National Weather Service...</warning>
<start-date>5/19/2009</start-date>
<end-date>5/19/2009</end-date>
</alert-set>
<weather-set>
<chance-of-rain type="percent">31</chance-of-rain>
<conditions>Partly Cloudy</conditions>
<temperature type="Fahrenheit">78</temperature>
</weather-set>
</DataSet>
To my delight, this turned out to be very straight-forward to do with XOM. It only took a few minutes to write this, even though I'm definitely not very experienced with the library yet. (It would have been even easier without the <blank/> elements, i.e., starting with simply <DataSet></DataSet>.)
So, unless you have compelling reasons for using only the standard JDK tools, I warmly recommend trying out XOM as it can make XML handling in Java much more pleasant.

Categories

Resources