3

I'm trying to add a node with a namespace and an attribute to an xml, but it fails if I try to do it as multiple commands in one execution of xmlstarlet:

<?xml version="1.0"?>
<levela xmlns:xi="http://www.w3.org/2001/XInclude">
  <levelb>
  </levelb>
</levela>

xmlstarlet ed -L -s /levela/levelb -t elem -n xi:input -i //xi:input -t attr -n "href" -v "aHref" file.xml

I'm trying to get:

<?xml version="1.0"?>
<levela xmlns:xi="http://www.w3.org/2001/XInclude">
  <levelb>
     <xi:input href="aHref"/>
  </levelb>
</levela>

But the attribute isn't added. So I get:

<?xml version="1.0"?>
<levela xmlns:xi="http://www.w3.org/2001/XInclude">
  <levelb>
     <xi:input/>
  </levelb>
</levela>

It works if I run it as two executions like this:

xmlstarlet ed -L -s /levela/levelb -t elem -n xi:input file.xml

xmlstarlet ed -L -i //xi:input -t attr -n "href" -v "aHref" file.xml

It also works if I add a tag without a namespace e.g:

xmlstarlet ed -L -s /levela/levelb -t elem -n levelc -i //levelc -t attr -n "href" -v "aHref" file.xml

<?xml version="1.0"?>
<levela xmlns:xi="http://www.w3.org/2001/XInclude">
  <levelb>
     <levelc href="aHref"/>
  </levelb>
</levela>

What am I doing wrong? Why doesn't it work with the namespace?

2 Answers 2

1

This will do it:

xmlstarlet edit \
  -s '/levela/levelb' -t elem -n 'xi:input' \
  -s '$prev' -t attr -n 'href' -v 'aHref' \
file.xml

xmlstarlet edit code can use the convenience $prev (aka $xstar:prev) variable to refer to the node created by the most recent -i (--insert), -a (--append), or -s (--subnode) option. Examples of $prev are given in doc/xmlstarlet.txt and the source code's examples/ed-backref*.

Attributes can be added using -i, -a, or -s.

What am I doing wrong? Why doesn't it work with the namespace?

Update 2022-04-15
The -i '//xi:input' … syntax you use is perfectly logical. As your own 2 alternative commands suggest it's the namespace xi that triggers the omission and there's a hint in the edInsert function in the source code's src/xml_edit.c where it says NULL /* TODO: NS */. When you've worked with xmlstarlet for some time you come to accept its limitations (or not); in this case the $prev back reference is useful. I wouldn't expect that TODO to go away anytime soon.
(end update)

Well, I think xmlstarlet edit looks upon node naming as a user responsibility, as the following example suggests,

printf '<v/>' |
xmlstarlet edit --omit-decl \
  -s '*' -t elem -n 'undeclared:qname' -v 'x' \
  -s '*' -t elem -n '!--' -v ' wotsinaname ' \
  -s '$prev' -t attr -n ' "" ' -v '' \
  -s '*' -t elem -n ' <&> ' -v 'harrumph!' 

the output of which is clearly not XML:

<v>
  <undeclared:qname>x</undeclared:qname>
  <!--  "" =""> wotsinaname </!-->
  < <&> >harrumph!</ <&> >
</v>

If you want to indent the new element, for example:

xmlstarlet edit \
  -s '/levela/levelb' -t elem -n 'xi:input' \
  --var newnd '$prev' \
  -s '$prev' -t attr -n 'href' -v 'aHref' \
  -a '$newnd' -t text -n ignored -v '' \
  -u '$prev' -x '(//text())[1][normalize-space()=""]' \
file.xml

The -x XPath expression grabs the first text node provided it contains nothing but whitespace, i.e. the first child node of levela. The --var name xpath option to define an xmlstarlet edit variable is mentioned in doc/xmlstarlet.txt but not in the user's guide.

I used xmlstarlet version 1.6.1.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! Nice solution, I had completely missed back-references. Though I still don't quite understand why an xpath that works perfectly on the generated XML doesn't work if I run it as a second command. If you can elaborate it would be appreciated. Regardless I accepted your answer since it solves the problem of addressing the new node.
0

It seems you can't insert an attribute and attribute value into a namespaced node... Maybe someone smarter can figure out something else, but the only way I could get around that, at least in this case, is this:

 xmlstarlet ed -N xi="http://www.w3.org/2001/XInclude" --subnode "//levela/levelb" \
 --type elem -n "xi:input" --insert  "//levela/levelb/*"  --type attr --name "href"\
 --value "aHref" file.xml

1 Comment

Interesting, at least you can get the node with xpath. But I do feel it's cheating a bit updating all nodes at the level even if it works for my example =)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.