If have a big 'xsl:choose' chunk in which I need to set a number of defined sets of attributes on different elements.
I really do not like to repeat the definition of sets of attributes inside every branch of the 'choose'.
So I would like to work with a variable that contains those attributes.
A lot easier to maintain and less room for error...
So far I have not been able to call the attribute node out?
I thought they are just a node-set, so copy-of would do the trick.
But that gives me nothing on output.
Is this because attribute nodes are not really children?
But XSLT 1.O does not allow me to address them directly...<xsl:copy-of select="$attributes_body/#*/> returns an error
Here is the stylesheet fragment (reduced from original)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="list">
<xsl:for-each select="figure">
<xsl:variable name="attributes_body">
<xsl:attribute name="chapter"><xsl:value-of select="#chapter"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
</xsl:variable>
<xsl:variable name="attributes_other">
<xsl:attribute name="chapter"><xsl:value-of select="#book"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
</xsl:variable>
<xsl:choose>
<xsl:when test="ancestor::body">
<xsl:element name="entry">
<xsl:copy-of select="$attributes_body"/>
<xsl:text>Body fig</xsl:text>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="entry">
<xsl:copy-of select="$attributes_other"/>
<xsl:text>other fig</xsl:text>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
If this can not be done in XLST 1.0 would 2.0 be able to do this?
<xsl:variable name="attributes_body">
<xsl:attribute name="chapter"><xsl:value-of select="#chapter"/></xsl:attribute>
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
</xsl:variable>
You need to select the wanted attributes -- not to copy their contents in the body of the variable.
Remember: Whenever possible, try always to specify an XPath expression in the select attribute of xsl:variable -- avoid copying content in its body.
Solution:
Just use:
<xsl:variable name="attributes_body" select="#chapter | #id">
Here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="x/a">
<xsl:variable name="vAttribs" select="#m | #n"/>
<newEntry>
<xsl:copy-of select="$vAttribs"/>
<xsl:value-of select="."/>
</newEntry>
</xsl:template>
</xsl:stylesheet>
when applied on this:
<x>
<a m="1" n="2" p="3">zzz</a>
</x>
produces:
<newEntry m="1" n="2">zzz</newEntry>
in my case, I was trying to store a tag attribute into a variable
to do so, use this syntax tag-name/#attribute-inside-tag-name
here is an example
<xsl:variable name="articleLanguage" select="/article/#language"/><!--the tricky part -->
//<!--now you can use this this varialbe as you like -->
<xsl:apply-templates select="front/article-meta/kwd-group[#language=$articleLanguage]"/>
and the xml was
<article article-type="research-article" language="es" explicit-lang="es" dtd-version="1.0">
.....
hope this help you
Related
I need to declare a fixed sequence of numbers. How do I do this?
For example, is it (I'm guessing here):
<xsl:element name="xsl:param">
<xsl:attribute name="name">MySequence</xsl:attribute>
<xsl:sequence>(1,2,3,4)</xsl:sequence>
</xsl:element>
or
<xsl:element name="xsl:param">
<xsl:attribute name="name">MySequence</xsl:attribute>
<xsl:sequence>1,2,3,4</xsl:sequence>
</xsl:element>
or what?
Thanks
If you're using XSLT 2.0, you can just create the sequence directly in the select like:
<xsl:param name="MySequence" select="('1','2','3','4')"/>
XSLT based verification...
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:param name="seq" select="('23453','74365','98','653')"/>
<xsl:template match="/">
<xsl:for-each select="$seq">
<xsl:value-of select="concat('Item ',position(),': ',.,'
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
applied to any XML input produces:
Item 1: 23453
Item 2: 74365
Item 3: 98
Item 4: 653
To build a sequence in the XSLT 2.0 sense you use a select e.g.
<xsl:sequence select="1 to 4" />
But if you're adding the value to an element you may prefer value-of
<xsl:value-of select="1 to 4" separator="," />
Given the snippet in the question, this would generate output XML of
<xsl:param name="MySequence">1,2,3,4</xsl:param>
Which makes the value of the generated param a comma separated string. If you actually want the param value to be a sequence in the generated XSLT then you need to generate a select attribute instead of using element content
<xsl:element name="xsl:param">
<xsl:attribute name="name" select="'MySequence'"/>
<xsl:attribute name="select">
<xsl:text>(</xsl:text>
<xsl:value-of select="1 to 4" separator=","/>
<xsl:text>)</xsl:text>
</xsl:attribute>
</xsl:element>
Giving output of
<xsl:param name="MySequence" select="(1,2,3,4)" />
This is my XSLT file:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:for-each select="//child_4331">
<xsl:value-of select="*"/>
<xsl:value-of select="#value" />
<xsl:attribute name="onclick">
<xsl:call-template name="GetOnClickJavaScript" />
</xsl:attribute>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
How can I set the click event on child_4331 value?
You didn't say, but I'm assuming you want to copy the child_4331 element and add the onclick attribute.
I would get rid of the template matching '/' and create one to match 'child_4331'. Use xsl:copy to create a copy of the element and add the attribute inside it. If the child_4331 element has attributes or child elements you will want to use xsl:apply-templates to pick them up.
Here is a sample snippet. Your solution may vary depending on your desired output. I can't give you more without knowing what your source XML looks like and what you expect to see in the result.
<xsl:template match="child_4331">
<xsl:copy>
<xsl:attribute name="onclick">
<xsl:call-template name="GetOnClickJavaScript" />
</xsl:attribute>
</xsl:copy>
</xsl:template>
I am trying to merge the elements from two separate web.xml files using XSLT. For example, if web-1.xml and web-2.xml are being merged, and I'm processing web-1.xml, I want all elements in web-2.xml to be added into the result, except any that already exist in web-1.xml.
In the XSLT sheet, I have loaded the document whose servlet's are to be merged into the other document using:
<xsl:variable name="jandy" select="document('web-2.xml')"/>
I then have the following rule:
<xsl:template match="webapp:web-app">
<xsl:copy>
<!-- Copy all of the existing content from the document being processed -->
<xsl:apply-templates/>
<!-- Merge any <servlet> elements that don't already exist into the result -->
<xsl:for-each select="$jandy/webapp:web-app/webapp:servlet">
<xsl:variable name="servlet-name"><xsl:value-of select="webapp:servlet-name"/></xsl:variable>
<xsl:if test="not(/webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
The problem I'm having is getting the test in the if correct. With the above code, the test always evaluates to false, whether a servlet-name element with the given node exists or not. I have tried all kinds of different tests but with no luck.
The relevant files are available at http://www.cs.hope.edu/~mcfall/stackoverflow/web-1.xml, and http://www.cs.hope.edu/~mcfall/stackoverflow/transform.xslt (the second web-2.xml is there as well, but StackOverflow won't let me post three links).
provide an anchor for the first document, just before the for-each loop:
<xsl:variable name="var" select="."/>
then, use it in your if:
<xsl:if test="not($var/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
Your template matches XPATH webapp:webapp from web-1.xml and
you are refencing absolute XPATH if your xsl:if condition: /webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name]. Try to do it using relative XPATH:
<xsl:if test="not(webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
I haven't checked it, so you have to give it a try.
Also, it would be easier if you could provide web-1.xml and web-2.xml files.
EDIT
The following XSLT merges two files - the only problem appears when there are sections of the same type (like listener) in two places of the input XML.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:webapp="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xpath-default-namespace="http://java.sun.com/xml/ns/javaee">
<xsl:output indent="yes"/>
<xsl:variable name="jandy" select="document('web-2.xml')"/>
<xsl:template match="/">
<xsl:element name="web-app">
<xsl:for-each select="webapp:web-app/*[(name() != preceding-sibling::node()[1]/name()) or (position() = 1)]">
<xsl:variable name="nodeName" select="./name()"/>
<xsl:variable name="web1" as="node()*">
<xsl:sequence select="/webapp:web-app/*[name()=$nodeName]"/>
</xsl:variable>
<xsl:variable name="web2" as="node()*">
<xsl:sequence select="$jandy/webapp:web-app/*[name() = $nodeName]"/>
</xsl:variable>
<xsl:copy-of select="$web1" copy-namespaces="no"/>
<xsl:for-each select="$web2">
<xsl:variable name="text" select="./*[1]/text()"/>
<xsl:if test="count($web1[*[1]/text() = $text]) = 0">
<xsl:copy-of select="." copy-namespaces="no"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This is my XML and XSLT code
<root>
<act>
<acts id>123</acts>
</act>
<comp>
<comps id>233</comps>
</comp>
</root>
<xsl:for-each select="act/acts">
<xsl:variable name="contactid" select="#id"/>
<xsl:for-each select="root/comp/comps">
<xsl:variable name="var" select="boolean(contactid=#id)"/>
</xsl:for-each>
<xsl:choose>
<xsl:when test="$var='true'">
. . . do this . . .
</xsl:when>
<xsl:otherwise>
. . . do that . . .
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
I want to dynamically assign true or false to var and use it inside <xsl:choose> for boolean test. I hope this helps to find a better solution to get rid of for-each also
First thing to note is that variables in XSLT are immutable, and cannot be changed once initialised. The main problem with your XSLT is that you define your variable within an xsl:for-each block and so it only exists within the scope of that block. It is not a global variable. A new variable gets defined each time that can only be used within the xsl:for-each
From looking at your XSLT it looks like you want to iterate over the acts element and perform a certain action depending on whether an comps element exists with the same value. An alternative approach would be to define a key to look up the comps elements, like so
<xsl:key name="comps" match="comps" use="#id" />
Then you can simply check whether a comps element exists like so (assuming you are positioned on an acts element.
<xsl:choose>
<xsl:when test="key('comps', #id)">Yes</xsl:when>
<xsl:otherwise>No</xsl:otherwise>
</xsl:choose>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="comps" match="comps" use="#id" />
<xsl:template match="/root">
<xsl:apply-templates select="act/acts" />
</xsl:template>
<xsl:template match="acts">
<xsl:choose>
<xsl:when test="key('comps', #id)"><res>Yes</res></xsl:when>
<xsl:otherwise><res>No</res></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied to the following (well-formed) XML
<root>
<act>
<acts id="123"/>
</act>
<comp>
<comps id="233"/>
</comp>
</root>
The following is output
No
However, it can often be preferably in XSLT to avoid the use of conditional statements like xsl:choose and xsl:if. Instead, you can structure the XSLT to make use of template matching. Here is the alternate approach
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="comps" match="comps" use="#id" />
<xsl:template match="/root">
<xsl:apply-templates select="act/acts" />
</xsl:template>
<xsl:template match="acts[key('comps', #id)]">
<res>Yes</res>
</xsl:template>
<xsl:template match="acts">
<res>No</res>
</xsl:template>
</xsl:stylesheet>
When applied to the same XML, the same result is output. Do note the more specific template for the acts node will take priority when matching the case where a comps exist.
There are some errors in your xml file, but assuming what you mean is:
<root>
<act><acts id="123"></acts></act>
<comp><comps id="233"></comps></comp>
</root>
Here is a full solution:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<doc>
<xsl:apply-templates select="root/comp/comps"/>
</doc>
</xsl:template>
<xsl:template match="root/comp/comps">
<xsl:variable name="compsid" select="#id"></xsl:variable>
<xsl:choose>
<xsl:when test="count(/root/act/acts[#id=$compsid])>0">Do This</xsl:when>
<xsl:otherwise>Do That</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Hi is there away on how to declare a link(ie:http://www.google.com) as a variable and then using the variable for an else if?Something like this?
<xsl:element name="a">
<xsl:attribute name="href">http://www.google.com</xsl:attribute>// first get the link
<xsl:choose>
<xsl:when test="http://www.google.com">
Do something 1
</xsl:when>
<xsl:otherwise>
Do something 2
</xsl:choose>
</xsl:element>
Is this possible?What should i be looking at?
is there away on how to declare a
link(ie:http://www.google.com) as a
variable and then using the variable
for an else if?
Use this code as a working example -- of course you need to learn at least the basics of XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vLink" select="'http://www.google.com'"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$vLink = 'http://www.google.com'">
It is the Google link...
</xsl:when>
<xsl:otherwise>
It is not (exactly) the Google link...
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted result is produced:
It is the Google link...
One can also use a global <xsl:param>. This can be set externally by the invoker of the transformation.
Match against the content straight forward, and declare the URL as a variable.
If you need it more globally try this:
...
<xsl:apply-templates select="a" />
...
<xsl:template match="a">
Just a link
</xsl:template>
<xsl:template match="a[starts-with(#href, 'http://google.com/') or starts-with(#href, 'http://www.google.com/')]">
Link to google.com
</xsl:template>
It's possible to some extent, but there is no if-else construct in XSL. Here's a version I tested that you might be able to adapt to your needs. The input I used was:
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<xml>
<LinkValue>http://www.google.com/</LinkValue>
</xml>
The XSL that showing "Do something 1" if LinkValue was the string above or "Do something 2" if I modified it was:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="LinkValue" select="//LinkValue"/>
<xsl:element name="a">
<xsl:attribute name="href"><xsl:value-of select="$LinkValue"/></xsl:attribute>
<xsl:if test="$LinkValue = 'http://www.google.com/'">
Do something 1
</xsl:if>
<xsl:if test="$LinkValue != 'http://www.google.com/'">
Do something 2
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Hopefully you can use these samples to figure out exactly what you need to implement for your scenario.