3

I want to sort xml elements numerically, but failed at the last step(to merge two xml).
This is what I have tried:

content of xml file

$ cat input.xml
<root>
    <title>hello, world</title>
    <items>
        <item>2</item>
        <item>1</item>
        <item>3</item>
    </items>
</root>

sort items

$ xmlstarlet sel -R -t -m '//item' -s A:N:- 'number(.)' -c '.' -n input.xml
<xsl-select>
    <item>1</item>
    <item>2</item>
    <item>3</item>
</xsl-select>

delete items

$ xmlstarlet ed -d '//item' input.xml
<?xml version="1.0"?>
<root>
  <title>hello, world</title>
  <items/>
</root>

how to merge the outputs? the result should be:

<root>
    <title>hello, world</title>
    <items>
        <item>1</item>
        <item>2</item>
        <item>3</item>
    </items>
</root>
kev
  • 155,172
  • 47
  • 273
  • 272

2 Answers2

2

I am not familiar with xmlstarlet, but for what I saw in its documentation it can be used to apply an XSL transformation to a XML file (tr) - you can use that command with this XSLT:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="items">
    <xsl:copy>
      <xsl:apply-templates select="item">
        <xsl:sort select="." data-type="number"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

to generate the sorted and merged output in a single operation.

MiMo
  • 11,793
  • 1
  • 33
  • 48
  • The answer does resolve the problem described, maybe without using xml starlet, but it does not answer the original question: I have two xml files that I like to merge, so creating a single transform is not possible. – Oliver Meyer Apr 03 '20 at 15:18
1

It's been a while since you asked but nevertheless. Following shell script produces the wanted result using a multi-stage pipeline -- though it's hardly the best way to process a larger input. For an approach using XInclude with xmlstarlet see this answer.

# shellcheck shell=sh
xmlstarlet select -R -t -m '//item' -s 'A:N:-' '.' -c '.' input.xml |
xmlstarlet select -R -t -c '/ | document("-")' input.xml |
xmlstarlet edit \
  -d '/xsl-select/root//item' \
  -m '/xsl-select/xsl-select/item' '/xsl-select/root/items' |
xmlstarlet select -B -I -t -c '/xsl-select/*[1]'
  1. run select to extract a sorted file of items (to stdout)
  2. run select to copy the original input and the sorted file and (-R) wrap them together, using the XSLT document function to access the sorted file on stdin (output from this step is listed below)
  3. invoke edit to delete the unsorted items and move the sorted items in place
  4. run select to extract and format the merged document

Output (indented) from step 2:

<xsl-select>
  <root>
    <title>hello, world</title>
    <items>
      <item>2</item>
      <item>1</item>
      <item>3</item>
    </items>
  </root>
  <xsl-select>
    <item>1</item>
    <item>2</item>
    <item>3</item>
  </xsl-select>
</xsl-select>
urznow
  • 1,576
  • 1
  • 4
  • 13