different values are parsed than original values from xml - java

protected void setUp() throws Exception {
super.setUp();
URL impactsUrl = ImpactsParserTest.class.getResource("test-impacts.xml");
ImpactsParser parser = new ImpactsParser();
this.impacts = parser.parseAsList(impactsUrl);
}
public void testImpactsParsing() {
System.out.println(this.impactInfo = (ImpactInfo)this.impacts.get(0));
System.out.println(this.impactInfo = (ImpactInfo)this.impacts.get(1));
it gives different values than my test xml given below as:
ImpactInfo onObject='impact1' onAction='show' toObject='impacted1' toAction='onshow' value='true' reversible='true'
ConditionInfo: onObject='impact1.dcr1' onAction='show' value='true'
ConditionInfo: onObject='impact1.dcr2' onAction='show' value='false'
ConditionInfo: onObject='impact1.dcr3' onAction='onshow' value='true'
ImpactInfo onObject='impact1.dcr1' onAction='onshow' toObject='impacted1' toAction='onshow' value='true' reversible='true'
ConditionInfo: onObject='impact1' onAction='show' value='true'
ConditionInfo: onObject='impact1.dcr2' onAction='show' value='false'
ConditionInfo: onObject='impact1.dcr3' onAction='onshow' value='true'
whereas xml file contains,
test-impacts.xml:
<impacts>
<impact onObject="impact1" onAction="show" toObject="impacted1" toAction="onshow" value="true">
<condition onObject="impact1.dcr1" onAction="show" value="true"/>
<condition onObject="impact1.dcr2" onAction="show" value="false"/>
<condition onObject="impact1.dcr3" onAction="onshow"/>
</impact>
<impact onObject="impact2" onAction="select" toObject="impacted2" toAction="onselect" value="false">
<condition onObject="impact2.dcr1" onAction="onshow" value="true"/>
<condition onObject="impact2.dcr2" onAction="onselect" value="false"/>
<condition onObject="impact2.dcr3" onAction="show"/>
<condition onObject="impact2.dcr4" onAction="select"/>
</impact>
</impacts>
why i am getting different values than original xml values ?
ImpactsParser.java:
public List<ImpactInfo> parseAsList(URL url) {
SAXReader reader = new SAXReader();
Document doc = reader.read(url);
Element descriptionsTag = doc.getRootElement();
return parseImpacts(descriptionsTag);
}
private List<ImpactInfo> parseImpacts(Element descriptionsTag) {
LinkedList<ImpactInfo> impacts = new LinkedList<ImpactInfo>();
for (Iterator<Element> iter = descriptionsTag.elementIterator(TAG_IMPACT); iter.hasNext();) {
Element ele = iter.next();
// iterate on all onObjects
Set<String> onObjectsSet = getValuesFromElementAttribute(ele, ATT_ON_OBJECT, ",");
Set<String> toObjectsSet = getValuesFromElementAttribute(ele, ATT_TO_OBJECT, ",");
for (Iterator<String> it = onObjectsSet.iterator(); it.hasNext();) {
String onObject = it.next();
for (Iterator<String> itt = toObjectsSet.iterator(); itt.hasNext();) {
ImpactInfo impactInfo = parseImpactInfo(onObject, itt.next(), ele);
impacts.add(impactInfo);
// create inverted impacts for impacts with conditions
if ((impactInfo.getConditions() != null) && (impactInfo.getConditions().size() > 0)) {
impacts.addAll(createInvertedImpacts(impactInfo));
}
}
}
}
return impacts;
}
protected Set<String> getValuesFromElementAttribute(Element element, String attributeName, String separator) {
Set<String> names = new HashSet<String>();
if (element.attribute(attributeName) != null) {
String[] dcrs = element.attribute(attributeName).getValue().split(separator);
for (int index = 0; index < dcrs.length; index++) {
if (dcrs[index] != null) {
names.add(dcrs[index].trim());
}
}
}
return names;
}

Most likely the code parser.parseAsList(impactsUrl) parses differently from what you expect, and this test is detecting an error in the code it's testing.
Post the code for ImpactsParser, and we might be able to help figure out where it's going wrong.
Edit:
Based on the code now posted for ImpactsParser, you're producing many more elements in your list of ImpactInfo items than there are <impact> tags in your document, which may be exactly what you want to do, but may be something you're misunderstanding.
Every time you find an <impact> tag, you're getting sets of onObject and toObject tags from the attributes, looping over the combined pairs (which will be only one in your sample xml shown), and creating one ImpactInfo object for each by a call to a function not shown.
If that object created has "conditions", presumably triggered by the presence of <condition> tags, you're creating another ImpactInfo object for each condition, and adding them to the list. This is what I suspect you may really not want, but I of course don't really know exactly what you want.
It's hard to go further than this in saying what's wrong, because it's not clear to me what the expected correct working is.
What you really ought to do is define in a collection of tests the expected results for different bits of input (using assertions instead of System.out.println) and try to get your tests to pass by modifying the code. Start with the simplest cases.

Related

How to get the values of child nodes in JDOM

I am trying to get a value by parsing an XML document using the JDOM library.
I want to get the values of the driverJar tags, which are child nodes based on the driverJars tag, but I can't get the values.
<connection>
<driverJars>
<driverJar>ojdbc11.jar</driverJar>
<driverJar>orai18n.jar</driverJar>
<driverJar>test.jar</driverJar>
</driverJars>
</connection>
I tried:
(It's done until the document is already loaded.)
if (element.getChild(DRIVER_JARS) != null) {
Element driverJarsElement = element.getChild(DRIVER_JARS);
List<Element> driverJarElementList = driverJarsElement.getChildren(DRIVER_JAR);
for (int i = 0; i < driverJarElementList.size(); i++) {
Element driverJarElement = driverJarElementList.get(i);
System.out.println(driverJarElement.getText()); // [Element: <driverJar/>]
}
}
If it is a child, you can get a value, but since it is a child, if you loop through the value, you cannot get the value of children by each index.
What I tried is the value (marked as a comment) that comes out when I print it with System.out.println.
How can I get the value?
What I want to get from the xml above is the String values of ojdbc11.jar, orai18n.jar, and test.jar.
Full code example
<connection>
<productId>oracle_10g</productId>
<productName>Oracle 9i ~ 21c</productName>
<driverJars>
<driverJar>ojdbc8.jar</driverJar>
<driverJar>orai18n.jar</driverJar>
</driverJars>
</connection>
String productId = element.getChildTextTrim(PRODUCT_ID); // oracle_10g
String productName = element.getChildTextTrim(PRODUCT_NAME); // Oracle 9i ~ 21c
Element driverJarsElement = element.getChild(DRIVER_JARS);
List<Element> driverJarElementList = driverJarsElement.getChildren(DRIVER_JAR);
if (element.getChild(DRIVER_JARS) != null) {
for (int i = 0; i < driverJarElementList.size(); i++) {
description.setDriverJars(new ArrayList<String (Arrays.asList(driverJarElementList.get(i).toString())));
}
}
(The reason I wrote that in setDriverJars is because it is a List.)
the code above is
(1) After loading the document, insert values into the fields declared in the object description.
(2) And make a copy of the object.
(3) Analyze the element and reconstruct the description using the copy.
(The method used to reconstruct the description has a different logic from the method in (1).)
In (1), I want to get values from xml, but I can't get values for multiple child nodes.
While the code in your question is not a minimal, reproducible example, the code below is essentially the same. One difference is that in the below code, I first get the root element from the DOM that is created from the XML file.
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
public class JdomTest {
private static final String DRIVER_JARS = "driverJars";
private static final String DRIVER_JAR = "driverJar";
public static void main(String[] args) {
File xmlFile = new File("connects.xml");
SAXBuilder saxBuilder = new SAXBuilder();
try {
Document doc = saxBuilder.build(xmlFile);
Element root = doc.getRootElement();
Element driverJarsElement = root.getChild(DRIVER_JARS);
List<Element> driverJarElementList = driverJarsElement.getChildren(DRIVER_JAR);
for (int i = 0; i < driverJarElementList.size(); i++) {
Element driverJarElement = driverJarElementList.get(i);
System.out.println(driverJarElement.getText());
}
}
catch (JDOMException | IOException x) {
x.printStackTrace();
}
}
}
Here are the contents of file connects.xml
<connection>
<productId>oracle_10g</productId>
<productName>Oracle 9i ~ 21c</productName>
<driverJars>
<driverJar>ojdbc11.jar</driverJar>
<driverJar>orai18n.jar</driverJar>
</driverJars>
</connection>
And here is the output I get when I run the above code:
ojdbc11.jar
orai18n.jar
My environment is JDK 17.0.4 on Windows 10 (64 bit) and JDOM 2.0.6

XML to JSON in JAVA

I have the following XML file :
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:process id="process_1" isExecutable="true">
<bpmn2:subProcess id="SubProcess_1" name="Sub Process 1">
<bpmn2:task id="Task_4" name="Task 4">
</bpmn2:task>
<bpmn2:task id="Task_2" name="Task 2">
</bpmn2:task>
<bpmn2:startEvent id="StartEvent_2" name="">
</bpmn2:startEvent>
<bpmn2:endEvent id="EndEvent_2" name="">
</bpmn2:endEvent>
<bpmn2:task id="Task_3" name="Task 3">
</bpmn2:task>
</bpmn2:subProcess>
<bpmn2:subProcess id="SubProcess_2" name="Sub Process 2">
<bpmn2:startEvent id="StartEvent_3" name="">
</bpmn2:startEvent>
<bpmn2:endEvent id="EndEvent_3" name="">
</bpmn2:endEvent>
<bpmn2:task id="Task_5" name="Task 5">
</bpmn2:task>
</bpmn2:subProcess>
</bpmn2:process>
I want to generate the Json String for this xml.
I have already writen the code for generating the Json String if inside the <bpmn2:process> </bpmn2:process> tags there is only one <bpmn2:subProcess> node, but now I don't know how to do when I have more than one bpmn2:subProcess node.
My code so far is :
Node nodeSubProcess = getNode("bpmn2:subProcess");
jw = new JSONStringer();
jw.object(); //create a new object for bpmn2:subProcess
generateChildNodesDefinitions(nodeSubProcess);
jw.endObject(); // close the object for bpmn2:subProcess
System.out.println(jw.toString());
And inside the generateChildNodesDefinitions(nodeSubProcess) method I have the code how I want to generate the Json string for my xml file, when I have only one subProcess node:
public static void generateChildNodesDefinitions(Node node) throws JSONException
{
if (node != null && node.hasChildNodes())
{
jw.key("nodes").array();
NodeList childnodelist = node.getChildNodes();
for (int k = 0; k < childnodelist.getLength(); k++)
{
Node childn = childnodelist.item(k);
if (childn.hasAttributes())
{
ArrayList<String> list = (ArrayList<String>) jsonValues.get(childn.getNodeName()); //where jsonValues is e Map where I have defined some new values I want to generate for each node I have inside pbmn2:subProcess node
// get attributes for each childnode
NamedNodeMap nnmchildnodes = childn.getAttributes();
// for each node create a JsonObject
jw.object();
if (list != null && !list.isEmpty())
{
jw.key("stencil").value(list.get(0));
jw.key("category").value("NODE");
if (list.get(1) != null)
{
jw.key(list.get(1)).value(list.get(2));
}
}
jw.endObject();
}
}
jw.endArray();
}
}
Try the JSON in Java API. It does all of the hard work for you.
public String xmlToJSON(String xmlString){
try {
JSONObject xmlJSONObj = XML.toJSONObject(xmlString);
String jsonString = xmlJSONObj.toString(4);
}catch (JSONException je){
je.printStackTrace();
}
return jsonString;
}
You might be better off to use Jackson both for parsing the xml and for writing the json back. Especially if you plan to process your data in java. If you only want a conversion, Jackson might be overkill. If you want your conversion to work in both ways, Jackson might provide an elegant solution. Jackson is meant for json (de-)serialization and can be extended to be used with xml.
Parsing your xml into your data classes is as simple as:
ObjectMapper xmlMapper = new XmlMapper();
YourRootClass yourRootObject = xmlMapper.readValue(xml, YourRootClass.class);
Writing your json back is as simple as:
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(fileOutput, yourRootObject);
You need to annotate your data classes in order for Jackson to work.
Jackson Annotations
Additional XML Annotations

read, replace placeholders and write a word file with docx4j

I have a Word File look like this:
You don't need to understand the content, just take a look to my placeholders <env> and <applikationsabkürzung>. There are 10 pages with these placeholders and now I should replace them with other content. The black and yellow box are company pictures which I won't share.
Now I started to read the whole docx4j doc and generate after some time the following code:
public void manipulateWord(String path, String env, String appl) {
try {
WordprocessingMLPackage wpml = WordprocessingMLPackage.load(new File(path));
MainDocumentPart mdp = wpml.getMainDocumentPart();
List<Object> content = mdp.getContent();
// Include all Strings to replace
HashMap<String, String> mappings = new HashMap<String, String>();
mappings.put("<env>", env);
mappings.put("<applikationsabkürzung>", appl);
for (Object object : content) {
Text textElement = (Text) object;
String textToReplace = textElement.getValue();
if (mappings.keySet().contains(textToReplace)) {
textElement.setValue(mappings.get(textToReplace));
}
}
wpml.save(new File("C:\\Users\\kristina\\Desktop\\outputfile.docx"));
} catch (Docx4JException e) {
LOG.error(e);
}
Some explanaition:
String path is the path of the file in the picture above
String env is the value which should replace <env>
String appl is the value which should replace <applikationsabkürzung>
But when I run the method, nothing happen, my console just print just some infos. If they're important, i'll edit the post, but i don't think so.
So where is my fault? Would it work like that? I'm shortly before to despair...
MainDocumentPart.getContent() will return all OpenXml components within the main document flow (things like headers and footers have their own elements). Your code is assuming that the result of List<Object> content will be a collection of Text elements, which is not necessarily the case. For example, a typical (simple) document structure would be like this:
P // Paragraph element
-> R // Run element
-> Text // Text element
… so getContent() is, in all likelihood, going to spit out a load of P objects for a start.
There are a few ways to traverse docx4 files -- see the main docx4j site for more -- but one approach is shown in the method below. You can pass in MaindocumentPart as the first Object, and Text.class as the object type to search for. This should then assist in identifying all Text elements which contain one of your mapping values:
public List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
List<Object> result = new ArrayList<Object>();
if (obj instanceof JAXBElement)
obj = ((JAXBElement<?>) obj).getValue();
if (obj.getClass().equals(toSearch))
result.add(obj);
else if (obj instanceof ContentAccessor) {
List<?> children = ((ContentAccessor) obj).getContent();
for (Object child : children) {
result.addAll(getAllElementFromObject(child, toSearch));
}
}
return result;
}

Compare two XML strings ignoring element order

Suppose I have two xml strings
<test>
<elem>a</elem>
<elem>b</elem>
</test>
<test>
<elem>b</elem>
<elem>a</elem>
</test>
How to write a test that compares those two strings and ignores the element order?
I want the test to be as short as possible, no place for 10-line XML parsing etc. I'm looking for a simple assertion or something similar.
I have this (which doesn't work)
Diff diff = XMLUnit.compareXML(expectedString, actualString);
XMLAssert.assertXMLEqual("meh", diff, true);
For xmlunit 2.0 (I was looking for this) it is now done, by using DefaultNodeMatcher
Diff diff = Diffbuilder.compare(Input.fromFile(control))
.withTest(Input.fromFile(test))
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.build()
Hope this helps this helps other people googling...
XMLUnit will do what you want, but you have to specify the elementQualifier. With no elementQualifier specified it will only compare the nodes in the same position.
For your example you want an ElementNameAndTextQualifer, this considers a node similar if one exists that matches the element name and it's text value, something like :
Diff diff = new Diff(control, toTest);
// we don't care about ordering
diff.overrideElementQualifier(new ElementNameAndTextQualifier());
XMLAssert.assertXMLEqual(diff, true);
You can read more about it here: http://xmlunit.sourceforge.net/userguide/html/ar01s03.html#ElementQualifier
My original answer is outdated. If I would have to build it again i would use xmlunit 2 and xmlunit-matchers. Please note that for xml unit a different order is always 'similar' not equals.
#Test
public void testXmlUnit() {
String myControlXML = "<test><elem>a</elem><elem>b</elem></test>";
String expected = "<test><elem>b</elem><elem>a</elem></test>";
assertThat(myControlXML, isSimilarTo(expected)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)));
//In case you wan't to ignore whitespaces add ignoreWhitespace().normalizeWhitespace()
assertThat(myControlXML, isSimilarTo(expected)
.ignoreWhitespace()
.normalizeWhitespace()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)));
}
If somebody still want't to use a pure java implementation here it is. This implementation extracts the content from xml and compares the list ignoring order.
public static Document loadXMLFromString(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(xml));
return builder.parse(is);
}
#Test
public void test() throws Exception {
Document doc = loadXMLFromString("<test>\n" +
" <elem>b</elem>\n" +
" <elem>a</elem>\n" +
"</test>");
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile("//test//elem");
NodeList all = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
List<String> values = new ArrayList<>();
if (all != null && all.getLength() > 0) {
for (int i = 0; i < all.getLength(); i++) {
values.add(all.item(i).getTextContent());
}
}
Set<String> expected = new HashSet<>(Arrays.asList("a", "b"));
assertThat("List equality without order",
values, containsInAnyOrder(expected.toArray()));
}
Cross-posting from Compare XML ignoring order of child elements
I had a similar need this evening, and couldn't find something that fit my requirements.
My workaround was to sort the two XML files I wanted to diff, sorting alphabetically by the element name. Once they were both in a consistent order, I could diff the two sorted files using a regular visual diff tool.
If this approach sounds useful to anyone else, I've shared the python script I wrote to do the sorting at http://dalelane.co.uk/blog/?p=3225
Just as an example of how to compare more complex xml elements matching based on equality of attribute name. For instance:
<request>
<param name="foo" style="" type="xs:int"/>
<param name="Cookie" path="cookie" style="header" type="xs:string" />
</request>
vs.
<request>
<param name="Cookie" path="cookie" style="header" type="xs:string" />
<param name="foo" style="query" type="xs:int"/>
</request>
With following custom element qualifier:
final Diff diff = XMLUnit.compareXML(controlXml, testXml);
diff.overrideElementQualifier(new ElementNameAndTextQualifier() {
#Override
public boolean qualifyForComparison(final Element control, final Element test) {
// this condition is copied from super.super class
if (!(control != null && test != null
&& equalsNamespace(control, test)
&& getNonNamespacedNodeName(control).equals(getNonNamespacedNodeName(test)))) {
return false;
}
// matching based on 'name' attribute
if (control.hasAttribute("name") && test.hasAttribute("name")) {
if (control.getAttribute("name").equals(test.getAttribute("name"))) {
return true;
}
}
return false;
}
});
XMLAssert.assertXMLEqual(diff, true);
For me, I also needed to add the method : checkForSimilar() on the DiffBuilder.
Without it, the assert was in error saying that the sequence of the nodes was not the same (the position in the child list was not the same)
My code was :
Diff diff = Diffbuilder.compare(Input.fromFile(control))
.withTest(Input.fromFile(test))
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.checkForSimilar()
.build()
I don't know what versions they took for the solutions, but nothing worked (or was simple at least) so here's my solution for who had the same pains.
P.S. I hate people to miss the imports or the FQN class names of static methods
#Test
void given2XMLS_are_different_elements_sequence_with_whitespaces(){
String testXml = "<struct><int>3</int> <boolean>false</boolean> </struct>";
String expected = "<struct><boolean>false</boolean><int>3</int></struct>";
XmlAssert.assertThat(testXml).and(expected)
.ignoreWhitespace()
.normalizeWhitespace()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.areSimilar();
}
OPTION 1
If the XML code is simple, try this:
String testString = ...
assertTrue(testString.matches("(?m)^<test>(\\s*<elem>(a|b)</elem>\\s*){2}</test>$"));
OPTION 2
If the XML is more elaborate, load it with an XML parser and compare the actual nodes found with you reference nodes.
I end up rewriting the xml and comparing it back. Let me know if it helps any of you who stumbled on this similar issue.
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;
public class XmlRewriter {
private static String rewriteXml(String xml) throws Exception {
SAXBuilder builder = new SAXBuilder();
Document document = builder.build(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
Element root = document.getRootElement();
XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat());
root.sortChildren((o1, o2) -> {
if(!StringUtils.equals(o1.getName(), o2.getName())){
return o1.getName().compareTo(o2.getName());
}
// get attributes
int attrCompare = transformToStr(o1.getAttributes()).compareTo(transformToStr(o2.getAttributes()));
if(attrCompare!=0){
return attrCompare;
}
if(o1.getValue()!=null && o2.getValue()!=null){
return o1.getValue().compareTo(o2.getValue());
}
return 0;
});
return xmlOutputter.outputString(root);
}
private static String transformToStr(List<Attribute> attributes){
return attributes.stream().map(e-> e.getName()+":"+e.getValue()).sorted().collect(Collectors.joining(","));
}
public static boolean areXmlSimilar(String xml1, String xml2) throws Exception {
Diff diff = DiffBuilder.compare(rewriteXml(xml1)).withTest(rewriteXml(xml2))
.normalizeWhitespace()
.ignoreWhitespace()
.ignoreComments()
.checkForSimilar()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
.build();
return !diff.hasDifferences();
}
// move below into another test class..
#Test
public void compareXml() throws Exception {
String xml1 = "<<your first XML str here>>";
String xml2 = "<<another XML str here>>";
assertTrue(XmlUtil.areXmlSimilar(xml1, xml2));
}
}

How to insert/replace XML tag in XmlDocument?

I have a XmlDocument in java, created with the Weblogic XmlDocument parser.
I want to replace the content of a tag in this XMLDocument with my own data, or insert the tag if it isn't there.
<customdata>
<tag1 />
<tag2>mfkdslmlfkm</tag2>
<location />
<tag3 />
</customdata>
For example I want to insert a URL in the location tag:
<location>http://something</location>
but otherwise leave the XML as is.
Currently I use a XMLCursor:
XmlObject xmlobj = XmlObject.Factory.parse(a.getCustomData(), options);
XmlCursor xmlcur = xmlobj.newCursor();
while (xmlcur.hasNextToken()) {
boolean found = false;
if (xmlcur.isStart() && "schema-location".equals(xmlcur.getName().toString())) {
xmlcur.setTextValue("http://replaced");
System.out.println("replaced");
found = true;
} else if (xmlcur.isStart() && "customdata".equals(xmlcur.getName().toString())) {
xmlcur.push();
} else if (xmlcur.isEnddoc()) {
if (!found) {
xmlcur.pop();
xmlcur.toEndToken();
xmlcur.insertElementWithText("schema-location", "http://inserted");
System.out.println("inserted");
}
}
xmlcur.toNextToken();
}
I tried to find a "quick" xquery way to do this since the XmlDocument has an execQuery method, but didn't find it very easy.
Do anyone have a better way than this? It seems a bit elaborate.
How about an XPath based approach? I like this approach as the logic is super-easy to understand. The code is pretty much self-documenting.
If your xml document is available to you as an org.w3c.dom.Document object (as most parsers return), then you could do something like the following:
// get the list of customdata nodes
NodeList customDataNodeSet = findNodes(document, "//customdata" );
for (int i=0 ; i < customDataNodeSet.getLength() ; i++) {
Node customDataNode = customDataNodeSet.item( i );
// get the location nodes (if any) within this one customdata node
NodeList locationNodeSet = findNodes(customDataNode, "location" );
if (locationNodeSet.getLength() > 0) {
// replace
locationNodeSet.item( 0 ).setTextContent( "http://stackoverflow.com/" );
}
else {
// insert
Element newLocationNode = document.createElement( "location" );
newLocationNode.setTextContent("http://stackoverflow.com/" );
customDataNode.appendChild( newLocationNode );
}
}
And here's the helper method findNodes that does the XPath search.
private NodeList findNodes( Object obj, String xPathString )
throws XPathExpressionException {
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile( xPathString );
return (NodeList) expression.evaluate( obj, XPathConstants.NODESET );
}
How about an object oriented approach? You could deserialise the XML to an object, set the location value on the object, then serialise back to XML.
XStream makes this really easy.
For example, you would define the main object, which in your case is CustomData (I'm using public fields to keep the example simple):
public class CustomData {
public String tag1;
public String tag2;
public String location;
public String tag3;
}
Then you initialize XStream:
XStream xstream = new XStream();
// if you need to output the main tag in lowercase, use the following line
xstream.alias("customdata", CustomData.class);
Now you can construct an object from XML, set the location field on the object and regenerate the XML:
CustomData d = (CustomData)xstream.fromXML(xml);
d.location = "http://stackoverflow.com";
xml = xstream.toXML(d);
How does that sound?
If you don't know the schema the XStream solution probably isn't the way to go. At least XStream is on your radar now, might come in handy in the future!
You should be able to do this with query
try
fn:replace(string,pattern,replace)
I am new to xquery myself and I have found it to be a painful query language to work with, but it does work quiet well once you get over the initial learning curve.
I do still wish there was an easier way which was as efficient?

Categories