Consider the following XSL transformation:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:dyn="http://exslt.org/dynamic"
extension-element-prefixes="dyn">
<xsl:variable name="_raw">
<test>1</test>
<test>2</test>
</xsl:variable>
<xsl:variable name="list" select="exsl:node-set($_raw)"/>
<xsl:template match="/">
<result>
<xsl:for-each select="$list/test">
<loop>
<xsl:value-of select="dyn:evaluate('exsl:node-set($list)/test')"/>
</loop>
</xsl:for-each>
</result>
</xsl:template>
</xsl:transform>
Executing this on any input gives:
<?xml version="1.0" encoding="UTF-8"?>
<result xmlns:exsl="http://exslt.org/common">
<loop>1</loop>
<loop/>
</result>
What I don't understand is:
The esxl:note-set() inside dyn:evaluate() is necessary if I want to reference $list in the XPath string. Otherwise, the first <loop> is also empty. Why? $list is already a node set.
It's exactly the same code executed twice. Why does it yield no result the second time?
This works if I don't take the values from $list, but from the XML input instead. Where's the difference?
If I remove the <xsl:for-each>, dyn:evaluate() works without the exsl:node-set() in it. Why? There's no reference to the context in the expression that is evaluated, it shouldn't make a difference.
My XSLT processor is Xalan 2.7.2.
I believe what you want to do is:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:dyn="http://exslt.org/dynamic"
extension-element-prefixes="exsl dyn">
<xsl:variable name="_raw">
<test>1</test>
<test>2</test>
</xsl:variable>
<xsl:variable name="list" select="exsl:node-set($_raw)"/>
<xsl:template match="/">
<result>
<xsl:for-each select="$list/test">
<loop>
<xsl:value-of select="dyn:evaluate(.)"/>
</loop>
</xsl:for-each>
</result>
</xsl:template>
</xsl:transform>
Which will result in:
<?xml version="1.0" encoding="UTF-8"?>
<result><loop>1</loop><loop>2</loop></result>
Edit:
I am still not sure what you are after here, but let me point out a few details:
It's exactly the same code executed twice. Why does it yield no result
the second time?
That may be a bug in Xalan. If you run your code with libxslt, the result will be:
<?xml version="1.0"?>
<result xmlns:exsl="http://exslt.org/common"><loop>1</loop><loop>1</loop></result>
The esxl:note-set() inside dyn:evaluate() is necessary if I want to
reference $list in the XPath string. Otherwise, the first is
also empty. Why? $list is already a node set.
I don't think it's necessary. Or perhaps I don't understand what it's necessary for. In any case, changing this:
<loop>
<xsl:value-of select="dyn:evaluate('exsl:node-set($list)/test')"/>
</loop>
to:
<loop>
<xsl:value-of select="dyn:evaluate('$list/test')"/>
</loop>
will result in:
<?xml version="1.0" encoding="UTF-8"?>
<result xmlns:exsl="http://exslt.org/common"><loop>1</loop><loop>1</loop></result>
and this time the result is same for both Xalan and libxslt.
Related
I am trying to learn the basics of XSLT, but am stuck on a particular use case. What I want to achieve is to transform one xml file into another xml (I am using XSLT 2.0), but a condition is that the grouping of elements in the output xml is decided by the value of one particular element in the input xml.
I will try to exemplify my question through a made-up example.
Lets say this is an input xml:
<products>
<shoes>
<shoe>
<name>Ecco City</name>
<category>Urban</category>
</shoe>
<shoe>
<name>Timberland Forest</name>
<category>Wildlife</category>
</shoe>
<shoe>
<name>Asics Gel-Kayano</name>
<category>Running</category>
</shoe>
</shoes>
<clothes>
<shorts>
<name>North Face</name>
<category>Wildlife</category>
</shorts>
<shorts>
<name>Adidas Running Shorts</name>
<category>Running</category>
</shorts>
</clothes>
Based on the value of the category element I want to, for each product, list similar products, that is, other products having the same category in the input xml, like this:
<output>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
</output>
This doesn't seem to be a grouping problem as such. If I understand correctly, you want to do something like:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="product-by-category" match="*" use="category" />
<xsl:template match="/products">
<output>
<xsl:for-each select="*/*">
<forSale>
<item>
<xsl:value-of select="name" />
</item>
<xsl:for-each select="key('product-by-category', category) except .">
<similarItem>
<xsl:value-of select="name" />
</similarItem>
</xsl:for-each>
</forSale>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<forSale>
<item>Ecco City</item>
</forSale>
<forSale>
<item>Timberland Forest</item>
<similarItem>North Face</similarItem>
</forSale>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
<forSale>
<item>North Face</item>
<similarItem>Timberland Forest</similarItem>
</forSale>
<forSale>
<item>Adidas Running Shorts</item>
<similarItem>Asics Gel-Kayano</similarItem>
</forSale>
</output>
I have this xsl path that gives me a desired value:
/path/to/#value
Is there a way to combine this into a substring?
substring(/path/to/#value, 1, 5)
The preceding statement does not work because I'm not as familiar to xsl as I thought
Actually, it should work just fine:
XML:
<?xml version='1.0'?>
<path>
<to value='123456'/>
</path>
XSLT:
<?xml version="1.0"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/">
<out>
<xsl:value-of select='substring(/path/to/#value, 1, 5)'/>
</out>
</xsl:template>
</xsl:stylesheet>
Another way is to use an intermediate variable:
<xsl:variable name='t' select='/path/to/#value'/>
<xsl:value-of select='substring( $t, 1, 5 )'/>
How can i copy an entire xml as is in an Variable?
Below is the sample xml:
<?xml version="1.0" encoding="UTF-8"?>
<products author="Jesper">
<product id="p1">
<name>Delta</name>
<price>800</price>
<stock>4</stock>
</product>
</products>
I have tried below xslt but it is not working.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:variable name="reqMsg">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:variable>
<xsl:copy-of select="$reqMsg"/>
</xsl:template>
</xsl:stylesheet>
Regards,
Rahul
Your transformation fails because at a certain point, it tries to create a variable (result tree fragment) containing an attribute node. This is not allowed.
It's not really clear what you mean by "copying an entire XML to a variable". But you probably want to simply use the select attribute on the root node:
<xsl:variable name="reqMsg" select="/"/>
This will actually create variable with a node-set containing the root node of the document. Using this variable with xsl:copy-of will output the whole document.
<xsl:copy-of select="document('path/to/file.xml')" />
Or if you need it more than once, to avoid repeating the doc name:
<xsl:variable name="filepath" select="'path/to/file.xml'" />
…
<xsl:copy-of select="document($filepath)" />
The result of document() should be cached IIRC, so don't worry about calling it repeatedly.
I want to check if in my XML exists node that has type attribute containing string type_attachment_.
Is it a correct way to check it?
<xsl:if test="count(*[contains(#Type, 'type_attachment_')]) > 0">
something
</xsl:if>
I don't know how nested can this node be. It can be for example as simple as that:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"?>
<hello-world>
<greeter>
<dsdsds>An XSLT Programmer
<greeting type = 'type_attachment_'>Hello, World!
</greeting>
</dsdsds>
</greeter>
</hello-world>
but can also contain this node nested in different other elements.
Expressions that match existing nodes are truthy. Expressions that do not match any nodes are falsy.
Therefore, you don't need to count the set of nodes returned. Simply test to see if anything matches.
<xsl:if test="*[contains(#Type, 'type_attachment')]">
something
</xsl:if>
Find out an example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:param name="filt">
<filters>
<ritem type="type_attachment_" relateditemnumber="8901037"/>
<ritem relateditemnumber="8901038"/>
<ritem type="type_attachment_" relateditemnumber="8901039"/>
<ritem relateditemnumber="8901040"/>
</filters>
</xsl:param>
<xsl:template match="/">
<xsl:for-each select="$filt/filters/ritem[#type='type_attachment_']">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<ritem type="type_attachment_" relateditemnumber="8901037"/>
<ritem type="type_attachment_" relateditemnumber="8901039"/>
I am struggling with what should be a simple XSL: Copy the updateResponse from the message below (note: I need XPATH 1.0 syntax for my integration system compatibility):
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:enterprise.soap.sforce.com" xmlns:n="urn:enterprise.soap.sforce.com">
<soapenv:Body>
<updateResponse>
<result>
<id>001S000000J1Bu0IAF</id>
<success>true</success>
</result>
</updateResponse>
</soapenv:Body>
</soapenv:Envelope>
I simply want the result structure to be:
<updateResponse xmlns="urn:enterprise.soap.sforce.com">
<result>
<id>001S000000J1Bu0IAF</id>
<success>true</success>
</result>
</updateResponse>
I am able to copy the soap objects, but am unsuccessful in copying the children of the soapenv:Body element. The first copy-of works for Body, but the second does not resolve the XPATH. My XMLSPY tool xpath query editor says the xpath is valid, but the XSL does not resolve.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:urn="enterprise.soap.sforce.com" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<xsl:namespace-alias stylesheet-prefix="soapenv" result-prefix="foo"/>
<xsl:template match="/">
<xsl:apply-templates select="/soapenv:Envelope/soapenv:Body/urn:updateResponse"/>
</xsl:template>
<xsl:template match="/soapenv:Envelope/soapenv:Body/urn:updateResponse">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Your namespaces are mismatched between your data and your stylesheet, that seems to be the only problem.
In your stylesheet document, change this:
xmlns:urn="enterprise.soap.sforce.com"
to this:
xmlns:urn="urn:enterprise.soap.sforce.com"
Then it will match the declared namespaces in your original input file.
This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<xsl:template match="/soapenv:Envelope/soapenv:Body//*">
<xsl:element name="{local-name()}"
namespace="urn:enterprise.soap.sforce.com">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<updateResponse xmlns="urn:enterprise.soap.sforce.com">
<result>
<id>001S000000J1Bu0IAF</id>
<success>true</success>
</result>
</updateResponse>
Note: First, the namespace declaration in your stylesheet is wrong. It should be xmlns:urn="urn:enterprise.soap.sforce.com". Second, for an exact result you need to strip in scope namespaces (there are three: xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/", xmlns="urn:enterprise.soap.sforce.com" (default), and xmlns:n="urn:enterprise.soap.sforce.com)
The namespaces in this document are weird; you have xmlns and xmlns:n both assigned to the same namespace. It looks like //soapenv:updateResponse should work. Have you tried that?