I have some XML that looks something like this:
<ExtensionObject>
<Value xmlns="">
<Key>key01</Key>
<StringValue>somewords</StringValue>
</Value>
<Value xmlns="">
<Key>key01</Key>
<NumberValue>12345</NumberValue>
</Value>
...........hundreds more Values..............
</ExtensionObject>
I rather optimistically wrote XSLT like this:
<xsl:template match="/ExtensionObject">
<VResult>
<xsl:for-each select="Value">
<xsl:variable name="fld">
<Value><xsl:value-of select="Key"/></Value>
</xsl:variable>
<Result field="$fld">
<Value>
<xsl:choose>
<xsl:when test="NumberValue">
<xsl:value-of select="NumberValue"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="StringValue"/>
</xsl:otherwise>
</xsl:choose>
</Value>
</Result>
</xsl:for-each>
</VResult>
</xsl:template>
And expected to get this:
<VResult>
<Result field="key01"><Value>somewords</Value></Result>
<Result field="key02"><Value>12345</Value></Result>
...........hundreds more Values..............
</VResult>
Instead I got:
<VResult>
<Result field="$fld"><Value>somewords</Value></Result>
<Result field="$fld"><Value>12345</Value></Result>
...........hundreds more Values..............
</VResult>
Does anyone know how I can insert the field names with the key value?
<xsl:attribute name="field"><xsl:value-of select="Key"/></xsl:attribute>
Instead of using for-each, it's always better to use the templates:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<VResults>
<xsl:apply-templates select='*' />
</VResults>
</xsl:template>
<xsl:template match="Value">
<Result>
<xsl:attribute name='field'><xsl:value-of select='Key' /></xsl:attribute>
<xsl:apply-templates />
</Result>
</xsl:template>
<xsl:template match="NumberValue|StringValue">
<Value><xsl:value-of select='.' /></Value>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Related
In context to my earlier question at "XSLT transformation from XML to XML document". Further i was trying
to rename the "<value>" element to "<sName>" along with updating the value to "ABC" if the value of "dataset/type/text() ='test'". I wrote below code
for the same but it was not working as expected. please help.
Source XML-
<soapenv:Header/>
<soapenv:Body>
<v1:QueryRequest version="1">
<subject>
<dataList>
<!--1 or more repetitions:-->
<dataset>
<type>company</type>
<value>abc</value>
</dataset>
<dataset>
<type>user</type>
<value>xyz</value>
</dataset>
</dataList>
</subject>
<testList>
<!--1 or more repetitions:-->
<criteria>
<type>test</type>
<value>1234</value>
</criteria>
<criteria>
<type>test2</type>
<value>false</value>
</criteria>
</testList>
</v1:QueryRequest>
</soapenv:Body>
</soapenv:Envelope>
XSL file :-
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:old="http://www.abc.com/s/v1.0"
exclude-result-prefixes="old"
xmlns:v2="http://www.abc.com/s/v2.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"
version="1.0" />
<xsl:param name="newversion" select="'2.0'" />
<xsl:param name="ABC" select="'ABC'" />
<xsl:param name="XYZ" select="'XYZ'" />
<xsl:param name="DFG" select="'DFG'" />
<!-- fix namespace declarations on root element -->
<xsl:template match="/*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<!-- copy the v2: binding from the stylesheet document -->
<xsl:copy-of select="document('')/xsl:stylesheet/namespace::v2" />
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<!-- replace namespace of elements in old namespace -->
<xsl:template match="old:*">
<xsl:element name="v2:{local-name()}">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="old:QueryRequest/#version">
<xsl:attribute name="version">
<xsl:value-of select="$newversion" />
</xsl:attribute>
</xsl:template>
<xsl:template match="criteria[type='service']/value">
<xsl:choose>
<xsl:when test="criteria[type='service']/value/text()='1234'">
<xsl:element name="sName">
<xsl:value-of select="$ABC"/>
</xsl:element>
</xsl:when>
<xsl:when test="criteria[type='service']/value/text()='5464'">
<xsl:element name="sName">
<xsl:value-of select="$XYZ"/>
</xsl:element>
</xsl:when>
<xsl:when test="criteria[type='service']/value/text()='8755'">
<xsl:element name="sName">
<xsl:value-of select="$DFG"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="type/text()">
<xsl:value-of
select="translate(., 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
</xsl:template>
</xsl:stylesheet>
Expected Output:
<testList>
<criteria>
<type>test</type>
<sName>ABC</sName>
</criteria>
<criteria>
<type>test2</type>
<value>false</value>
</criteria>
</testList>
Within a
<xsl:template match="criteria[type='service']/value">
the value element is the current context node, so your other XPath expressions need to be relative to that, i.e. instead of
<xsl:when test="criteria[type='service']/value/text()='1234'">
you would just use
<xsl:when test="text()='1234'">
or better
<xsl:when test=".='1234'">
I have a XSL-File which has been written for being used with a XSLT 2.0 Transformer, but now I need to change the file in order to make it work with XSLT 1.0. I know the for-each-group-command is a XSLT 2.0 feature, but what about all the other things? How would I change my file to make it work with 1.0?
Here's the XSL-File:
<xsl:template match="ROWSET">
<ROWSET>
<xsl:for-each-group select="ROW" group-by="UKID">
<UEBERKUNDE>
<NAME><xsl:value-of select="UEBERKUNDE" /></NAME>
<xsl:copy-of select="UKID" />
<xsl:for-each-group select="current-group()" group-by="KUNDENNR">
<KUNDE>
<xsl:copy-of select="KUNDENNR" />
<xsl:copy-of select="KNAME1" />
<xsl:copy-of select="KNAME2" />
<xsl:copy-of select="KNAME3" />
<xsl:copy-of select="LAND" />
<xsl:copy-of select="PLZ" />
<xsl:copy-of select="ORT" />
<xsl:copy-of select="ADM" />
<xsl:copy-of select="KUNDENKLASSE" />
<xsl:copy-of select="MITARBEITER" />
<xsl:copy-of select="BELEGART" />
<EFAKTURA><xsl:value-of select="normalize-space(EFAKTURA)" /></EFAKTURA>
<WEBSTATUS><xsl:value-of select="normalize-space(WEBSTATUS)" /></WEBSTATUS>
<ZAHLUNGSBEDINGUNG><xsl:value-of select="normalize-space(ZAHLUNGSBEDINUNG)" /></ZAHLUNGSBEDINGUNG>
<xsl:for-each select="current-group()[count(. | key('rowsByMonth', concat(MONAT,'+',JAHR,'+',KUNDENNR))[1]) = 1]">
<ROW>
<xsl:copy-of select="JAHR" />
<xsl:copy-of select="MONAT" />
<xsl:copy-of select="HANDLING" />
<xsl:copy-of select="SOLLFRACHT" />
<xsl:for-each select="key('rowsByMonth', concat(MONAT,'+',JAHR,'+',KUNDENNR))">
<WARENGRUPPE>
<xsl:copy-of select="HGNAME" />
<xsl:copy-of select="HGID" />
<xsl:copy-of select="DG_BASIS" />
<xsl:copy-of select="EKECHT" />
<xsl:copy-of select="DB_BASIS" />
<xsl:copy-of select="NETTO" />
</WARENGRUPPE>
</xsl:for-each>
</ROW>
</xsl:for-each>
</KUNDE>
</xsl:for-each-group>
</UEBERKUNDE>
</xsl:for-each-group>
</ROWSET>
</xsl:template></xsl:stylesheet>
Thanks in advance!
If you want to do grouping in XSLT 1.0 then see http://www.jenitennison.com/xslt/grouping/muenchian.xml for simply "group by" and http://www.biglist.com/lists/xsl-list/archives/200101/msg00070.html for nested grouping.
So
<ROWSET>
<xsl:for-each-group select="ROW" group-by="UKID">
<UEBERKUNDE>
<NAME><xsl:value-of select="UEBERKUNDE" /></NAME>
<xsl:copy-of select="UKID" />
<xsl:for-each-group select="current-group()" group-by="KUNDENNR">
roughly translate into two keys
<xsl:key name="by-ukid" match="ROW" use="UKID"/>
<xsl:key name="by-nr" match="ROW" use="concat(UKID, '|', KUNDENNR)"/>
and
<ROWSET>
<xsl:for-each select="ROW[generate-id() = generate-id(key('by-ukid',UKID)[1])]">
<UEBERKUNDE>
<NAME><xsl:value-of select="UEBERKUNDE" /></NAME>
<xsl:copy-of select="UKID" />
<xsl:for-each select="key('by-ukid', UKID)[generate-id() = generate-id(key('by-nr', concat(UKID, '|', KUNDENNR))[1])]">
If you have further nested grouping you need more keys with concatenated key values.
i am trying to write an xslt code that will check whether the description element exist or not if it exist then it will show the description element but if it does not exist then it should not show the description element.but my code below still show element although there is no value in it.how can we code it so that it wont show out the description element if there is no description for a services.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Service">
<xsl:element name="equipment">
<xsl:if test="description !='' ">
<xsl:value-of select="description" />
</xsl:if>
<xsl:if test="not(description)">
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
as there is the an empty equipment element being returned.i want it to return only the first 2 equipment element that is not empty.
Updated solution is follows; please check
<xsl:template match="Services">
<xsl:for-each select="Service">
<xsl:if test="count(description) > 0 and description!=''">
<equipment>
<xsl:value-of select="description"/>
</equipment>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="/">
<xsl:apply-templates select="//Service"/>
</xsl:template>
<xsl:template match="Service">
<xsl:if test="description !='' ">
<xsl:element name="equipment">
<xsl:value-of select="description" />
</xsl:element>
</xsl:if>
</xsl:template>
or
<xsl:template match="/">
<xsl:apply-templates select="//Service"/>
</xsl:template>
<xsl:template match="Service">
<xsl:if test="child::description[text()]">
<xsl:element name="equipment">
<xsl:value-of select="description" />
</xsl:element>
</xsl:if>
</xsl:template>
Does this work for you?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- place <result /> as root to produce wellformed XML -->
<xsl:template match="/">
<result><xsl:apply-templates /></result>
</xsl:template>
<!-- rewrite those <Service /> that have a <description /> -->
<xsl:template match="Service[./description]">
<equipment><xsl:value-of select="description" /></equipment>
</xsl:template>
<!-- remove those who do not -->
<xsl:template match="Service[not(./description)]" />
</xsl:transform>
I have an XML document structured as follows
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
For each unique attribute value (delimited by commas) I need to list all item names associated with that value like so:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
My initial plan was to use a template to parse the attributes into Attribute nodes, surrounding each with appropriate tags, and then separating out the unique values with an XPATH expression like
Attribute[not(.=following::Attribute)]
but since the result of the template isn't a node-set that ever goes through an XML parser, I can't traverse it. I also tried exslt's node-set() function only to realize it does not allow me to traverse the individual Attribute nodes either.
At this point I'm at a loss for a simple way to do this and would really appreciate any help or ideas on how to proceed. Thanks!
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kAtrByVal" match="attr" use="."/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<groups>
<xsl:apply-templates/>
</groups>
</xsl:variable>
<xsl:variable name="vPass1"
select="ext:node-set($vrtfPass1)"/>
<xsl:apply-templates select="$vPass1/*"/>
</xsl:template>
<xsl:template match="item">
<group name="{name}">
<xsl:apply-templates select="attributes"/>
</group>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:variable name="vText" select=
"concat($pText,',')"/>
<attr>
<xsl:value-of select="substring-before($vText,',')"/>
</attr>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match=
"attr[generate-id()
=
generate-id(key('kAtrByVal',.)[1])
]
">
<xsl:value-of select="concat('
',.,': ')"/>
<xsl:for-each select="key('kAtrByVal',.)">
<xsl:value-of select="../#name"/>
<xsl:if test="not(position()=last())">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
produces the wanted, correct result:
a: item1
b: item1
c: item1, item2
d: item1, item2
e: item2
Explanation:
Pass1: tokenization and end result:
<groups>
<group name="item1">
<attr>a</attr>
<attr>b</attr>
<attr>c</attr>
<attr>d</attr>
</group>
<group name="item2">
<attr>c</attr>
<attr>d</attr>
<attr>e</attr>
</group>
</groups>
.2. Pass2 takes the result of Pass1 (converted to a nodeset using the extension function ext:node-set()) as input, performs Muenchian grouping and produces the final, wanted result.
My first thought is to make two passes. First, tokenize the attributes elements using a (slightly) modified version of #Alejandro's answer to this previous question:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
<xsl:template match="item">
<item name="{name}">
<xsl:apply-templates select="attributes"/>
</item>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="separator" select="','"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<val>
<xsl:value-of select="normalize-space($text)"/>
</val>
</xsl:when>
<xsl:otherwise>
<val>
<xsl:value-of select="normalize-space(
substring-before($text, $separator))"/>
</val>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after(
$text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Which produces:
<items>
<item name="item1">
<val>a</val>
<val>b</val>
<val>c</val>
<val>d</val>
</item>
<item name="item2">
<val>c</val>
<val>d</val>
<val>e</val>
</item>
</items>
Then apply the following stylesheet to that output:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="byVal" match="val" use="." />
<xsl:template match="val[generate-id() =
generate-id(key('byVal', .)[1])]">
<xsl:value-of select="." />
<xsl:text> : </xsl:text>
<xsl:apply-templates select="key('byVal', .)" mode="group" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="val" mode="group">
<xsl:value-of select="../#name" />
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="val" />
</xsl:stylesheet>
Producing:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
Doing this in one stylesheet would require more thought (or an extension function).
I have a pretty flat XML structure that I need to reorder into categorised sections and, for the life of me, I can't figure out how to do it in XSLT (not that I'm by any means an expert.)
Basically, the original XML looks kinda like:
<things>
<thing>
<value>one</value>
<type>a</type>
</thing>
<thing>
<value>two</value>
<type>b</type>
</thing>
<thing>
<value>thee</value>
<type>b</type>
</thing>
<thing>
<value>four</value>
<type>a</type>
</thing>
<thing>
<value>five</value>
<type>d</type>
</thing>
</things>
And I need to output something like:
<data>
<a-things>
<a>one</a>
<a>four</a>
</a-things>
<b-things>
<b>two</b>
<b>three</b>
</b-things>
<d-things>
<d>five</d>
</d-things>
</data>
Note that I can't output <c-things> if there aren't any <c> elements, but I do know ahead of time what the complete list of types is, and it's fairly short so handcoding templates for each type is definitely possible. It feels like I could probably hack something together using <xsl:if> and <xsl:for-each> but it also feels like there must be a more ... 'templatey' way to do it. Can anyone help?
Cheers.
As you are using Saxon, use the native XSLT 2.0 grouping.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="things">
<data>
<xsl:for-each-group select="thing" group-by="type">
<xsl:element name="{concat(current-grouping-key(),'-things')}">
<xsl:for-each select="current-group()">
<xsl:element name="{current-grouping-key()}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</data>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0 you can group with keys. This approach is called Muenchian Grouping.
The xsl:key defines an index containing thing elements, grouped by the string value of their type element. Function key() returns all nodes from the key with the specified value.
The outer xsl:for-each selects the thing elements that are the first returned by key() for their value.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="thing" match="thing" use="type" />
<xsl:template match="things">
<data>
<xsl:for-each select="thing[generate-id(.)=generate-id(key('thing',type)[1])]">
<xsl:element name="{concat(type,'-things')}">
<xsl:for-each select="key('thing',type)">
<xsl:element name="{type}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
The generic solution is to use an XSL key:
<xsl:key name="kThingByType" match="thing" use="type" />
<xsl:template match="things">
<xsl:copy>
<xsl:apply-templates select="thing" mode="group">
<xsl:sort select="type" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="thing" mode="group">
<xsl:variable name="wholeGroup" select="key('kThingByType', type)" />
<xsl:if test="generate-id() = generate-id($wholeGroup[1])">
<xsl:element name="{type}-thing">
<xsl:copy-of select="$wholeGroup/value" />
</xsl:element>
</xsl:if>
</xsl:template>
The above yields:
<things>
<a-thing>
<value>one</value>
<value>four</value>
</a-thing>
<b-thing>
<value>two</value>
<value>thee</value>
</b-thing>
<d-thing>
<value>five</value>
</d-thing>
</things>
In XSLT 2, you can do this very elegantly. Say you have a template for formatting each thing before it is wrapped in an <a> element:
<xsl:template match="thing" mode="format-thing">
<xsl:value-of select="value/text()"/>
</xsl:template>
Then you can apply that to each thing of some $type to build the <a-things> elements via a function:
<xsl:function name="my:things-group" as="element()">
<xsl:param name="type" as="xs:string"/>
<xsl:param name="things" as="element(thing)*"/>
<xsl:element name="{ concat($type, '-things') }">
<xsl:for-each select="$things[type/text() eq $type]">
<xsl:element name="{ $type }">
<xsl:apply-templates select="." mode="format-thing"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:function>
Then you can call that function for each unique type (a, b, d in your sample input) to build the entire output and you're done:
<xsl:template match="/">
<data>
<xsl:sequence select="
for $type in distinct-values(things/thing/type/text())
return my:things-group($type, /things/thing)
"/>
</data>
</xsl:template>
Of course, asking the question made it obvious...
My solution does use an <xsl:if>, but I can't see how it couldn't now I think about it. My solution looks basically like:
<xsl:if test="/things/thing/type = 'a'">
<a-things>
<xsl:apply-templates select="/things/thing[type='a']" mode="a" />
</a-things>
</if>
<xsl:template match="/things/thing[type='a']" mode="a">
<a><xsl:value-of select="value"/>
</xsl:template>
And repeat for the other types. I've coded it up, and it seems to work just fine.
<a-things>
<xsl:for-each select="thing[type = 'a']">
<a><xsl:value-of select="./value" /></a>
</xsl:for-each>
</a-things>
If you want to get really snazzy, replace the <a-things> and the predicate with parameters and use attribute value templates:
<xsl:param name="type" />
<xsl:element name="{$type}-things">
<xsl:for-each select="thing[type = $type]">
<xsl:element name="{$type}"><xsl:value-of select="./value" /></xsl:element>
</xsl:for-each>
</xsl:element>
And using grouping, you can do it without the if:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="things">
<data>
<xsl:for-each select="thing[not(type=preceding-sibling::thing/type)]">
<xsl:variable name="type"><xsl:value-of select="type" /></xsl:variable>
<xsl:element name="concat($type, '-things')">
<xsl:for-each select="../thing[type=$type]">
<xsl:element name="$type">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>