Why replace one attribute by element is valid, and two attributes not? - xslt-1.0

In a identity transform we can delete an attribute by
<xsl:template match="#myAttrib"/>
this works for any input... And we can "replace" the attribute by an element with
<xsl:template match="#myAttrib"><b>my new element</b></xsl:template>
... but it works only when input have only one attribute.
By other hand, if I need to replace attribute's value, the xsl:template behaviour is the same, that is,
<xsl:template match="#myAttrib">newValue</xsl:template>
not replaces the value, but delete attribute and include the "newValue" as a textNode.
Why "replace value" is invalid?
Why "replace by element" is not an error?
Why "replace by element" in a "two attributes (per element) context" is an error?
EDIT (for explain "two attributes"), suppose the input
<root>
<parent myAttrib1="1" myAttrib2="2">
<child myAttrib="1" myAttrib3="1"/>
</parent>
<sibling myAttrib0="1"/>
</root>
only the element sibling have one attribute.

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.
Edit:
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.

Why "replace value" is invalid?
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>
Why "replace by element" is not an error?
See above. An XSLT template substitutes one thing for another.
Why "replace by element" in a "two attributes (per element) context" is an error?
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:
You are substituting myAttrib1 with an element.
You have an identity template copying 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.

Related

Default namespace of the xsl:document

I created originally the following variable:
<xsl:variable as="document-node()" name="changesTexts">
<xsl:document>
<ps >
<p>Processed with <ptr target="#{$applicationID}"/>.</p>
<p>proofreading according to workflow 1.1.</p>
</ps>
</xsl:document>
</xsl:variable>
When I tried to access it like this
<xsl:variable name="p" select="$changesTexts//p"/>
It didn't work: an empty item() was the result.
After I added the namespace to the root element,
<ps xmlns="http://www.music-encoding.org/ns/mei">
I can access the desired elements by:
<xsl:variable name="p" select="$changesTexts//mei:p"/>
So basically I solved my problem but I would like to understand how the things work. I couldn't figure out what kind of default namespace the elements in the first case get. I tried:
name() (BTW this doesn't show me the namespace in the regular xml document either)
namespace-uri()
Also, I noticed in the debugger, that the variable $changesTexts is of the type document-node and in other cases, when I use fn:document(), the variables are of the type document-node(1). So there is obviously some subtle difference (?)
When you use a literal result element S in XSLT to create an element R in your result tree, the expanded name of R will be the same as the expanded name of S: that is, it will have the same local-name and the same namespace.
So the namespace of the elements constructed by your <ps> and <p> instructions is determined by the default namespace declared in the stylesheet (probably on the xsl:stylesheet element, but it could be on some inner element).

XSLT 1.0: Key based on a whitespace separated attribute

I have an XML containing elements a with an attribute #ref that contains at least one pointer and MAY contain any number of pointers separated by whitespaces: <a ref="#p1 #p2"/>. In an XSLT 1.0 stylesheet, I need to apply a template to all and only those elements b referenced in an a/#ref within the document.
So, I thought the idea would be to define a <xsl:key name="k1" match="a" use="my:refs(#ref)"/> with a function splitting the value of #ref into its constituents and then have a <xsl:template match="b[key('k1', #xml:id)]"/>. If I interpret the specs correctly:
The use attribute is an expression specifying the values of the key; the
expression is evaluated once for each node that matches the pattern. If the
result is a node-set, then for each node in the node-set, the node that
matches the pattern has a key of the specified name whose value is the
string-value of the node in the node-set;
I need to provide a function to #use that returns a node-set consisting of the values of a/#ref (minus the leading '#') as a string.
My solution was to define <xsl:key name="k1" match="a" use="str:tokenize(#ref, '# ')/> and <xsl:template match="a[key('k1', #xml:id)">.
When I use Xalan as processor, this gives me the wanted result. Saxon 9.6.0.7, however, complains about a circular key definition.
Now I am a bit confused: is my solution valid at all? and if so, why is Saxon complaining? Are there other/better(/real) solutions? [see comments].
Edit: Additional question: How would I get my result in XSLT 2.0? Simply using tokenize(#ref, '#') in the key definition is not enough because of the whitespaces while for normalize-space(tokenize(#ref, '#')) Saxon will give the same error XTDE0640.
Thanks,
Dario
example XML:
<root>
<b xml:id="#p1">P1</b>
<b xml:id="#p2">P2</b>
<b xml:id="#pn">Pn</b>
<a ref="#p2" />
<a ref="#p1 #pn" />
</root>
The output should be
P1
P2
Pn
(no particular order required).

XSLT: Dynamic node name insertion based on template parameter

I am trying to generate the following structure in an XSLT template.
<ns:e1>
<child1>some value<child1>
<child2>some value<child2>
<child3>some value<child3>
</ns:e1>
or
<ns:e2>
<child1>some value<child1>
<child2>some value<child2>
<child3>some value<child3>
</ns:e2>
or other elements ns:e3 etc (although finite), based on a template parameter (say type). Typically I could use an xls:choose construct. In such a case, I would be duplicating the child elements (whose values are also template parameters).
Is there a way in XSLT to dynamically assume the element name ns:e1 or ns:e2 so that I can put the child elements once in its parent. I could save maintenance effort later if I have change the child elements or values (change once in one place and avoid bugs due to human errors).
Thanks for your help in advance.
Yes, you may use the xsl:element instruction to do that.
Assuming you always want to have <child1>some value<child1><child2>some value<child2><child3>some value<child3> as children for your parent element, you could rewrite your code like so:
<xsl:variable name="elementName">
<!-- compute the element name here ... -->
</xsl:variable>
<!-- Here we create an element having the name computed in variable elementName -->
<xsl:element name="{$elementName}" namespace="http://www.anamespace.com/and/so/on">
<child1>some value<child1>
<child2>some value<child2>
<child3>some value<child3>
</xsl:element>

xslt 1.0 select first n elements whose child element is one of allowed values

I have one xml, and I have to find first n element whose child element has one of allowed value
e.g. for following xml, I want to select element whose state is WA or NY. List of allowed state is dynamic value so I can't use
<xsl:apply-templates select="element[(state='WA' or state='NY')]"/>
when I am trying to filter it with contains, nothing is happening. e.g.
<xsl:variable name="allowedListPadded">;WA;NY;</xsl:variable>
<xslt:apply-templates select="element[contains($allowedListPadded,concat(';',state,';'))]"/>
XML:
<items>
<element>
<state>WA</state>
<title>Washington</title>
</element>
<element>
<state>OR</state>
<title>Oragon</title>
</element>
<element>
<state>NY</state>
<title>New York</title>
</element>
<element>
<state>WA</state>
<title>Washington News</title>
</element>
<element>
<state>TX</state>
<title>Texas</title>
</element>
</items>
I was thinking to filter elements in apply-templates and then in template, wants to use position() < n. However got stuck with first part only.
complete xslt as asked.
<xslt:stylesheet version="1.0" xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="xslt">
<xslt:output omit-xml-declaration="yes" method="xml" />
<xslt:template match="root">
<xslt:text>{"statelist":</xslt:text>
<xslt:choose>
<xslt:when test="$allowedListPadded=''">
<!-- if no list is present, give default state -->
<xslt:apply-templates select="element[state = 'WA']"/>
</xslt:when>
<xslt:otherwise>
<xslt:apply-templates select="element[contains(allowedListPadded,concat(';',state,';'))]"/>
</xslt:otherwise>
</xslt:choose>
<xslt:text>}</xslt:text>
</xslt:template>
</xslt:stylesheet>
<xslt:apply-templates select="element[contains(allowedListPadded,concat(';',state,';'))][position() < 5]"/>
The trick here is not using an and operator in your predicate, but to use two predicates. The first one creates a node-set including the selected elements, and the second one returns a node-set with 5 (for example) elements of this node-set.
You are matching root element and there isn't any root element in your xml. You must change that and match items element. Your approach:
<xslt:variable name="allowedListPadded">;WA;NY;</xslt:variable>
<xslt:apply-templates select="element[contains($allowedListPadded,concat(';',state,';'))]"/>
should work then.

XML configuration of Zend_Form: child nodes and attributes not always equal?

A set of forms (using Zend_Form) that I have been working on were causing me some headaches trying to figure out what was wrong with my XML configuration, as I kept getting unexpected HTML output for a particular INPUT element. It was supposed to be getting a default value, but nothing appeared.
It appears that the following 2 pieces of XML are not equal when used to instantiate Zend_Form:
Snippet #1:
<form>
<elements>
<test type="hidden">
<options ignore="true" value="foo"/>
</test>
</elements>
</form>
Snippet #2:
<form>
<elements>
<test type="hidden">
<options ignore="true">
<value>foo</value>
</options>
</test>
</elements>
</form>
The type of the element doesn't appear to make a difference, so it doesn't appear to be related to hidden fields.
Is this expected or not?
As it was rather quiet on here, I took a look further into the source code and documentation.
On line 259 of Zend_Config_Xml, the SimpleXMLElement object attributes are converted to a string, resulting in:
options Object of: SimpleXMLElement
#attributes Array [2]
label (string:7) I can't see this because
value (string:21) something happens to this
becoming
options (string:21) something happens to this
So, I hunted through the documentation only to find that "value" is a reserved keyword when used as an attribute in an XML file that is loaded into Zend_Config_Xml:
Example #2 Using Tag Attributes in Zend_Config_Xml
"..Zend_Config_Xml also supports two
additional ways of defining nodes in
the configuration. Both make use of
attributes. Since the extends and the
value attributes are reserved keywords
(the latter one by the second way of
using attributes), they may not be
used..."
Thus, it would appear to be "expected" according to the documentation.
I'm not entirely happy that this is a good idea though, considering "value" is an attribute of form elements.
Don't worry about this. The reserved keywords were moved to their own namespace, and the previous attributes were depricated. In Zend Framework 2.0 the non-namespaced attributes will be removed so you can use them again.