Pablo Morelli
Pablo Morelli

Reputation: 133

Append an element in a XML using DOM keeping the format

i have a xml like this

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Empleado>
  <ConsultorTecnico>
    <Nombre>Pablo</Nombre>
    <Legajo antiguedad="4 meses">7778</Legajo>
  </ConsultorTecnico>
  <CNC>
    <Nombre>Brian</Nombre>
    <Legajo antiguedad="1 año, 7 meses">2134</Legajo>
  <Sueldo>4268.0</Sueldo>
  </CNC>
</Empleado>

What i want is to read a XML and append "Sueldo" at the same level than "Nombre" and "Legajo" in the element "CNC". "Sueldo" must be "Legajo" x 2

The code I have appends "Sueldo" as you can see in the XML above but it does not indent it as it should, Im using the propierties to indent (This XML is created the same way, using DOM)

public class Main
{
    public static void main(String[] args)
    {
        try
        {
          File xml = new File("C:\\Empleado.xml");
          if (xml.exists() == true)
          {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(xml);

            String legajo = doc.getElementsByTagName("Legajo").item(1).getFirstChild().getNodeValue();

        Element sueldo = doc.createElement("Sueldo");
        Node valorSueldo = doc.createTextNode(String.valueOf(Float.valueOf(legajo)*2));
        sueldo.appendChild(valorSueldo);

        Node cnc = doc.getElementsByTagName("CNC").item(0);

        cnc.appendChild(sueldo);

        DOMSource source = new DOMSource(doc);
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = tf.newTransformer();

        t.setOutputProperty(OutputKeys.INDENT, "yes");
        t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","2"); 

        FileOutputStream fos = new FileOutputStream("C:\\Empleado.xml");
        StreamResult sr = new StreamResult(fos);

        t.transform(source,sr);    

        }
      else
          throw new Exception("No hay archivo XML con ese nombre en el directorio");
    }
    catch (Exception e) 
    {
      System.out.println(e.getMessage());
    }
}

}

Thank you in advance guys, I'll appreciate the help here!

Upvotes: 4

Views: 3158

Answers (2)

Ian Roberts
Ian Roberts

Reputation: 122364

Assuming your input file is the same as the output you've shown but without the Sueldo element, then the initial CNC element has five child nodes as far as the DOM is concerned

  • The whitespace text node (newline and four spaces) between <CNC> and <Nombre>
  • The Nombre element node
  • The whitespace text node (newline and four spaces) between </Nombre> and <Legajo
  • The Legajo element node
  • The whitespace text node (newline and two spaces) between </Legajo> and </CNC>

You are inserting the Sueldo element after this final text node, which produces

  <CNC>
    <Nombre>Brian</Nombre>
    <Legajo antiguedad="1 año, 7 meses">2134</Legajo>
  <Sueldo>4268.0</Sueldo></CNC>

and the INDENT output property simply moves the closing </CNC> tag to the next line, aligned with the opening one. To get the auto indentation to do the right thing you would need to remove all the whitespace-only text nodes from the initial tree.

Alternatively, forget the auto-indentation and do it yourself - instead of adding Sueldo as the last child of CNC (after that final text node), instead add a newline-and-four-spaces text node immediately after the Legajo (i.e. before the last text node) and then add the Sueldo element after that.


As an alternative approach entirely, I would consider doing the transformation in XSLT rather than using the DOM APIs

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <!-- ignore whitespace-only text nodes in the input -->
  <xsl:strip-space elements="*"/>
  <!-- and re-indent the output -->
  <xsl:output method="xml" indent="yes" />

  <!-- Copy everything verbatim except where otherwise specified -->
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>

  <!-- For CNC elements, add a Sueldo as the last child -->
  <xsl:template match="CNC">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
      <Sueldo><xsl:value-of select="Legajo * 2" /></Sueldo>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

which you could either run using the TransformerFactory API from Java code or using a standalone command-line XSLT processor.

Upvotes: 1

Mario Rossi
Mario Rossi

Reputation: 7799

XML does not intrinsically define any indentation or pretty-form. If you want it to be "indented", you need to insert content with newlines and spaces. In this case, you need content immediately after element Legajo and before element Sueldo.

To my taste, the best strategy is to ignore all formatting from XML files and use generalized prettyfiers immediately before human consumption. Or, better, give them good XML editors. If you have every program that manipulates XML files concerned about this detail, most of the benefits of XML are gone (and a lot of effort misused).

UPDATE: Just noticed that you are using element CNC to "position" the insert, not Legajo. The space-and-newlines content needs to go immediately before element CNC (and after element Sueldo).

Upvotes: 0

Related Questions