xmlstarlet add string "null" to self-closing tag

95 Views Asked by At

I am trying to create an sqlite3 loader from data returned from ( Service-Now's REST API ) but it uses self closing tags in it's xml return.

If I could convert the fields from empty self closing to an opening and closing pair with a new value of "null" then I could parse the string if the field was part of an array.

[ CURRENT ]

# cat test.xml | xmllint --format -

<?xml version="1.0" encoding="UTF-8"?>
<response>
  <result>
    <serial_number>
      <display_value>6W5QY03</display_value>
      <value>6W5QY03</value>
    </serial_number>
    <location>
      <display_value/>
      <value/>
    </location>
  </result>
</response>

[ DESIRED ]

# cat test.xml | xmllint --format -

<?xml version="1.0" encoding="UTF-8"?>
<response>
  <result>
    <serial_number>
      <display_value>6W5QY03</display_value>
      <value>6W5QY03</value>
    </serial_number>
    <location>
      <display_value>null<display_value/>
      <value>null<value/>
    </location>
  </result>
</response>

Have done a lot of searching but substitution with utilities like ( sed ) I don't think is the way to go.

If I can't put something there I can't test for it. If the return could contain many positive returns one failure testing for status would not work.

# cat test.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <response>
      <result>
        <serial_number>
          <display_value>6W5QY03</display_value>
          <value>6W5QY03</value>
        </serial_number>
        <location>
          <display_value/>
          <value/>
        </location>
      </result>
    </response>

# xmlstarlet sel -T -t -v "response/result/location/display_value/text()" test.xml

# echo $?
1

# xmlstarlet sel -T -t -v "response/result/serial_number/display_value/text()" test.xml
6W5QY03
# echo $?
0
2

There are 2 best solutions below

1
Siebe Jongebloed On BEST ANSWER

You could use xslt in xmlstarlet i.e. with this xslt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  
  <xsl:output indent="yes"/>
  
  <xsl:strip-space elements="*"/>
  
  <!-- Default copy template -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  
  <!-- 
    Template that matches a element that has no node(): 
    text() en element() are both nodes
  -->
  <xsl:template match="*[not(node())]">
    <xsl:copy>
      <xsl:text>null</xsl:text>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>
1
mzjn On

The following xmlstarlet command does what you want. The ed option is used to 1) find all elements that don't have any content and 2) add the text "null" to them.

xmlstarlet ed -u "//*[not(node())]" -v "null" test.xml

Note that this does not capture just self-closing elements; it captures all empty elements. From an XML point of view, <x/> and <x></x> are equivalent.