Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manipulate XML comments with JAXB

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.

like image 449
Viorel Florian Avatar asked May 05 '16 22:05

Viorel Florian


3 Answers

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..

like image 54
Pratiyush Kumar Singh Avatar answered Nov 10 '22 08:11

Pratiyush Kumar Singh


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">&lt;!--</xsl:text><xsl:copy-of select="."/>--<xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

As you can see there are 2 modes:

  1. if you choose normal it will simply copy the content of the node
  2. if you choose comment it will comment its content

So 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\">&lt;!--</xsl:text><xsl:copy-of select=\".\"/>--<xsl:text disable-output-escaping=\"yes\">&gt;</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

like image 20
Nicolas Filotto Avatar answered Nov 10 '22 07:11

Nicolas Filotto


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.

like image 34
Sanjeev Avatar answered Nov 10 '22 09:11

Sanjeev