Can I put all namspace definition to top level element with JAXB - java

Using handcrafted code my xml was like this:
<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://musicbrainz.org/ns/mmd-1.0#"
xmlns:ext="http://musicbrainz.org/ns/ext-1.0#">
<artist-list offset="0" count="8">
<artist type="Person" id="00ed154e-8679-42f0-8f42-e59bd7e185af"
ext:score="100">
Now using JAXB which is much better but although the xml is perfectly valid I need to force it to put the xmlns:ext="http://musicbrainz.org/ns/ext#-1.0" within the metadata element not the artist element for compatability with client code that I have no control over.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<metadata xmlns="http://musicbrainz.org/ns/mmd-1.0#">
<artist-list offset="0" count="4">
<artist type="Person" id="00ed154e-8679-42f0-8f42-e59bd7e185af"
ext:score="100" xmlns:ext="http://musicbrainz.org/ns/ext#-1.0">
Can this be done please ?
EDIT:Worked round it with String replace because I only have to deal with one specific case
String xml = sw.toString();
//Remove extension namespace definition
xml=xml.replace("xmlns:ext=\"http://musicbrainz.org/ns/ext#-1.0","");
//Add it to the top instead
xml=xml.replace("<metadata xmlns=\"http://musicbrainz.org/ns/mmd-1.0#\">",
"<metadata xmlns=\"http://musicbrainz.org/ns/mmd-1.0#\" xmlns:ext=\"http://musicbrainz.org/ns/ext-1.0#\">");
//Now write out to the proper output stream
out.write(xml);

I don't think there's a way to do it using JAXB, but here's a quick post-processor using Dom4J:
public static void moveNameSpacesToRoot(Document document) {
final Element rootElement = document.getRootElement();
moveNameSpacesToRootElement(rootElement, rootElement);
}
#SuppressWarnings("unchecked")
private static void moveNameSpacesToRootElement(
Element thisElement, Element rootElement) {
if (!thisElement.equals(rootElement)) {
Namespace namespace = thisElement.getNamespace();
if (!namespace.equals(Namespace.NO_NAMESPACE)) {
Namespace existingRootNamespace =
rootElement.getNamespaceForPrefix(namespace.getPrefix());
if (existingRootNamespace == null) {
rootElement.add(namespace);
}
thisElement.remove(namespace);
}
}
for (Element child : (List<Element>) thisElement.elements()) {
moveNameSpacesToRootElement(child, rootElement);
}
}
Oh, I just realized that you need attributes, not elements. However, the change is trivial, so I'll leave that for you.

There is at least no documented feature in JAXB to control on which element the namespace prefix declaration should be placed. You should however be aware that the two XML snippets are semantically identical (it does not matter if the namespace prefix is declared on the same node or on any ancestor), so you should opt to fix the broken client code or get someone with control of the client code to fix it.

Related

JAXB doesn't report about illegal element duplicate

Suppose I have an annotated Java class:
#XmlRootElement
class Person {
public String name;
public int age;
}
Instances of that class should be created from an XML document. But sometimes XML record does not match well to my POJOs. For example, it may has multiple <name> tags:
<person>
<name>Dr. Jekyll</name>
<name>Mr. Hyde</name>
<age>35</age>
</person>
In such cases I expect to get an error message from unmarshaller. But it actually just quietly re-initializes the property twice. Is there a way to make JAXB notify me about those errors? (Something like VerificationErrorHandler would be a perfect solution. Throwing an exception from the property setter looks too boiler-platy for me)
UPD. My JAXB setup contains no surprise:
var jaxb = JAXBContext.newInstance(Person.class);
var jaxbDecoder = jaxb.createUnmarshaller();
var errorLog = new ArrayList<ValidationEvent>();
jaxbDecoder.setEventHandler(e -> errorLog.add(e));
// Now unmarshal:
var p = (Person) jaxbDecoder.unmarshal(xml);
After that, errorLog is empty despite my XML document is obviously incorrect.

Problem in converting Java objects to XML based on a given XSD file

I am generating a custom XML export in DSpace 5.2. The item that is to be exported as an XML file has an array of metadata values. The values must appear in the XML file as the given XSD file defines their hierarchy. I add the values based on the XSD order into the XML, but some XML tags are in an order different from the insertion order.
More details
The approach I am using is, at first, move the array of metadata values into a map. The keys of the map are the metadata field names. Then, based on the XSD, I get an appropriate value from the map and generate an XML element like this:
import org.dspace.content.Metadatum;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
public class DSpaceXML implements Serializable {
// A member variable
private Document doc;
// A DSpace built-in function used to export an item to an XML
public final void addItem(Item item) throws Exception {
// Initialize this.doc
Element rootElement = doc.createElement("root");
Element higherElement = doc.createElement("higher-element");
Element lowerElement = doc.createElement("lower-element");
insertMetadataAsChildOfElement(higherElement, "child-of-higher", "dc.childOfHigher");
rootElement.appendChild(higherElement);
insertMetadataAsChildOfElement(lowerElement, "child-of-lower", "dc.childOfLower");
rootElement.appendChild(lowerElement);
// stuff to generate other elements of the XML
}
private void insertMetadataAsChildOfElement(Element parentElement, String childElementName,
String key) {
Element childElement;
<Metadatum> metadatumList = (<Metadatum>) metadataMap.get(key);
childElement = createElement(childElementName, metadatum.value);
parentElement.appendChild(childElement);
}
private Element createElement(String name, String value) {
Element el = doc.createElement(name);
el.appendChild(doc.createTextNode(value));
return el;
}
}
I expect an XML like this:
<root>
<higher-element>
<child-of-higher>Value1</child-of-higher>
</higher-element>
<lower-element>
<child-of-lower>Value2</child-of-lower>
</lower-element>
<another-element-1/>
....
<another-element-n/>
</root>
What I get is like this (<lower-element> is before <higher-element>):
<root>
<lower-element>
<child-of-lower>Value2</child-of-lower>
</lower-element>
<another-element-1/>
....
<another-element-k/>
<higher-element>
<child-of-higher>Value1</child-of-higher>
</higher-element>
<another-element-k-plus-1/>
....
<another-element-n/>
</root>
I cannot figure out why this happens while rootElement.appendChild(higherElement) is called before rootElement.appendChild(lowerElement). Also, I would appreciate if someone let me know if my approach is the best one for generating an XML from an XSD.
I figured out that I had a bug in my code. Due to checking a lot of metadata values, many lines after the line rootElement.appendChild(lowerElement), I had a line rootElement.appendChild(higherElement), so it overrode the former hierarchy of XML elements. As a result <higher-element> appeared after <lower-element>. But about the second part of my question, I will be happy if someone would tell me about the best practices of generating an XML based on an XSD regarding the limitations of DSpace 5.

Navigating XML using XPATH with a different namespace

I am struggling to find out how to navigate in to the area of the xml that uses namespaces. Using basic xpath i can navigate to the message detail node fine, but I am not sure what I need to do in terms of getting in to that block as everything inside uses namespaces. Please could someone help?
Thanks
<?xml version="1.0" encoding="UTF-8"?>
<Message>
<MessageList>
<MessageCount>2</MessageCount>
<DateTimeStamp>2016-02-11T12:50:26</DateTimeStamp>
<MessageDetail>
<MessageID>2332445456767</MessageID>
<Env:MessageContainer xmlns:Env="http://www.somesite.com/schema/v1.0/envelope" xmlns:BS="http://www.somesite.com/schema/v1.0/BusinessServices">
<Env:MessageParties>
public List<String> getRefs(String xmlMessageToSend)
{
try
{
Document doc = createDocument(xmlMessageToSend.getBytes());
XPath xpath = xPathFactory.newXPath();
xpath.setNamespaceContext(new NamespaceContext() {
#Override
public String getNamespaceURI(String prefix)
{
if (prefix == null)
throw new NullPointerException("Null prefix");
else if ("Env".equals(prefix))
return "http://www.om.com/schema/v1.0/envelope";
else if ("BS".equals(prefix))
return "http://www.o.com/schema/v1.0/BusinessServices";
return XMLConstants.NULL_NS_URI;
}
#Override
public Iterator getPrefixes(String namespaceURI)
{
throw new UnsupportedOperationException();
}
#Override
public String getPrefix(String namespaceURI)
{
throw new UnsupportedOperationException();
}
});
XPathExpression exp = xpath
.compile("/Message/MessageList/MessageDetail/Env:MessageContainer");
Node result = (Node)exp.evaluate(doc, XPathConstants.NODE);
System.out.println(result.getTextContent());
}
catch (XPathExpressionException | SAXException | IOException | ParserConfigurationException e)
{
e.printStackTrace();
}
return new ArrayList<String>();
}
You don't say what you're using to navigate the document, but generally, there should be a way in the API of whatever you're using for you to declare a namespace prefix that matches the one on Env:MessageContainer. Then you can use that prefix in your XPath, e.g. //e:MessageContainer (assuming you mapped 'e' to "http://www.somesite.com/schema/v1.0/envelope").
You need to use a message prefix in the XPath expression. For example,
//foo:MessageContainer
This prefix need not be the same prefix used for the namespace URI as in the original document. Here I've used the prefix foo even though it was Env in your document. As long as both prefixes map to the same URI (http://www.somesite.com/schema/v1.0/envelope in this example) the XPath will match.
How exactly you bind this prefix to the desired namespace URI varies depending on the host language in which the XPath expression is embedded. In XSLT, for example, you simply declare the relevant prefixes in the XSLT stylesheet, much as you would in any other XML document. In XOM, by contrast, you have to supply an XPathContext object that maps the namespace prefixes accordingly. And so on for other languages and APIs.
You can access your elements via e.g.
//Env:MessageContainer
But to achieve this, your xmlns:Env="http://www.somesite.com/schema/v1.0/envelope" xmlns:BS="http://www.somesite.com/schema/v1.0/BusinessServices" should be defined in the <root> element instead of (or in addition to) the <Env:MessageContainer> element.
But if you cannot change your source XML, the correct solution would be the comprehensive style of writing the same thing as above:
//*[local-name()='MessageContainer'][namespace-uri()='http://www.somesite.com/schema/v1.0/envelope']

Merging multiple XML files in Java

I've been looking for the best way to do this, but I can't seem to find a clear answer how this should be done.
I have an Arraylist of Files in my Java code, representing a list of xml files which should be merged and written to a new XML file. This is not a fixed length list, I'm estimating it would be between 2-10 files. All these files have a very similar document structure, but some attributes should be summed while merging. For example:
File1
<events>
<commandEvents date="2013-07-16">
<commandEvent count="1" commandId="update"/>
<commandEvent count="1" commandId="debug"/>
<commandEvent count="3" commandId="resume"/>
</commandEvents>
</events>
File 2
<events>
<commandEvents date="2013-07-16">
<commandEvent count="2" commandId="resume"/>
</commandEvents>
<commandEvents date="2013-07-15">
<commandEvent count="2" commandId="resume"/>
<commandEvent count="1" commandId="update"/>
</commandEvents>
</events>
Result
<events>
<commandEvents date="2013-07-16">
<commandEvent count="1" commandId="update"/>
<commandEvent count="1" commandId="debug"/>
<commandEvent count="5" commandId="resume"/>
</commandEvents>
<commandEvents date="2013-07-15">
<commandEvent count="2" commandId="resume"/>
<commandEvent count="1" commandId="update"/>
</commandEvents>
</events>
To clarify, the merging should occur on commandEvents[#date]/commandEvent[#commandId].The commandEvent elements have some more attributes, but these are the same for each element so I've omitted them here. Not all dates will be available in each document.
I first found some answers to go the XSLT route, but I'm quite confused about the XSLT syntax to do this.
Although I'm not entirely sure about the size that these files may reach, but I would be highly suprised they would be >1mb, so a Java DOM parser as JDOM or XOM might work as well, but I'd have to load all these files at the same time or iterate in pairs.
What is regarded as the best way to do this? And if XSLT is regarded as the best solution, would it be possible to give me some tips for this?
Here's a simple merge, in which all children of the root node in one document get appended to the root node of a second document:
public static void mergeSecondLevel(Document from, Document to) {
Element fromRoot = from.getDocumentElement();
Element toRoot = to.getDocumentElement();
Node child = null;
while ((child = fromRoot.getFirstChild()) != null) {
to.adoptNode(child);
toRoot.appendChild(child);
}
}
If you're trying to do some sort of processing on the nodes before merging them (you say some attributes should be summed), then this won't be sufficient. There's a linked post that covers using XPath to retrieve nodes, but even then you're going to have to write logic to ensure the correct updates.
Check XmlCombiner which is a Java library that implements XML merging and allows to add the filter in which you can specify the logic for summing the values of the 'count' attribute.
Here is the code for the initialization of the library:
import org.atteo.xmlcombiner.XmlCombiner;
// create combiner specifying the attributes which are used as a keys
XmlCombiner combiner = new XmlCombiner(Lists.newArrayList("date", "commandId"));
// add the filter
combiner.setFilter(filter);
// combine files
combiner.combine(firstFile);
combiner.combine(secondFile);
// store the result
combiner.buildDocument(resultFile);
And here is the code for the filter itself:
XmlCombiner.Filter filter = new XmlCombiner.Filter() {
#Override
public void postProcess(Element recessive, Element dominant, Element result) {
if (recessive == null || dominant == null) {
return;
}
Attr recessiveNode = recessive.getAttributeNode("count");
Attr dominantNode = dominant.getAttributeNode("count");
if (recessiveNode == null || dominantNode == null) {
return;
}
int recessiveValue = Integer.parseInt(recessiveNode.getValue());
int dominantValue = Integer.parseInt(dominantNode.getValue());
result.setAttribute("count", Integer.toString(recessiveValue + dominantValue));
}
};
Disclaimer: I am the author of the XmlCombiner.

JAXB: Get Tag as String

This question may have been answered before in some dark recess of the Interwebs, but I couldn't even figure out how to form a meaningful Google query to search for it.
So: Suppose I have a (simplified) XML document like so:
<root>
<tag1>Value</tag1>
<tag2>Word</tag2>
<tag3>
<something1>Foo</something1>
<something2>Bar</something2>
<something3>Baz</something3>
</tag3>
</root>
I know how to use JAXB to unmarshal this into a Java Object in the standard use cases.
What I don't know how to do is unmarshal tag3's contents wholesale into a String. By which I mean:
<something1>Foo</something1>
<something2>Bar</something2>
<something3>Baz</something3>
as a String, tags and all.
Use annotation #XmlAnyElement.
I've been looking for the same solution and I expected to find some annotation that prevents parsing dom and live it as it is, but did not find it.
Detail at:
Using JAXB to extract inner text of XML element
and
http://blog.bdoughan.com/2011/04/xmlanyelement-and-non-dom-properties.html
I added one cheking in method getElement(), otherwise we could get IndexOutOfBoundsException
if (xml.indexOf(START_TAG) < 0) {
return "";
}
For me it's quite strange behavior with this solution. method getElement() is called for every tag of your xml. The first call is for "Value", the second - "ValueWord", etc. It appends the next tag for previous
update:
I noticed that this approach works only for ONE occurence of tag that we want to parse to String. It's impossible to parse correctly the followint example:
<root>
<parent1>
<tag1>Value</tag1>
<tag2>Word</tag2>
<tag3>
<something1>Foo</something1>
<something2>Bar</something2>
<something3>Baz</something3>
</tag3>
</parent1>
<parent2>
<tag1>Value</tag1>
<tag2>Word</tag2>
<tag3>
<something1>TheSecondFoo</something1>
<something2>TheSecondBar</something2>
<something3>TheSecondBaz</something3>
</tag3>
</parent2>
"tag3" with parent tag "parent2" will contain parameters from the first tag (Foo, Bar, Baz) instead of (TheSecondFoo, TheSecondBar, TheSecondBaz)
Any suggestions are appreciated.
Thanks.
I have an utility method that might come in handy for you in that case. See if it helps. I made a sample code with your example:
public static void main(String[] args){
String text= "<root><tag1>Value</tag1><tag2>Word</tag2><tag3><something1>Foo</something1><something2>Bar</something2><something3>Baz</something3></tag3></root>";
System.out.println(extractTag(text, "<tag3>"));
}
public static String extractTag(String xml, String tag) {
String value = "";
String endTag = "</" + tag.substring(1);
Pattern p = Pattern.compile(tag + "(.*?)" + endTag);
Matcher m = p.matcher(xml);
if (m.find()) {
value = m.group(1);
}
return value;
}

Categories