In a identity transform we can delete an attribute by
this works for any input... And we can \"r
It's difficult to answer your questions because some of your assumptions are wrong. For example:
<xsl:template match="@myAttrib"><b>my new element</b></xsl:template>
works for any number of elements. When applied (together with an identity transform template) to the following input:
<root>
<parent myAttrib="1">
<child myAttrib="1"/>
</parent>
<sibling myAttrib="1"/>
</root>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<parent>
<b>my new element</b>
<child>
<b>my new element</b>
</child>
</parent>
<sibling>
<b>my new element</b>
</sibling>
</root>
So clearly your assertion that "it works only when input have only one element" is not true.
With regard to:
<xsl:template match="@myAttrib">newValue</xsl:template>
This does not replace the value of myAttrib
because the template matches the attribute - not its value (as an aside: the value of an attribute is not a node and cannot be matched). So just like before, the attribute is matched and another node is output in its place; first it was an element, now it's a text node. That's the only difference.
The "replace procedure" is one attribute-node per one element-node.
No, that's not true either. Consider, for example, the following input:
<root>
<parent red="1" green="2">
<child red="1" blue="1"/>
</parent>
<sibling green="1" blue="1"/>
</root>
and the following template:
<xsl:template match="@red | @blue">
<new/>
</xsl:template>
or:
<xsl:template match="@*[contains(name(), 'r')]">
<new/>
</xsl:template>
--
BTW, none of these examples will work with Saxon - but that's another story.
An XSLT template replaces one thing with another thing. In this case, you are replacing an attribute with a text node. If you want to replace an attribute with another attribute of the same name, but different content, you can do this:
<xsl:template match="@myAttrib">
<xsl:attribute name="{name()}">newValue</xsl:attribute>
</xsl:template>
See above. An XSLT template substitutes one thing for another.
This can cause an error in certain situations. It is not automatically an error.
XSLT does not allow adding attributes to a parent element in the output stream after other node types have already been added. Presumably, what is happening in your case is:
myAttrib1
with an element.myAttrib2
as a new attribute.If myAttrib2
gets processed after myAttrib1
, then an error will occur. (There is no guarantee on the order in which attributes will be processed).
This can be tricky to fix, but here is one approach that will work in certain cases:
<xsl:template match="@*[../@myAttrib]" />
<xsl:template match="@myAttrib">
<xsl:copy-of select="../@*[(. | current())[2]]" />
<b>my new element</b>
</xsl:template>
Translating to another readers what I understand from Michael's answer (@michael.hor257k) and my comments there; thanks Michael!
Assume you have an input XML,
<root>
<parent A="1" B="2">
<child C="1" D="1" E="0"/>
</parent>
<sibling E="1">text1</sibling>
</root>
Here's a diagram of the internal DOM representation, that is a tree:
root / \ parent sibling / \ \ \ (A,B) child (E) [text1] \ (C,D,E)
The element root
is a node, the element parent
is a node, the attribute @A
is a node, etc. Text also is a node... But not all the tree-itens are nodes: some itens in the diagram are into parenthesis, because they are collections of attribute-nodes.
In the diagram, the collection is a tree-item, the attribute not. We can imagine procedures to delete or replace items of the tree.
The "Delete node" task works with any individual node, pointed by its XPath.
We can imagine "Delete item" task as well (see diagram), and point the item by a XPath.
To delete a collection-item, the XPath must to point all the nodes of the collection, so parent/@*
poits an item, but parent/@A
not (because ramains parent/@B
). XPath sibling/@E
points a collection because sibling
element have only one attribute. XPath @E
points two nodes, one characterizing a collection, other not.
The task "Replace item X by text" or "Replace item X by element", need a XPath pointing the item X. Only tree-itens can be replaced. To replace a collection-item, the XPath must to point all the nodes of the collection.
Summarizing: the collection of attributes is the item, not the attribute-node; this is the point (!), and where confusion arises.
In DOM representation we can access nodeValue property, for both, elements and attributes, and we can change it in both cases: this is other source of confusion, because this concept of "change the nodeValue property" not exists in XSLT.
So,
Why "replace value" is invalid?
An XPath sibling/@E
points to the a node attribute E
. We need something like sibling/@E/nodeValue()
to point the value and replace it, but this kind of XPath not exist.
(edit) IMPORTANT: as showed in this question, we can to change an attribute value in a ID-transform, by the use of xsl:attribute
, see @JLRishe's answer in this page.
Why "replace by element" is not an error?
The concept is "replace an item by other item". Make sense when we see the diagram o the "tree of items".
Is wrong to imagine "replace a node by element" because is wrong to imagine a "tree of nodes", and a generic XPath node can be an attribute of a collection with more than one members.
Why "replace by element" in a "two attributes (per element) context" is an error?
Because the XPath of an individual attribute of a collection with more than one attributes, not represents the collection. The XPath must to point all attributes of the collection, to be used in a replace procedure.