Given:
<x>
<a />
<d />
<b />
<c />
<e />
<f />
</x>
I would like to use xmlstarlet to move <d />
to be before <e />
.
The closest I've got is:
echo .. | xml ed -m "//d" "//e"
Which produces:
<e>
<d/>
</a>
This is unfortunately the example the manual gives.
echo .. | xml ed -m "//d" "//x"
Puts <d />
at the end, which is not the right place.
I tried to get preceding-sibling
to work (if indeed that is right approach), but while:
echo .. | xml sel -t -c "//e/preceding-sibling::*[1]"
Results in <c />
, that query doesn't work as a move destination (it complains that move destination is not a single node), nor would it really, since best case would be it would end up inside <c />
.
I'm not sure if ed -m
is the wrong approach, of if there is a form of XPATH which points to a location between elements instead of an element.
Edit: interestingly insert works more like how I'd expect, inserting what you pass it before the element picked with xpath:
$ xml ed -i "//c" -t elem -n "foo" -v "bar" test.xml
<?xml version="1.0"?>
<x>
<a/>
<d/>
<b/>
<foo>bar</foo>
<c/>
<e/>
<f/>
</x>
Unfortunately the value passed (bar
above) cannot be XML, so I could pick it out of somewhere with sel and then inject it in with this command I don't think.
This seems like it should be easy using ed
. If it is, I'm not seeing it. (I don't use xmlstarlet very often.)
You may need to use XSLT...
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()[not(self::d)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="c">
<xsl:copy-of select="."/>
<xsl:copy-of select="../d"/>
</xsl:template>
</xsl:stylesheet>
Command Line
$ xml tr test.xsl test.xml
<?xml version="1.0"?>
<x>
<a/>
<b/>
<c/>
<d/>
<e/>
<f/>
</x>