XSLT 1.0 cannot select variables in xsl:value-of select

I try to transform a piece of XML that is generated by multiple sources. If I enter the text directly into the value-of select statement it works as intended. But as soon as I try to use variables it won't work any more.

If I use variables in the xsl:for-each statement it works fine too. This is the XSL that works fine

<xsl:variable name="crm_acc" select="account" />
<xsl:variable name="nav_acc" select="kontakt" />
<xsl:variable name="crm_fname" select="firstname" />
<xsl:variable name="nav_fname" select="fname" />

<TreeView>
  <xsl:for-each select="$crm_acc | $nav_acc">
    <TreeViewItem Header="Item">
      <TreeViewItem Header="Firstname:" >
        <xsl:value-of select="firstname | fname" />
      </TreeViewItem>
    </TreeViewItem>
  </xsl:for-each>
</TreeView>

But if I use variables inside of xsl:value-of select it just doesn't work as specified in the next code block. I tried a lot of combinations e.g. using only one variable with "firstname", "firstname | fname", trying to concat(...) the text etc...

<TreeView>
  <xsl:for-each select="$crm_acc | $nav_acc">
    <TreeViewItem Header="Item">
      <TreeViewItem Header="Firstname:" >
        <xsl:value-of select="$crm_fname | $nav_fname" />
      </TreeViewItem>
    </TreeViewItem>
  </xsl:for-each>
</TreeView>

Input XML:

<?xml version='1.0'?>
<root>
  <account system="CRM">
    <firstname>test1</firstname>
  </account>
  <account system="CRM">
    <firstname>test2</firstname>
  </account>
  <kontakt system="NAV">
    <erstername>nav1</erstername>
  </kontakt>
</root>

Only once I got a result, as I specified "account/firstname" in the variable. But this way I always got the value of the first element only in every for-each iteration. It seems as if it looses the context when specifying variables (that the processor is currently inside an "account" entity and should select "firstname as a subnode).

I read that something changed in the value-of select statement from XSLT 1.0 to 2.0 but I do not fully understand what the difference is (I am pretty new to XSLT).

How do I have to specify the XSL to get it to work?

Update: The expected output as in code block 1

<?xml version="1.0" encoding="UTF-8"?>
<TabItem xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Header="Xaml Accounts Tab">
  <TreeView>
    <TreeViewItem Header="CRM">
       <TreeViewItem Header="Firstname:">test1</TreeViewItem>
    </TreeViewItem>
    <TreeViewItem Header="CRM">
       <TreeViewItem Header="Firstname:">test2</TreeViewItem>
    </TreeViewItem>
    <TreeViewItem Header="NAV">
       <TreeViewItem Header="Firstname:">nav1</TreeViewItem>
    </TreeViewItem>
  </TreeView>
</TabItem>

Edit: What I should have mentioned is that these template(s) will/can get modified so it would be easier if all the "variables" are in one place. It could be that the target is not named "contact" but something else and changing it in the whole document is error prone and redundant. Also these variables are used in another process read by a XmlReader. I try to keep hassle of configuration and redundancy as low as possible.

Answers


Here are two possible approaches you might want to explore: one uses a variable to store all possible names of an element you might wish to call upon; the other assumes that the position of an element within its parent is known, therefore the name is of no importance.

The following stylesheet shows both methods, the former for the parent element, the latter for the firstname child.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:variable name="CRM">
    <name>account</name>
    <name>kontakt</name>
</xsl:variable>

<xsl:template match="/">
<TreeView>
    <xsl:for-each select="root/*[local-name()=exsl:node-set($CRM)/name]">
        <TreeViewItem Header="{@system}">      
            <TreeViewItem Header="Firstname:" >
                <xsl:value-of select="*[1]" />
            </TreeViewItem>
        </TreeViewItem>
  </xsl:for-each>
</TreeView>

</xsl:template>
</xsl:stylesheet>

The problem is that variables in XSLT are immutable. Treat them as constants. When you did

<xsl:variable name="crm_fname" select="firstname" />

You selected "firstname" in the current context. I suppose that code in a <xsl:template match='root'> context, so you selected "root/firstname". There is no such node, so the result is empty as expected.

When you call the variable in the for-each you are simply printing the contents of the variable, which is nothing.

It worked when you said the variable was account/firstname because that node exists, but since the variable is actually a constant, it contains the contents of the first match and any other attempts to change that value were ignored.


In XSLT, you have to forget about any procedural paradigms you know. You should avoid xsl:for-each whenever inappropriate and write separate templates instead.

Likewise, there is no need to use variables in your case. That is, it's not impossible to use them but, as pointed out by @helderdarocha, you have to be aware of their immutability and the context.

In the stylesheet you show, it is likely that most of the variables are empty because the elements you select do not exist in that context. To select elements regardless of where in the hierarchy they are, begin an expression with //.

Stylesheet

<?xml version="1.0" encoding="utf-8"?>

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

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

   <xsl:template match="/root">
      <TabItem xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Header="Xaml Accounts Tab">
         <TreeView>
            <xsl:apply-templates/>
         </TreeView>
      </TabItem>
   </xsl:template>

   <xsl:template match="account|kontakt">
      <TreeViewItem  Header="{@system}">
         <xsl:apply-templates/>
      </TreeViewItem>
   </xsl:template>

   <xsl:template match="firstname|erstername">
      <TreeViewItem  Header="Firstname:">
         <xsl:value-of select="."/>
      </TreeViewItem>
   </xsl:template>

</xsl:stylesheet>

Output

<?xml version="1.0" encoding="UTF-8"?>
<TabItem xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         Header="Xaml Accounts Tab">
   <TreeView>
      <TreeViewItem xmlns="" Header="CRM">
         <TreeViewItem Header="Firstname:">test1</TreeViewItem>
      </TreeViewItem>
      <TreeViewItem xmlns="" Header="CRM">
         <TreeViewItem Header="Firstname:">test2</TreeViewItem>
      </TreeViewItem>
      <TreeViewItem xmlns="" Header="NAV">
         <TreeViewItem Header="Firstname:">nav1</TreeViewItem>
      </TreeViewItem>
   </TreeView>
</TabItem>

Need Your Help

how keep same css on other pages after change

javascript css

what i am trying to do is: Change the css of a page with javascript (already done) and keep that css when i navigate to a different page.