xmlxmlstarlet

xmlstarlet add string "null" to self-closing tag


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

Solution

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