Use XSL to keep only first paragraph of RSS <description> field - xslt

I am trying to work with this RSS feed.
I would like to use XSL to keep only the first paragraph of the description field. I have read up on how to target only certain children of an XML structure, but I think because the paragraph tags in the RSS feed are added as &lt and &gt they don't seem to work when doing something like this:
<xsl:value-of select="description/p[position() = 1]" disable-output-escaping="yes"/>
Ideally I would like to select only the text between the paragraph tags and discard them completely.
Any help would really be appreciated.

Use this transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/*/channel/item/description">
<xsl:value-of select=
"substring-before(substring-after(., '<P>'), '</P>')
"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

Related

xsl: when two nodes are equal, display child of first node

I'm using XML Editor 19.1, Saxon P.E 9.7.
For each selected div, I'm looking to display a graphic/#url, following each <surface> if surface/#xml:id = div/#facs.
XSL
<xsl:for-each select="descendant-or-self::div3[#type='col']/div4[#n]">
<xsl:variable name="div4tablet" select="#facs"/>
<xsl:choose>
<xsl:when test="translate(.[#n]/$div4tablet, '#', '') = preceding::facsimile/surfaceGrp[#type='tablet']/surface[#n]/#xml:id">
<xsl:value-of select=""/> <!-- DISPLAY graphic/#url that follows facsimile/surfaceGrp/surface -->
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
[....]
</xsl:for-each>
TEI example
<facsimile>
<surfaceGrp n="1" type="tablet">
<surface n="1.1" xml:id="ktu1-2_i_1_to_10_img">
<graphic url="../img/KTU-1-2-1-10-recto.jpg"/>
<zone xml:id=""/>
<zone xml:id=""/>
</surface>
<surface n="1.2" xml:id="ktu1-2_i_10_to_30_img">
<graphic url="../img/KTU-1-2-10-30-recto.jpg"/>
<zone xml:id=""/>
</surface>
[...]
</surfaceGrp>
<surfaceGrp n="2">
[...]
</surfaceGrp>
</facsimile>
<text>
[...]
<div3 type="col">
<div4 n="1.2.1-10" xml:id="ktu1-2_i_1_to_10" facs="#ktu1-2_i_1_to_10_img">
[...]
</div4>
<div4 n="1.2.10-30" xml:id="ktu1-2_i_10_to_30" facs="#ktu1-2_i_10_to_30_img">
[...]
</div4>
</div3>
</text>
I have tried <xsl:value-of select="preceding::facsimile/surfaceGrp[#type='tablet']/surface[#n, #xml:id]/graphic/#url"/>, but it displays all graphic/#url and not only the one that follows fascsimile/surfaceGrp/surface.
So my question: how to display only surface/graphic/#url for each div3[#type='col']/div4[#n]?
In advance, thank you for your kind help.
As you use XSLT 2 or 3 and the elements have the xml:id attribute you do not even need a key but can use the id function:
<xsl:template match="div4">
<div>
<xsl:value-of select="id(substring(#facs, 2))/graphic/#url"/>
</div>
</xsl:template>
I put the use of id into a template matching the div4 element but you can of course use it the same way inside of your for-each selecting those elements.
See a minimal but complete sample at https://xsltfiddle.liberty-development.net/bdxtpR.
you should use xsl:key for this type of problem.
First, we must declare a key for the target node
<xsl:key name="kSurface" match="surface" use="concat('#', #xml:id)"/>
notice the concat function being used here, an # was being added to the xml:id so that the keys would appear as:
#ktu1-2_i_1_to_10_img
#ktu1-2_i_10_to_30_img
now in this loop:
<xsl:for-each select="descendant-or-self::div3[#type='col']/div4[#n]">
we can access the key that matches the #facs attribute by having:
<xsl:value-of select="key('kSurface', #facs)/graphic/#url"/>
The whole stylesheet is below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:key name="kSurface" match="surface" use="concat('#', #xml:id)"/>
<xsl:template match="/">
<xsl:for-each select="descendant-or-self::div3[#type='col']/div4[#n]">
<xsl:value-of select="key('kSurface', #facs)/graphic/#url"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
see it in action here.

XSLT replace variable element text

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.

Trouble with xsl:for-each

I must be missing some fundamental concept of processing an XML document. Here is my source XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Root>
<Element>visitorNameAlt</Element>
<Element>visitorScore</Element>
<Element>visitorTimeouts</Element>
<Element>Blank</Element>
<Element>homeNameAlt</Element>
<Element>homeScore</Element>
<Element>homeTimeouts</Element>
<Element>Blank</Element>
<Element>period</Element>
<Element>optionalText</Element>
<Element>flag</Element>
<Element>Blank</Element>
<Element>scoreLogo</Element>
<Element>sponsorLogo</Element>
</Root>
And my XSL stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/Root">
<xsl:value-of select="position()"/>
<xsl:value-of select="Element"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
All I want is to pluck the "Element" names from the source XML doc with their relative position in front.
My output is just "1" followed by the first element and nothing more.
I am new to XSLT, but have processed other documents successfully with for-each.
Thanks in advance.
Bill
You're looping over Root tags, not Element tags. Try this:
<xsl:template match="/">
<xsl:for-each select="/Root/Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
Note that you must change the second value-of select to "." or "text()".
XSLT is not an imperative programming language. The XSLT processor grabs each element in turn and tries to match it to your stylesheet. The idiomatic way to write this is without a for-each:
<xsl:template match="/Root">
<xsl:apply-templates select="Element"/>
</xsl:template>
<xsl:template match="Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:template>
The first template matches the root and tells the processor to apply the stylesheet to all the Element nodes inside the Root. The second template matches those nodes, and outputs the desired information.

How to apply an XSLT transformation that includes spaces to an XML doc using tDOM?

I have some XML of the form:
<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24 />
...
</reg>
...
</definitions>
I want the XML to be the definitive reference and use XSLT to transform it to HTML for documentation, .h for building (and maybe other forms too).
The HTML version is working fine and produces a table per register, with a row per field:
... (header boilerplate removed)
<xsl:for-each select="definitions/reg">
<table>
<tr>
<th><xsl:value-of select="#offset"/></th>
<th><xsl:value-of select="#mnem"/></th>
</tr>
<xsl:for-each select="field">
<tr>
<td><xsl:value-of select="#msb"/>..<xsl:value-of select="#lsb"/></td>
<td><xsl:value-of select="#mnem"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
Converting to a .h isn't going so well. I'm completely failing to generate the required spaces in the output:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
#define <xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'd hope for that to produce the output:
#define SOMEREGISTER 0x0000
But I actually get:
#define SOMEREGISTER0x0000
I don't understand why I get the space after the '#define', but not the one after the transformed mnemonic. I've tried a simpler solution with just an inline space, with the same results.
I'm too new to this (XSLT) to know whether I'm a) doing it wrong or b) finding a limitation in tDOM.
Testing with this:
# I could have read these from a file I suppose...
set in {<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24" />
</reg>
</definitions>}
set ss {<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text>#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>}
# Interesting code starts here
package require tdom
set indoc [dom parse $in]
set xslt [dom parse -keepEmpties $ss]
set outdoc [$indoc xslt $xslt]
puts [$outdoc asText]
I find that this works. The issue is that the tDOM parser doesn't handle the xml:space attribute correctly; without the magical -keepEmpties option, all the empty strings are stripped from the stylesheet and that leads to a wrong XSLT stylesheet being applied. But with the option, it appears to do the right thing.
Note that the XSLT engine itself is doing the right thing. It's the XML parser/DOM builder. (I think it's a bug; I'll look up where to report it.)
Per:
http://www.ibm.com/developerworks/xml/library/x-tipwhitesp/index.html
Try using the preserve space directive:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text xml:space="preserve">#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You don't have an output method specified in your second stylesheet, so the default is gonna be XML. I'd advice you to use output method "text", then use <xsl:text> elements for any literal output. Check this example:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="definitions/reg"><xsl:text>#define </xsl:text><xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/><xsl:text> </xsl:text><xsl:value-of select="#offset"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT: by the way, that
at the end is a character code. It's simply the decimal value of the ASCII code for a line feed. This makes sure you start a new line for the next reg entry. If you need the Windows/DOS convention (carriage return + line feed), use
instead.

How can I select nodes from a tree whose markup is stored in a variable?

Consider the following XSLT script:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:variable name="stringmap">
<map>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</map>
</xsl:variable>
<xsl:template match="/">
<!-- IMPLEMENT ME -->
</xsl:template>
</xsl:stylesheet>
I'd like this script to print redgreenblue.
Is there any way to treat the XML markup which is stored in the stringmap variable as a document of its own which I can run XPath queries on? I'm basically looking for something like
<xsl:for-each select="document($stringmap)/map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
(except that the document() function expects an URI).
Motivation: I have various long <xsl:choose> elements which map a given string to another string. I'd like to replace all those with a single template which takes a 'map' argument (which is a simple XML document). My hope is that I can then replace the <xsl:choose> with a simple statement like <xsl:value-of select="$stringmap/map/entry/value[../key='$givenkey']"/>
I'm using XSLT 1.0 using xsltproc.
You're almost right, using document('') will allow you to process node sets inside the current stylesheet:
<xsl:for-each select="document('')/xsl:stylesheet/xsl:variable[#name='stringmap']/map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
It's not necessary to define the map node set as a variable in this case:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:data="some.uri" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<data:map>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</data:map>
<xsl:template match="/">
<xsl:for-each select="document('')/xsl:stylesheet/data:map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
If you do not use xsl:variable as a wrapper, you must remember that a top level elements must have a non null namespace URI.
In XSLT 2.0 it would've been possible to just iterate over the content in a variable:
<xsl:variable name="map">
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="$map/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
A posting by M. David Peterson just taught me how to make this work:
It's not necessary to have an <xsl:variable> for this case. Instead, I can embed the data document directly into the XSL stylesheet (putting it into a namespace for sanity) and then select elements from that. Here's the result:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="uri:map">
<xsl:output method="text" encoding="iso-8859-1"/>
<map:colors>
<entry><key>red</key><value>rot</value></entry>
<entry><key>green</key><value>gruen</value></entry>
<entry><key>blue</key><value>blau</value></entry>
</map:colors>
<xsl:template match="/">
<xsl:for-each select="document('')/*/map:colors/entry">
<xsl:value-of select="key"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This generates the expected output redgreenblue.
The trick is to use document('') to get a handle to the XSLT document itself, then * to get into the toplevel xsl:stylesheet element and from there I can access the color map.