XSLT available is 1.0.
I'm working on a dual-language site in an XML-based CMS (Symphony CMS), and need to replace the English version of a category name with the French version.
This is my source XML.
<data>
<our-news-categories-for-list-fr>
<entry id="118">
<title-fr handle="technology">Technologie</title-fr>
</entry>
<entry id="117">
<title-fr handle="healthcare">Santé</title-fr>
</entry>
</our-news-categories-for-list-fr>
<our-news-article-fr>
<entry id="100">
<categories>
<item id="117" handle="healthcare" section-handle="our-news-categories" section-name="Our News categories">Healthcare</item>
<item id="118" handle="technology" section-handle="our-news-categories" section-name="Our News categories">Technology</item>
</categories>
<main-text-fr mode="formatted"><p>Blah blah</p></main-text-fr>
</entry>
</our-news-article-fr>
</data>
This is part of the XSLT that I currently have for the French version.
<xsl:template match="data">
<xsl:apply-templates select="our-news-article-fr/entry"/>
</xsl:template>
<xsl:template match="our-news-article-fr/entry">
<xsl:if test="categories/item">
<p class="category">In:</p>
<ul class="category">
<xsl:for-each select="categories/item">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template match>
The problem: the visible text of the anchor (<xsl:value-of select="."/>) gives the English version of the category title.
The handles of the following nodes match (all handles are in English), and so I'm thinking I should be able to match one from the other.
/data/our-news-categories-for-list-fr/entry/title-fr/#handle (value of title-fr node is French translation of category title)
/data/our-news-article-fr/entry/categories/item/#handle
I'm new to XSLT and am struggling to find how to do this.
Many thanks.
Add <xsl:key name="k1" match="our-news-categories-for-list-fr/entry" use="#id"/> as a child of your XSLT stylesheet element. Then use e.g. <li><xsl:value-of select="key('k1', #id)/title-fr"/></li>.
../our-news-categories-for-list-fr/entry/title-fr/text() instead of #handle should do it
Your problem is that you are in
<our-news-article-fr>
and need to reference
<our-news-categories-for-list-fr>
so I do a parent .. to walk up the tree and then down the entry nodes
Within the xsl:for-each repetition instruction, the context is our-news-article-fr/entry/categories/item. If you use . you select the current context, that's why you are receiving the english version there.
Another approach (not saying the simplest and the best one) is simply specify an XPath expression which locates the correct node. You can use the ancestor:: axis to go out from the current context to data and then use your test node. The needed predicate must match against the current context using current() function:
<xsl:value-of select="
ancestor::data[1]/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
"/>
If data is the root of your document you can obviously use an absolute location path:
/
data/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
Related
I have the following XSLT template
<xsl:template match="/root">
<r>
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
</r>
</xsl:template>
Which I use to translate the following example XML
<root>
<transport>
<route>
<ship> <!-- this can be a car -->
<fuel>
<litres>
42
</litres>
</fuel>
</ship>
</route>
<route>
<enddate>2015-08-21</enddate>
<car>
<fuel>
<litres>
42
</litres>
</fuel>
</car>
</route>
</transport>
</root>
Notice that under route I can have a ship OR a car.
I can't find a way to reduce the xpath exression to only cover for the choice between ship and car
<xsl:value-of select="transport/route[not(enddate)]/ship|car/fuel/litres"/>
To make the above work I have to change it to this:
<xsl:value-of
select="transport/route[not(enddate)]/ship/fuel/litres |
transport/route[not(enddate)]/car/fuel/litres"/>
but I that feels as I'm copying to much of the expression and violates my DRY nature. I tried transport/route[not(enddate)][car|ship]/fuel/litres but without success.
What should the expression be if I want to get rid of the duplication of the xpath?
One way to write your expression is as follows:
<xsl:value-of select="transport/route[not(enddate)]/*[self::car or self::ship]/fuel/litres"/>
Alternatively, if a route element can contain only only child element (whether ship, car or something else, you could also write this:
<xsl:value-of select="transport/route[not(enddate)][ship or car]/*/fuel/litres"/>
Using an XSLT 2.0 or 3.0 processor you can use <xsl:value-of
select="transport/route[not(enddate)]/(ship | car)/fuel/litres"/> but be aware that value-of with a version="2.0" or version="3.0" stylesheet outputs a sequence of values of the selected sequence and not the first value in the selected sequence like version="1.0" does.
I need to transform the XML inside the CDATA of the XML using the XSLT.
Input:
<pre>
<![CDATA[<p><strong>Guidance</strong> about simplifying medication in <em>patients<em> with end-stage CHF who appear to be imminently dying.</p>]]>
</pre>
Output:
<ce:section-title>Pre</ce:section-title>
<ce:para><ce:bold>Guidance</ce:bold> about simplifying medication in <ce:italic>patients</ce:italic> with <ce:inter-ref xlink:type="simple" xlink:href="/formulary/en/drug-treatment-in-the-imminently-dying.html#heart-failure">end-stage CHF who appear to be imminently dying</ce:inter-ref>.</ce:para>
Could you please suggest me on this. Thanks in advance.
Which XSLT processor and which version of XSLT can you use? With the commercial versions of Saxon 9 you can use XSLT 3.0 and
<xsl:template match="pre">
<ce:section-title>Pre</ce:section-title>
<xsl:apply-templates select="parse-xml(.)"/>
</xsl:template>
<xsl:template match="p">
<ce:para>
<xsl:apply-templates/>
</ce:para>
</xsl:template>
<!-- now add similar templates here for transformation of strong, em etc -->
I am trying to make this (xml 1.0) code work . I am new to this and already exhausted myself in
trying different ways. Does someone know my mistake?
<xsl:for-each select="News/Sport">
<xsl:if test="local-name()='Basketball'">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:value-of select="News/Sport/Basketball/Phrases/Phrase"/>
</xsl:if>
</xsl:for-each>
When I transform it into an HTML file the content doesn't show up. When I remove the xsl:for each and the xsl:if statements the content is successfully presented. I only wish that the content is first checked (if it is available in the XML file) and if yes, that it is taken from the XML content.
Thank you in advance for your help!
EDIT:
This is my XML code
<News>
<Sport>
<Basketball>
<Phrases>
<Phrase>Zach Randolph recovered the opening tipoff in Game 1 of the Western Conference Finals, and he didn’t touch the ball again until the possession following the Grizzlies’ first timeout.
</Phrase>
<Phrases>
</Basketball>
</Sport>
</News>
EDIT2:
Could you tell me why I cannot apply a template inside this below function? Only the text works now:(
<xsl:for-each select="News/Sport[Basketball]">
<xsl:apply-templates select="News/Sport/*" />
</xsl:for-each>
<xsl:template match="Basketball">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:apply-templates select="Phrases/Phrase"/>
</xsl:template>
<xsl:for-each select="News/Sport">
<xsl:if test="local-name()='Basketball'">
In this if test, the context node is a Sport element, so local-name() will always be Sport and will never equal Basketball.
I only wish that the content is first checked (if it is available in the XML file) and if yes, that it is taken from the XML content.
The usual way to handle this sort of thing in XSLT is to define templates matching the various nodes that might be present and then applying templates to all the nodes that are actually found. If there are no nodes of a particular type then the corresponding template will not fire
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:template match="/">
<html>
<body>
<!-- apply templates that match all elements inside Sport, which may
be Basketball, Football, etc. -->
<xsl:apply-templates select="News/Sport/*" />
</body>
</html>
</xsl:template>
<!-- when we find a Basketball element ... -->
<xsl:template match="Basketball">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:apply-templates select="Phrases/Phrase"/>
</xsl:template>
<!-- when we find a Football element ... -->
<xsl:template match="Football">
<p>
<xsl:text>Football Sport</xsl:text>
</p>
<!-- whatever you need to do for Football elements -->
</xsl:template>
<xsl:template match="Phrase">
<p><xsl:value-of select="." /></p>
</xsl:template>
</xsl:stylesheet>
That way you don't need any explicit for-each or if, the template matching logic handles it all for you.
You are missing the idea of a context node. Within a xsl:for-each, everything you refer to is about or relative to the selected nodes. So, for instance, within <xsl:for-each select="News/Sport">, the context node is the Sport elements, and a test like <xsl:if test="local-name()='Basketball'"> is always going to be false because the local name is always Sport.
It looks like you want just <xsl:if test="Basketball"> which tests whether there are any Basketball child nodes of the current Sport node.
The same thing applies to <xsl:value-of select="News/Sport/Basketball/Phrases/Phrase"/>. Because everything is relative to the Sport node, XSLT is looking for News/Sport/Basketball/Phrases/Phrase within the Sport element, which never exists.
In addition, you can just put text in literally: there is no need for an xsl:text element here, so your code should look like
<xsl:for-each select="News/Sport">
<xsl:if test="Basketball">
<p>Basketball Sport</p>
<xsl:value-of select="Basketball/Phrases/Phrase"/>
</xsl:if>
</xsl:for-each>
You can refine this further by adding a predicate to the for-each selection, so that it matches only Sport elements with a Basketball child. Like this
<xsl:for-each select="News/Sport[Basketball]">
<p>Basketball Sport</p>
<xsl:value-of select="Basketball/Phrases/Phrase"/>
</xsl:for-each>
I am new to XSLT so excuse me if this is a noob question.
Let's say I have this XML document (one hotel element with 2 private_rates):
<hotel>
<private_rates>
<private_rate>
<id>1</id>
</private_rate>
<private_rate>
<id>2</id>
</private_rate>
</private_rates>
</hotel>
Is there any way to use XSLT to transform it into 2 hotel elements, each with one private rate ?
<hotel>
<private_rates>
<private_rate>
<id>1</id>
</private_rate>
</private_rates>
</hotel>
<hotel>
<private_rates>
<private_rate>
<id>2</id>
</private_rate>
</private_rates>
</hotel>
How would the XSLT for that look like? Any help will be greatly appreciated! thanks.
As an alternative to Jollymorphic's solution, my preference would be
<xsl:template match="private_rates">
<hotel>
<xsl:copy-of select="."/>
</hotel>
</xsl:template>
Since a legal XML document has to have a single, containing document element, I presume that this sequence of hotels is going inside something. That being said, how about this:
<xsl:template match="hotel">
<xsl:for-each select="private_rates/private_rate">
<hotel>
<private_rates>
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</private_rates>
</hotel>
</xsl:for-each>
</xsl:template>
The apply-templates usage here presumes that you also have an identity template in your stylesheet that will copy source content that isn't more specifically matched by other templates (such as this one).
Underscores are frowned upon in element and attribute names, incidentally.
I know there is the current() function to retrieve the current node in XSL, but is there a way to be able to reference the "previous" and "next" nodes in reference to the current position?
No. The current context cannot know which nodes are "next" or "previous".
This is because when, for example, templates are applied, the mechanics go like this:
You do: <xsl:apply-templates select="*" /><!-- select 3 nodes (a,b,c) -->
The XSLT processor makes list of nodes to be processed (a,b,c)
For each of those nodes, the XSLT processor selects and executes a matching template
When the template is called, the current() node is defined, and position() is defined, but other than that the template has no knowledge of the execution flow.
Execution order is subject to the processors preferences, as long as the outcome is guaranteed to be the same. Your (theoretical) predictions might be true for one processor, and wrong for another. For a side-effects free programming language like XSLT, knowledge like this would be a dangerous thing, I think (because people would start to rely on execution order).
You can do use the following::sibling or preceding::sibling XPath axes, but that's something different from knowing what node will be processed next
EDIT
The above explanation tries to answer the question as it has been asked, but the OP meant something different. It's about grouping/outputting unique nodes only.
As per the OP's request, here a quick demonstration of how to achieve a grouping using the XPath axes.
XML (the items are pre-sorted):
<items>
<item type="a"></item>
<item type="a"></item>
<item type="a"></item>
<item type="a"></item>
<item type="b"></item>
<item type="e"></item>
</items>
XSLT
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/items">
<!-- copy the root element -->
<xsl:copy>
<!-- select those items that differ from any of their predecessors -->
<xsl:apply-templates select="
item[
not(#type = preceding-sibling::item/#type)
]
" />
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<!-- copy the item to the output -->
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
Output:
<items>
<item type="a"></item>
<item type="b"></item>
<item type="e"></item>
</items>
Assuming you are talking about next and previous nodes in the document structure, as opposed to the current execution flow (such as a for-each loop), see the preceding-sibling and following-sibling axes: XPath Axes on W3.
To get the next node of the same name:
following-sibling::*[name() = name(current())]
Depending on the context, you may need to use name(.) in the first part.
You could track previous and current in variables for later handling.
I.e. you can keep tag(i-2), tag(i-1) and work with them in tag(i).
Just another idea.
Regards.