XSLT replace variable element text - xslt

I have an example document that looks like this
<document>
<memo>
<to>Allen</to>
<p>Hello! My name is <bold>Josh</bold></p>
<p>It's nice to meet you <bold>Allen</bold>. I hope that we get to meet up more often.</p>
<from>Josh</from>
<memo>
</document>
and this is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:company="http://my.company">
<xsl:output method="html"/>
<xsl:variable name="link" select="company:generate-link()"/>
<xsl:template match="/document/memo">
<h1>To: <xsl:value-of select="to"/></h1>
<xsl:for-each select="p">
<p><xsl:apply-templates select="node()" mode="paragraph"/></p>
</xsl:for-each>
<xsl:if test="from">
<p>From: <strong><xsl:value-of select="from"/></strong></p>
</xsl:if>
<xsl:copy-of select="$link"/>
</xsl:template>
<xsl:template match="bold" mode="paragraph">
<b><xsl:value-of select="."/></b>
</xsl:template>
<xsl:template match="text()" mode="paragraph">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
and the variable link contains the following example node:
When I do a copy-of out the variable link it prints out the node correctly (but obviously without any text). I want to insert it into the document and replace the text using XSLT. For example, the text could be:
View all of <xsl:value-of select="/document/memo/from"/>'s documents.
So the resulting document would look like:
<h1>To: Allen</h1>
<p>Hello! My name is <b>Josh</b></p>
<p>It's nice to meet you <b>Allen</b>. I hope that we get to meet up more often.</p>
<from>Josh</from>
View all of Josh's documents.
I have been searching the internet on how to do this but I couldn't find anything. If anyone could help I would appreciate it a lot!
Thanks,
David.

You haven't said which XSLT processor that is nor have you shown the code of that extension function to allow us to understand what it returns but based on your comment saying it returns a node you can usually process it further with templates so if you use <xsl:apply-templates select="$link"/> and then write a template
<xsl:template match="a[#href]">
<xsl:copy>
<xsl:copy-of select="#*"/>
View all of <xsl:value-of select="$main-doc/document/memo/from"/>'s documents.
</xsl:copy>
</xsl:template>
where you declare a global variable <xsl:variable name="main-doc" select="/"/> you should be able to transform the node returned from your function.

Related

I'm unable to get the desired values in my XSLT

This is the input :
<Data>
<A_ID>123456789</A_ID>
<A_Code>ojhgf</A_Code>
<A_Rec>
<inner1>2345</inner1>
<inner2>14April</inner2>
<inner3>15November</inner3>
</A_Rec>
</Data>
This is my XSLT:
<xsl:variable name="AID" select="A_ID" />
<xsl:variable name="ACode" select="A_Code" />
<xsl:for-each xmlns:sch="http://schemas.w3.com/" select="//A_Rec">
<sch:COMPANY>
<xsl:value-of select="$AID" />
</sch:COMPANY>
<sch:COMPANY_CODE>
<xsl:value-of select="$ACode" />
</sch:COMPANY_CODE>
</xsl:for-each>
I am trying to get the value "123456789" in the below mentioned line. $AID should hold 123456789, i am getting the desired value outside the for-each loop though.
But I’m not getting AID and ACode values inside the for-each loop for Company and company code. What do I do?
You should be using "Data/A_ID". Assuming that your context node is not "Data" as I see nothing else wrong.
Once created, XSLT variables cannot change their value.
There's a very good SO answer here that may help you redesign your XSLT.
Edit: Lingamurthy CS has correctly identified an issue with your XPath, but take a look at the SO page I've linked as it still might be useful.
It seems you want to output A_ID and A_Code in a different node with a different namespace.
Try this instead:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<root xmlns:sch="http://schemas.w3.com/">
<xsl:apply-templates select="Data/A_ID|Data/A_Code"/>
</root>
</xsl:template>
<xsl:template match="Data/A_ID">
<xsl:element name="sch:COMPANY" namespace="http://schemas.w3.com/">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="Data/A_Code">
<xsl:element name="sch:COMPANY_CODE" namespace="http://schemas.w3.com/">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

XSLT: nested for-each and dynamic variable

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>

XSL FO inline alignment on an existing sort/conditional XSL

I need to get right-align and left-align working in the same line. Looking over similar responses, I found the below recommendation,
<fo:block text-align-last="justify">
LEFT TEXT (want this to be the Contacts element from the below)
<fo:leader leader-pattern="space" />
RIGHT TEXT (want this to be the Address1 element from the below)
</fo:block>
But when I try to apply it to my existing XSL code (see below) I can’t make it work – I don’t know enough about how to edit it to accommodate/merge both the sort/conditionals and the FO. Can someone help me get this right?
Exsiting/working code:
<?xml version="1.0"?><!-- DWXMLSource="XML - Builder Members.xml" -->
<!DOCTYPE xsl:stylesheet [<!ENTITY nbsp " ">]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/">
<memberdata>
<xsl:for-each select="memberdata/memberinfo">
<xsl:sort select="SortKey"/>
<memberdata>
<xsl:if test="Contacts[.!='']">
<Contacts><xsl:value-of select="Contacts" /></Contacts>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:if test="Address1[.!='']">
<Address1><xsl:value-of select="Address1" /></Address1>
<xsl:text>
</xsl:text>
</xsl:if>
</memberdata>
</xsl:for-each>
</memberdata>
</xsl:template>
</xsl:stylesheet>
Independently of the actual answer to your question (which is impossible to give in the current form the question is in), I'd like to suggest a few improvements to your general approach to XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="no"/>
<xsl:template match="memberdata">
<xsl:copy>
<xsl:apply-templates select="memberinfo">
<xsl:sort select="SortKey" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="memberinfo">
<memberdata>
<xsl:apply-templates select="Contacts" />
<xsl:apply-templates select="Address1" />
</memberdata>
</xsl:template>
<xsl:template match="Contacts|Address1">
<xsl:if test="normalize-space() != ''">
<xsl:copy-of select="." />
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Avoid <xsl:for-each>, use distinct templates and <xsl:apply-templates> instead. This results in cleaner, less duplicated and less deeply nested code. It also could result in more efficient processing of your stylesheet, as XSLT processors are optimized for template matching and can parallelize template execution.
Note that you can use the same template for multiple elements, see third template above.
Avoid adding line-breaks via such a construct: <xsl:text>
</xsl:text>. Doing this destroys source code readability and is prone to errors as soon as the source code is formatted (I've already done this in your question to be able to indent your code properly in the first place). Use character references
instead to separate source code layout and output layout.
Note that you can use <xsl:copy-of> to make a copy of an element, no need to do <foo><xsl:value-of select="foo" /></foo>.
Taking your request at face value, this seems to be what you're asking for, which merges the sort, the conditionals and the FO.
<?xml version="1.0"?><!-- DWXMLSource="XML - Builder Members.xml" -->
<!DOCTYPE xsl:stylesheet [<!ENTITY nbsp " ">]>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/">
<memberdata>
<xsl:for-each select="memberdata/memberinfo">
<xsl:sort select="SortKey"/>
<memberdata>
<fo:block text-align-last="justify">
<xsl:if test="Contacts[.!='']">
<Contacts><xsl:value-of select="Contacts" /></Contacts>
<xsl:text>
</xsl:text>
</xsl:if>
<fo:leader leader-pattern="space" />
<xsl:if test="Address1[.!='']">
<Address1><xsl:value-of select="Address1" /></Address1>
<xsl:text>
</xsl:text>
</xsl:if>
</fo:block>
</memberdata>
</xsl:for-each>
</memberdata>
</xsl:template>
</xsl:stylesheet>
However it seems unlikely that you really want to mix <fo:*> elements and other elements (<memberdata>) in your output... unless you plan to process them later to produce a full FO document. So the above may not be quite the solution you need.
(See also #Tomalak's good points about how to improve the XSLT. I would differ with him only on the question of for-each vs. apply-templates... it really depends on several factors and what your priorities are.)

How to declare variable as a link in XSLT

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.

XSLT 2.0 External lookup using key() and document()

I'm pulling what's left of my hair out trying to get a simple external lookup working using Saxon 9.1.0.7.
I have a simple source file dummy.xml:
<something>
<monkey>
<genrecode>AAA</genrecode>
</monkey>
<monkey>
<genrecode>BBB</genrecode>
</monkey>
<monkey>
<genrecode>ZZZ</genrecode>
</monkey>
<monkey>
<genrecode>ZER</genrecode>
</monkey>
</something>
Then the lookup file is GenreSet_124.xml:
<GetGenreMappingObjectsResponse>
<tuple>
<old>
<GenreMapping DepartmentCode="AAA"
DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
Genre="10 - NEWS"/>
</old>
</tuple>
<tuple>
<old>
<GenreMapping DepartmentCode="BBB"
DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
Genre="11 - NEWS"/>
</old>
</tuple>
... lots more
</GetGenreMappingObjectsResponse>
What I'm trying to achieve is simply to get hold of the "Genre" value based on the "DepartmentCode" value.
So my XSL looks like:
...
<!-- Set up the genre lookup key -->
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:variable name="lookupDoc" select="document('GenreSet_124.xml')"/>
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc">
<xsl:with-param name="curr-label" select="genrecode"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template>
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup', $curr-genrecode)/#Genre"/>
</xsl:template>
...
The issue that I have is that I get nothing back. I currently just get
<?xml version="1.0" encoding="UTF-8"?>
<stuff>
<Genre/>
<Genre/>
<Genre/>
<Genre/>
</stuff>
I have moved all the lookup data to be attributes of GenreMapping, previously as child elements of GenreMapping whenever I entered the template match="GetGenreMappingObjectsResponse" it would just print out all text from every GenreMapping (DepartmentCode, DepartmentName, Genre)!
I can't for the life of me figure out what I am doing wrong. Any helpo/suggestions would be greatly appreciated.
PLease find the current actual XSLT listing:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Define the global parameters -->
<xsl:param name="TransformationID"/>
<xsl:param name="TransformationType"/>
<!-- Specify that XML is the desired output type -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<!-- Set up the genre matching capability -->
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:variable name="documentPath"><xsl:value-of select="concat('GenreSet_',$TransformationID,'.xml')"/></xsl:variable>
<xsl:variable name="lookupDoc" select="document($documentPath)"/>
<!-- Start the first match on the Root level -->
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template >
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup', $curr-genrecode, $lookupDoc)/#Genre"/>
</xsl:template>
</xsl:stylesheet>
The TransformationID is alway 124 (so the correct lookup file is opened. The Type is just a name that I am currently not using but intending to.
In XSLT 2.0 there are two ways you can do what you want:
One is the three-parameter version of the key function. The third parameter lets you specify the root node you want the key to work on (by default it's always the root of the main document):
<xsl:value-of select="key('genre-lookup', $curr-genrecode,$lookupDoc)/#Genre"/>
Another way is to use the key function under the $lookupDoc node:
<xsl:value-of select="$lookupDoc/key('genre-lookup', $curr-genrecode)/#Genre"/>
Both of these methods are documented in the XSLT 2.0 specification on keys, and won't work in XSLT 1.0.
For the sake of completeness, you'd have to rewrite this to not use keys if you're restricted to XSLT 1.0.
<xsl:value-of select="$lookupDoc//GenreMapping[#DepartmentCode = $curr-genrecode]/#Genre"/>
Aha! The problem is the select="$lookupDoc" in your apply-templates call is calling a default template rather than the one you expect, so the parameter is getting lost.
Change it to this:
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
That will call your template properly and the key should work.
So the final XSLT sheet should look something like this:
<xsl:variable name="lookupDoc" select="document('XMLFile2.xml')"/>
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template>
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup',$curr-genrecode,$lookupDoc)/#Genre"/>
</xsl:template>
OK, so this is a bit mental and I don't claim to understand it but it works (sounds like a career in software).
The issue I was having is that when I call the apply-templates and pass in the external document as a variable it never matched any templates even ones called "Genremapping".
So I used a wildcard to catch it, also when calling the apply-templates I narrowed down the node set to be the child I was interested in. This was pretty bonkers a I could print out the name of the node and see "GenreMapping" yet it never went into any template I had called "GenreMapping" choosing instead to only ever go to "*" template.
What this means is that my new template match gets called for every single GenreMapping node there is so it may be a little inefficient. What I realised then was all I needed to do was print sometihng out if a predicate matched.
So it looks like this now (no key used whatsoever):
...
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<key_value><xsl:value-of select="genrecode"/></key_value>
<xsl:variable name="key_val"><xsl:value-of select="genrecode"/></xsl:variable>
<code>
<xsl:apply-templates select="$lookupDoc/*/*/*/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</code>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template >
<xsl:template match="*">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select=".[#DepartmentCode = $curr-genrecode]/#Genre"/>
</xsl:template>
...
All of which output:
Note, the last key_value correctly doesn't have a code entry as there is no match in the lookup document.
<?xml version="1.0" encoding="UTF-8"?>
<stuff xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Genre>
<key_value>AAA</key_value>
<code>10 - NEWS</code>
</Genre>
<Genre>
<key_value>AAA</key_value>
<code>10 - NEWS</code>
</Genre>
<Genre>
<key_value>BBB</key_value>
<code>11 - NEWS</code>
</Genre>
<Genre>
<key_value>SVVS</key_value>
<code/>
</Genre>
</stuff>
Answer on a postcode. Thanks for the help Welbog.