How to assign XSLT variables with XML attributes - xslt

Here is my XML file:
<Veranstaltungen xmlns="urn:schemas-etourist:Veranstaltung">
<Veranstaltung attribute1="xyz" attribute2="xyz">
<OBJECT>
<string xmlns="urn:eTourist:i18n" xml:lang="de-DE">GERMAN TEXT</string>
<string xmlns="urn:eTourist:i18n" xml:lang="en-GB">ENGLISH TEXT</string>
<string xmlns="urn:eTourist:i18n" xml:lang="cs-CZ">CZECH TEXT</string>
</OBJECT>
...
And here my XSLT variables:
...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:td="urn:schemas-etourist:Veranstaltung"
xmlns:td2="urn:schemas-etourist:SchemaExtension"
xmlns:td3="urn:eTourist:i18n"
xmlns:php="http://php.net/xsl"
extension-element-prefixes="php">
...
<xsl:variable name="german">
<xsl:value-of select="td:OBJECT/td3:string[#xml:lang='de-DE']"></xsl:value-of>
</xsl:variable>
<xsl:variable name="english">
<xsl:value-of select="td:OBJECT/td3:string[#xml:lang='en-GB']"></xsl:value-of>
</xsl:variable>
...
Variable 'german' is filled correctly, HOWEVER variable 'english' is filled with the GERMAN TEXT value. How do we fill variable 'english' with the ENGLISH TEXT value?
Many thanks for any help!

Try using the lang() function, which is designed to match languages like this, taking account of scope (remember an xml:lang attribute applies to all descendant elements as well as to the element on which the attribute is specified):
<xsl:variable name="german">
<xsl:value-of select="td:OBJECT/td3:string[lang('de')]"></xsl:value-of>
</xsl:variable>
<xsl:variable name="english">
<xsl:value-of select="td:OBJECT/td3:string[lang('en')]"></xsl:value-of>
</xsl:variable>

Related

XSLT grouping with filter

I am trying to do XSLT grouping within a call template while applying a filter. The below code works fine but it is not considering the filter condition and is returning all the nodes . Please help me to understand what I am doing wrong . My understanding is that is that it has something to do with the scope of the below line
<xsl:for-each select="key('ParentName',#ParentName)" >
Here is the xml and xslt .
<?xml version="1.0" encoding="utf-16"?>
<Documentation>
<Consequences>
<Nodes>
<Node1 name ="abc1" ParentName="Group1" IsInternal ="true" />
<Node1 name ="abc2" ParentName="Group2" IsInternal ="true"/>
<Node1 name ="bcd1" ParentName="Group2" IsInternal ="true" />
<Node1 name ="bcd2" ParentName="Group1" IsInternal ="false"/>
<Node1 name ="efg1" ParentName="Group2" IsInternal ="false"/>
<Node1 name ="efg2" ParentName="Group1" IsInternal ="false" />
</Nodes>
</Consequences>
</Documentation>
XSLT : -
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Consequences" >
<xsl:call-template name="Template1">
<xsl:with-param name="NodeList" select="Nodes/Node1[#IsInternal='true']"/>
</xsl:call-template>
</xsl:template>
<xsl:key name="ParentName" match="Node1" use="#ParentName" />
<xsl:template name ="Template1">
<xsl:param name="NodeList" />
<xsl:for-each select="$NodeList[generate-id()=generate-id(key('ParentName',#ParentName)[1])]">
<Group>
<xsl:value-of select="#ParentName" />
<xsl:for-each select="key('ParentName',#ParentName)" >
<SubItems>
<xsl:value-of select="#name" />
</SubItems>
</xsl:for-each>
</Group>
</xsl:for-each>
</xsl:template>
Assuming that you are trying to select only the elements who's #IsInternal='true', instead of simply all of the Node1 elements that have the specified #ParenName.
Your xsl:key is matching on all of the Node1 elements and indexed by the #ParentName value. If you are only using that key to retrieve the elements who's #IsInternal='true', then you can add a predicate filter to the match criteria:
<xsl:key name="ParentName" match="Node1[#IsInternal='true']" use="#ParentName" />
If you need to use that key to lookup Node1 elements by #ParentName in other areas without regard to what the #IsInternal value is, then you could filter the items returned from the key lookup:
<xsl:for-each select="key('ParentName',#ParentName)[#IsInternal='true']" >
<SubItems>
<xsl:value-of select="#name" />
</SubItems>
</xsl:for-each>
You could also define a key that uses the value of both the #ParentName and the #IsInternal values as the key value:
<xsl:key name="ParentName" match="Node1"
use="concat(#ParentName,'-',#IsInternal)" />
and then retrieve them with the same key value:
key('ParentName',concat(#ParentName,'-',#IsInternal))

how to fetch the value of one node based on the value from another node

I want to fetch the "index" value based on what is present in the string
<sch name="main">
<norm string="back-slash"/>
<norm string="open-braces" />
<norm string="close-braces" />
</sch>
<strings name="consts">
<string name="back-slash" val="\\" index="0"/>
<string name="close-braces" val="]" index="2"/>
<string name="remove-null" val="null" index="3" />
</strings>
i tried this but it doesnt' work. Can yuou please help?
<xsl:template match="norm" >
<xsl:variable name="$nme" select="#string"/>
<xsl:value-of select="/strings/#name=$nme/#index"/>,
</xsl:template>
/strings/#name=$nme/#index
is not valid XPath. You need an attribute selector if you wish to target a node by one of its attributes.
/strings/*[#name=$nme]/#index
First of all, the name of your variable $nme is not a valid Qname.
Instead of
<xsl:variable name="$nme" select="#string"/>
you should use
<xsl:variable name="nme" select="#string"/>
try this template:
<xsl:template match="norm" >
<xsl:variable name="nme" select="#string"/>
<xsl:value-of select="../following-sibling::strings/string[#name=$nme]/#index"/>,
</xsl:template>

msxsl:node-set() not recognized

I am trying to pull nodes out of a node set stored in a variable using the msxsl:node-set() function and am not getting anything. My xml looks like this:
<Root>
<Items olditemnumber="100" newitemnumber="200">
<Item ItemNumber="100" ItemAliasCode="1001" ItemCode="X" />
<Item ItemNumber="100" ItemAliasCode="1002" ItemCode="X" />
<Item ItemNumber="200" ItemAliasCode="2001" ItemCode="X" />
<Item ItemNumber="200" ItemAliasCode="2003" ItemCode="X" />
<Item ItemNumber="100" ItemAliasCode="1003" ItemCode="P" />
<Item ItemNumber="100" ItemAliasCode="1004" ItemCode="P" />
<Item ItemNumber="200" ItemAliasCode="2002" ItemCode="P" />
</Items>
</Root>
In my xslt I try to populate a variable with a subset of the nodes and then call them using the msxsl:node-set() function. This doesn't return anything however.
XSLT looks like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:template match="//Root">
<xsl:variable name="OldItemNumber" select="/Items/#olditemnumber"/>
<xsl:variable name="NewItemNumber" select="/Items/#newitemnumber"/>
<xsl:variable name="OldItems">
<xsl:value-of select="//Item[#ItemNumber = $OldItemNumber]"/>
</xsl:variable>
<xsl:variable name="NewItems">
<xsl:value-of select="//Item[#ItemNumber = $NewItemNumber]"/>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($OldItems)/Item">
...work
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The XSLT skips over the for-each loop, though I see in the watch that the the Xpath query grabs the right nodes in assigning the variables. The watch also tells me that the msxsl:node-set() function is undefined. Any help would be appreciated. What am I missing?
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:template match="//Root">
<xsl:variable name="OldItemNumber" select="/Items/#olditemnumber"/>
<xsl:variable name="NewItemNumber" select="/Items/#newitemnumber"/>
<xsl:variable name="OldItems" select="//Item[#ItemNumber = $OldItemNumber]"/>
<xsl:variable name="NewItems" select="//Item[#ItemNumber = $NewItemNumber]"/>
<xsl:for-each select="$OldItems">
...work
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
msxsl:node-set is for converting a result tree fragment (a.k.a. RTF) to a node set, which is not needed on your case.
xsl:value-of is for creating text nodes, so don't use it for selecting nodes of the input tree that you want to further query/process.

XSLT: merge two tags using attribute from one, tag from another with a transform/mapping

I have two tags in the input file, variable and type:
<variable baseType="int" name="X">
</variable>
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
And I need to generate the following output file:
<Item name="Y">
<Field name="X" type="Long" />
</Item>
So conceptually my approach here has been to convert the type tag into the Item tage, the variable instance to the Field. That's working fine:
<xsl:for-each select="type[#baseType='structure']">
<Item>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:for-each select="variableInstance">
<Field>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:attribute name="type">**THIS IS WHERE I'M STUCK**</xsl:attribute>
</Field>
</xsl:for-each>
</Item>
</xsl:for-each>
The problem I'm stuck on is:
I don't know how to get the variableInstance/Field tag to match on the variable tag by name, so I can access the baseType.
I need to map "int" to "Long" once I'm able to do 1.
Thanks in advance!
PROBLEM 1.
For the first problem that you have you can use a key:
<xsl:key name="variable-key" match="//variable" use="#name" />
That key is going to index all variable elements in the document, using their name. So now, we can access any of those elements by using the following XPath expression:
key('variable-key', 'X')
Using this approach is efficient when you have a lot of variable elements.
NOTE: this approach is not valid if each variable has its own scope (i.e. you have local variables which are not visible in different parts of the document). In that case this approach should be modified.
PROBLEM 2.
For mapping attributes you could use a template like the following:
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
The meaning of this transformation is: each time that we match a baseType attribute with int value, it has to be replaced by a Long value.
This transformation would be in place for each #baseType attribute in the document.
Using the described strategies a solution could be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- Index all variable elements in the document by name -->
<xsl:key name="variable-key"
match="//variable"
use="#name" />
<!-- Just for demo -->
<xsl:template match="text()" />
<!-- Identity template: copy attributes by default -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- Match the structure type -->
<xsl:template match="type[#baseType='structure']">
<Item>
<xsl:apply-templates select="*|#*" />
</Item>
</xsl:template>
<!-- Match the variable instance -->
<xsl:template match="variableInstance">
<Field>
<!-- Use the key to find the variable with the current name -->
<xsl:apply-templates select="#*|key('variable-key', #name)/#baseType" />
</Field>
</xsl:template>
<!-- Ignore attributes with baseType = 'structure' -->
<xsl:template match="#baseType[. = 'structure']" />
<!-- Change all baseType attributes with long values to an attribute
with the same name but with an int value -->
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
That code is going to transform the following XML document:
<!-- The code element is present just for demo -->
<code>
<variable baseType="int" name="X" />
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
</code>
into
<Item name="Y">
<Field baseType="Long" name="X"/>
</Item>
Oki I've got a solution for point 1 xsltcake slice or with use of templates. For point two I would probably use similar template to the one that Pablo Pozo used in his answer

XSL outputting more than expected

I'm fairly new to XSLT and XPath and have been banging my head against the wall for a while on this problem.
I have the following XML:
<reply>
<multi-results>
<multi-item>
<name>node1</name>
<information>
<block>
<slot>A</slot>
<state>Online</state>
<colour>purple</colour>
</block>
<block>
<slot>B</slot>
<state>Online</state>
<colour>yellow</colour>
</block>
<block>
<slot>C</slot>
<state>Online</state>
<colour>red</colour>
</block>
<block>
<slot>D</slot>
<state>Online</state>
<colour>blue</colour>
</block>
</information>
</multi-item>
</multi-results>
<address>
<label>this is an arbitrary bit of text included for this example</label>
</address>
</reply>
There are a variable number of "block" entries per file.
I want to "CSV" the data, and I'm using the following XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="*/text()[normalize-space()]">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
<xsl:template match="*/text()[not(normalize-space())]" />
<xsl:template match="block">
<xsl:value-of select="slot"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="state"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="colour"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Output:
node1A|Online|purple
B|Online|yellow
C|Online|red
D|Online|blue
this is an arbitrary bit of text included for this example
However, the output includes both the "name" and the "label"...
I want only what I'm explicitly asking for in the XSL:
A|Online|purple
B|Online|yellow
C|Online|red
D|Online|blue
I don't understand why. Can someone explain please?
Also, there may be multiple "name" elements, each with its own number of "block" elements.
Many thanks in advance
The elements outside the <block> are being processed using the default template rules. To prevent this you need to add
<xsl:template match="/">
<xsl:apply-templates select="block"/>
</xsl:template>
Then you don't need the template rules that match text nodes, because you never apply-templates to text nodes.
Just remove xsl:value-of from your first xsl:template. You get "name" and "label" contents because of it: it takes any text node and outputs its content. Moreover you don't need checking conditions on text nodes, leave one xsl:template for them with empty body:
<xsl:template match="*/text()"/>