I need to read an XML file and comment or uncomment some elements to it based on some conditions. The file starts off like this:
<elements>
<!-- <element1 atribute="value"/> -->
<!-- <element2 atribute="value"/> -->
<!-- <element3 atribute="value"/> -->
<!-- <element4 atribute="value"/> -->
<!-- <element5 atribute="value"/> -->
</elements>
If I want to activate element1
, element3
and element5
, the file should look like this:
<elements>
<element1 atribute="value"/>
<!-- <element2 atribute="value"/> -->
<element3 atribute="value"/>
<!-- <element4 atribute="value"/> -->
<element5 atribute="value"/>
</elements>
In other words, I'm looking for a way to add or remove <!--
-->
tags from each XML line that meets the conditions.
Unfortunately, this behavior is needed, and cannot be changed.
I think reading commented and uncommented makes this problem complex. Simpler way would be adding attribute by which you can either activate tag or deactivate. No workaround would be required just you would require to mark it true or false.
For Example:
<elements>
<!-- <element1 atribute="value"/> -->
<!-- <element2 atribute="value"/> -->
<!-- <element3 atribute="value"/> -->
<!-- <element4 atribute="value"/> -->
<!-- <element5 atribute="value"/> -->
</elements>
could be transformed into to.
<elements>
<element1 atribute="value" isActive="false"/>
<element2 atribute="value" isActive="false"/>
<element3 atribute="value" isActive="false"/>
<element4 atribute="value" isActive="false"/>
<element5 atribute="value" isActive="false"/>
</elements>
Similarly, below
<?xml version="1.0" encoding="UTF-8"?>
<elements>
<element1 atribute="value"/>
<!--<element2 atribute="value"/>-->
<element3 atribute="value"/>
<!--<element4 atribute="value"/>-->
<element5 atribute="value"/>
</elements>
could be transformed into to.
<elements>
<element1 atribute="value" isActive="true"/>
<element2 atribute="value" isActive="false"/>
<element3 atribute="value" isActive="true"/>
<element4 atribute="value" isActive="false"/>
<element5 atribute="value" isActive="true"/>
</elements>
This could be optimized way of solving this problem. Now, you might use JAXB and mark element active or inactive in stead of commenting and un-commenting.
If this doesn't makes your life easier there is always workaround using regex, xslt etc..
For such need I would clearly suggest XSLT
as it is somehow an XML transformation
and XSLT
has been created to transform XML
content.
I would then use a template of a stylesheet that is meant to be used as a String format like this:
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='2.0'>
<xsl:template match='/'>
<elements>
<xsl:apply-templates select="elements/element1" mode="%s"/>
<xsl:apply-templates select="elements/element2" mode="%s"/>
<xsl:apply-templates select="elements/element3" mode="%s"/>
<xsl:apply-templates select="elements/element4" mode="%s"/>
<xsl:apply-templates select="elements/element5" mode="%s"/>
</elements>
</xsl:template>
<xsl:template match='*' mode='normal'>
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match='*' mode='comment'>
<xsl:text disable-output-escaping="yes"><!--</xsl:text><xsl:copy-of select="."/>--<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
</xsl:stylesheet>
As you can see there are 2 modes:
normal
it will simply copy the content of the nodecomment
it will comment its contentSo in case we activate element1
, element3
and element5
, the real content of our stylesheet will be String.format(template, "normal", "comment", "normal", "comment", "normal")
In the code snippet below, I use jcabi-xml as it is very easy to use but you are free to use another library if you wish, XSLT
is a standard so it will still work.
XML first = new XMLDocument(
"<elements>\n" +
" <element1 atribute=\"value\"/>\n" +
" <element2 atribute=\"value\"/>\n" +
" <element3 atribute=\"value\"/>\n" +
" <element4 atribute=\"value\"/>\n" +
" <element5 atribute=\"value\"/>\n" +
"</elements>"
);
String template = "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='2.0'>\n" +
" <xsl:template match='/'>\n" +
" <elements>\n" +
" <xsl:apply-templates select=\"elements/element1\" mode=\"%s\"/>\n" +
" <xsl:apply-templates select=\"elements/element2\" mode=\"%s\"/>\n" +
" <xsl:apply-templates select=\"elements/element3\" mode=\"%s\"/>\n" +
" <xsl:apply-templates select=\"elements/element4\" mode=\"%s\"/>\n" +
" <xsl:apply-templates select=\"elements/element5\" mode=\"%s\"/>\n" +
" </elements>\n" +
" </xsl:template>\n" +
" <xsl:template match='*' mode='normal'>\n" +
" <xsl:copy-of select=\".\"/>\n" +
" </xsl:template>\n" +
" <xsl:template match='*' mode='comment'>\n" +
" <xsl:text disable-output-escaping=\"yes\"><!--</xsl:text><xsl:copy-of select=\".\"/>--<xsl:text disable-output-escaping=\"yes\">></xsl:text>\n" +
" </xsl:template>\n" +
"</xsl:stylesheet>";
XML second = new XSLDocument(
String.format(template, "normal", "comment", "normal", "comment", "normal")
).transform(first);
System.out.println(second.toString());
Output:
<?xml version="1.0" encoding="UTF-8"?>
<elements>
<element1 atribute="value"/>
<!--<element2 atribute="value"/>-->
<element3 atribute="value"/>
<!--<element4 atribute="value"/>-->
<element5 atribute="value"/>
</elements>
NB: For the sake of readability, I formatted the output
I do not think it is achievable using JAXB
purely. Here is a way to achieve using STAX API
. I used similar implementation where i needed to manipulate XML comments
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader reader =factory.createXMLEventReader(new FileReader("input.xml"));
XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(new FileWriter("out.xml"));
String toggleMe = "element2";
String regEx = "<!--(.*)-->";
while(reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if(event.getEventType() == XMLStreamConstants.COMMENT) {
if(event.toString().contains(toggleMe)) {
String xmlElement = event.toString().replaceAll(regEx, "$1");
XMLEventReader elementReader = factory.createFilteredReader(factory.createXMLEventReader(new StringReader(xmlElement)), new DocElementEventFilter());
while(elementReader.hasNext()) {
writer.add(elementReader.nextEvent());
}
}else {
writer.add(event);
}
} else {
writer.add(event);
}
}
writer.flush();
writer.close();
reader.close();
This is very specific to the example xml you have given and currently support toggle of one element. You can extend it to toggle multiple elements as well.
Above code also uses following Event Filter
class DocElementEventFilter implements EventFilter {
@Override
public boolean accept(XMLEvent event) {
return !(event.isStartDocument() || event.isEndDocument());
}
}
Hope this helps you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With