only text value of element through xslt - xslt

I have a below XML. I want to get the text value of Title element. I have used <xsl:value-of select="Title/text()"/>,But it does not fetch the output.
XML-
<Section>
<Chapter>
<Title>
<Marker>MarkerText1</Marker>some text1
</Title>
</Chapter>
<Chapter>
<Title>
<Marker>MarkerText2</Marker>sometext2
<Marker>MarkerText3</Marker>some text3
</Title>
</Chapter>
</Section>
I have used below XSL but it does not fetch any results. And when I used Title/text()[last()] then it gives the last value. I mean text()[last()] is working but not just text()
<xsl:template match="/Section/Chapter">
<xsl:value-of select="Title/text()"/>
</xsl:template>
Output should contain:
<Title>some text1</Title><Title>some text2 sometext3</Title>

If you're using XSLT 1.0, then the value-of a node set is the string value of its first node in document order. So
<xsl:value-of select="Title/text()"/>
will give you the value of the first child text node under the first (in this case only) Title element under the current context node. For the Chapter elements in your example this would be the text node between the opening <Title> tag and the opening <Marker> tag, which consists of a single newline character.
XSLT 2.0 is different, in that case value-of would give you the values of all the selected nodes, separated by spaces, for the first Chapter this would be newline, space, some text1, newline.

I'm not sure what you mean by the "text value", but if you mean the same as what the spec calls the "string value", then use
<xsl:value-of select="Title"/>
If that's not what you want then you need to explain your desired output more clearly.

This XML input:
<?xml version="1.0" encoding="utf-8"?>
<Section>
<Chapter>
<Title>
<Marker>MarkerText1</Marker>some text1
</Title>
</Chapter>
<Chapter>
<Title>
<Marker>MarkerText2</Marker>some text2
<Marker>MarkerText3</Marker>some text3
</Title>
</Chapter>
</Section>
Provided to this XSLT transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<Titles>
<xsl:apply-templates/>
</Titles>
</xsl:template>
<xsl:template match="Title">
<Title><xsl:apply-templates select="text()"/></Title>
</xsl:template>
<xsl:template match="Title/text()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
Yields this XML output:
<?xml version="1.0" encoding="UTF-8"?>
<Titles>
<Title>some text1
</Title>
<Title>some text2
some text3
</Title>
</Titles>
Explanation:
The match="/" template establishes a root element for the produced
Title elements. (The rootless sequence of Title elements you mention in
your question would not be well-formed XML.)
The match="Title" template generates the requested Title elements
and applies pattern matching on the contained text() nodes.
The match="Title/text() template copies over text nodes that are
children of Title elements in the input XML.
If you'd prefer your output to look like this:
<?xml version="1.0" encoding="utf-8"?>
<Titles>
<Title>some text1</Title>
<Title>some text2 some text3</Title>
</Titles>
You can use this slightly more complicated template for match="Title/text()":
<xsl:template match="Title/text()">
<xsl:if test="preceding-sibling::text()">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="normalize-space()"/>
</xsl:template>

Related

Getting 2 result values every for-each iteration

I am using a "xsl:for-each" to iterate over each element with name content and attribute that contains the text "period". When attempting to take out one date per each "xsl:for-each" iteration, it returns 2 values.
The matching of text "period" must be done like this due to the input data might change and it is unknown how many elements with id containing ="period", that would appear in the data.
I would like to keep the xpath search critera in the "xsl:for-each" syntax, because I am using the template to point out root.
When I try to subset the dates using date[1] it still return both dates.
XSLT Fiddle
Same code as in above fiddle:
Data:
<?xml version="1.0" encoding="utf-8" ?>
<section>
<content id="period1">
<date>2021-01-01</date>
</content>
<content id="period2">
<date>2020-01-01</date>
</content>
</section>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="/section">
<xsl:for-each select="//content/#*[contains(., 'period')]">
<date>
<!--<xsl:value-of select="."/>-->
<!--<xsl:value-of select="//date[1]"/>-->
<xsl:value-of select="//content/date"/>
</date>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Result:
<!DOCTYPE HTML>
<date>2021-01-01 2020-01-01</date>
<date>2021-01-01 2020-01-01</date>
Wanted result:
<!DOCTYPE HTML>
<date>2021-01-01</date>
<date>2020-01-01</date>
Use relative paths
<xsl:for-each select="content[#*[contains(., 'period')]]">
<date>
<!--<xsl:value-of select="."/>-->
<!--<xsl:value-of select="//date[1]"/>-->
<xsl:value-of select="date"/>
</date>
</xsl:for-each>

How to find a space between elements in xslt

I want to remove the additional space in the 4th paragraph. It was introduced by the XSLT which I have written.
Given below the XML and XSLT output.
<Chapter>
<para>A <emphasis>B</emphasis> <emphasis>C</emphasis> <emphasis>N</emphasis> D</para>
<para>A <emphasis>B</emphasis> <emphasis>C</emphasis> <emphasis>N</emphasis> D</para>
<para>A <emphasis>B</emphasis> <emphasis>C</emphasis> <emphasis>N</emphasis> D</para>
<para>A <emphasis>B</emphasis><emphasis>C</emphasis> <emphasis>N</emphasis> D</para>
</Chapter>
Given the XSLT code below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="para" />
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="emphasis">
<b><xsl:apply-templates/></b>
<xsl:if test="self::emphasis/following::text()[starts-with(., ' ')] and following-sibling::node()[1][self::emphasis]"><xsl:text> </xsl:text></xsl:if>
</xsl:template>
</xsl:stylesheet>
Expected output:
<p>A <b>B</b> <b>C</b> <b>N</b> D</p>
<p>A <b>B</b> <b>C</b> <b>N</b> D</p>
<p>A <b>B</b> <b>C</b> <b>N</b> D</p>
<p>A <b>B</b><b>C</b> <b>N</b> D</p>
I know the problem is with the strip-space given in the XSLT, that's why the spaces removed between those elements. I have tried to overcome the stip-space and given space between those elements. Still, there is an additional space that has been added in the 4th paragraph after B. Anyone know how to remove that unwanted space or any solution present to give the space between those elements.

XSLT: Iterating through all value of a xsl:key map

XML
<books>
<book title="XML Today" author="David Perry" release="2016"/>
<book title="XML and Microsoft" author="David Perry" release="2015"/>
<book title="XML Productivity" author="Jim Kim" release="2015"/>
</books>
The following XSL code iterates through all books by David Perry.
XSL
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each select="key('title-search', 'David Perry')">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
HTML output
<HTML>
<BODY>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
</BODY>
</HTML>
Now I would like to iterate not only through all books by David Perry but through all books by any author.
How would a corresponding outer loop look like?
Or in other words: How do I iterate through all values of my title-search key.
The output should be something like this:
<HTML>
<BODY>
<H1>David Perry</H1>
<DIV>XML Today</DIV>
<DIV>XML and Microsoft</DIV>
<H1>Jim Kim</H1>
<DIV>XML Productivity</DIV>
</BODY>
</HTML>
This should do the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="title-search" match="book" use="#author"/>
<xsl:template match="/books">
<HTML>
<BODY>
<xsl:apply-templates select="book" />
</BODY>
</HTML>
</xsl:template>
<xsl:template match="book">
<xsl:variable name="author" select="#author" />
<xsl:if test="generate-id(.) = generate-id(key('title-search', $author)[1])">
<H1><xsl:value-of select="#author" /></H1>
<xsl:apply-templates select="//book[#author = $author]" mode="titles"/>
</xsl:if>
</xsl:template>
<xsl:template match="book" mode="titles">
<DIV>
<xsl:value-of select="#title"/>
</DIV>
</xsl:template>
</xsl:stylesheet>
It uses a technique called Muenchian grouping. Each element in an XML document implicitly has a unique ID assigned to it by the XSLT processor (it can also be explicitly assigned with the id attribute in the document itself). This part:
generate-id(.) = generate-id(key('title-search', $author)[1])
basically tests if the ID of the current book element is the same as that of the first book element with the same author. The variable $author is taken from the current book, The key is used to look up the <book> elements with that same author, the [1] predicate takes the first one. As a result, the <H1> is only generated for the first occurrence of that specific author, and in that same if element we're then applying the template for listing the books of that author. The mode is used to avoid a clash between these templates. There's no doubt a solution that doesn't use modes, but this works too. You could also do a lot of this with <xsl:for-each> but I made separate templates because XSLT is declarative and works best when treating it as such.
Grouping is a lot easier in XSLT 2, but when stuck with XSLT 1, the Muenchian grouping technique often provides a solution once you grok it.
Try this:
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<HTML>
<BODY>
<xsl:for-each-group select="books/book" group-by="#author">
<H1><xsl:value-of select="current-grouping-key()"/></H1>
<xsl:for-each select="current-group()">
<DIV><xsl:value-of select="#title"/></DIV>
</xsl:for-each>
</xsl:for-each-group>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>

How to select unique child nodes of all siblings in XSLT 1

I'm looking for the best way to get all unique (no duplicates) nested nodes of all sibling nodes. The node I'm am interested in is "Gases". The sibling nodes are "Content". My simplified XML:
<Collection>
<Content>
<Html>
<root>
<Gases>NO2</Gases>
<Gases>CH4</Gases>
<Gases>O2</Gases>
</root>
</Html>
</Content>
<Content>
<Html>
<root>
<Gases>NO2</Gases>
<Gases>CH4</Gases>
<Gases>CO</Gases>
<Gases>LEL</Gases>
<Gases>NH3</Gases>
</root>
</Html>
</Content>
</Collection>
Desired result: NO2 CH4 O2 CO LEL NH3
I'm new to XSLT so any help would be much appreciated. I've been trying to use XPATH, similar to here, but with no luck.
This XSLT stylesheet will produce the desired output. Note that it relies on there being no duplicate Gases element inside a single Content element.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<!-- Match Gases elements whose value does not appear in a Gases element inside a previous
Content element. -->
<xsl:template match="//Gases[not(. = ancestor::Content/preceding-sibling::Content//Gases)]">
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:template>
<!-- Need to override the built-in template for text nodes, otherwise they will still get
printed out. -->
<xsl:template match="text()"/>
</xsl:stylesheet>

With XSLT, how can I process normally, but hold some nodes until the end and then output them all at once (e.g. footnotes)?

I have an XSLT application which reads the internal format of Microsoft Word 2007/2010 zipped XML and translates it into HTML5 with XSLT. I am investigating how to add the ability to optionally read OpenOffice documents instead of MSWord.
Microsoft stores XML for footnote text separately from the XML of the document text, which happens to suit me because I want the footnotes in a block at the end of the output HTML page.
However, unfortunately for me, OpenOffice puts each footnote right next to its reference, inline with the text of the document. Here is a simple paragraph example:
<text:p text:style-name="Standard">The real breakthrough in aerial mapping
during World War II was trimetrogon
<text:note text:id="ftn0" text:note-class="footnote">
<text:note-citation>1</text:note-citation>
<text:note-body>
<text:p text:style-name="Footnote">Three separate cameras took three
photographs at once, a direct downward and an oblique on each side.</text:p>
</text:note-body>
</text:note>
photography, but the camera was large and heavy, so there were problems finding
the right aircraft to carry it.
</text:p>
My question is, can XSLT process the XML as normal, but hold each of the text:note items until the end of the document text, and then emit them all at one time?
You're thinking of your logic as being driven by the order of things in the input, but in XSLT you need to be driven by the order of things in the output. When you get to the point where you want to output the footnotes, go find the footnote text wherever it might be in the input. Admittedly that doesn't always play too well with the apply-templates recursive descent processing model, which is explicitly input-driven; but nevertheless, that's the way you have to do it.
Don't think of it as "holding" the text:note items, instead simply ignore them in the main pass and then gather them at the end with a //text:note and process them there, e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:text="whateveritshouldbe">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- normal mode - replace text:note element by [reference] -->
<xsl:template match="text:note">
<xsl:value-of select="concat('[', text:note-citation, ']')" />
</xsl:template>
<xsl:template match="/">
<document>
<xsl:apply-templates select="*" />
<footnotes>
<xsl:apply-templates select="//text:note" mode="footnotes"/>
</footnotes>
</document>
</xsl:template>
<!-- special "footnotes" mode to de-activate the usual text:node template -->
<xsl:template match="#*|node()" mode="footnotes">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="footnotes" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You could use <xsl:apply-templates mode="..."/>. I'm not sure on the exact syntax and your use case, but maybe the example below will give you a clue on how to approach your problem.
Basic idea is to process your nodes twice. First iteration would be pretty much the same as now, and the second iteration only looks for footnotes and only outputs those. You differentiate those iteration by setting "mode" parameter.
Maybe this example will give you a clue how to approach your problem. Note that I used different tags that in your code, so the example would be simpler.
XSLT sheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="doc">
<xml>
<!-- First iteration - skip footnotes -->
<doc>
<xsl:apply-templates select="text" />
</doc>
<!-- Second iteration, extract all footnotes.
'mode' = footnotes -->
<footnotes>
<xsl:apply-templates select="text" mode="footnotes" />
</footnotes>
</xml>
</xsl:template>
<!-- Note: no mode attribute -->
<xsl:template match="text">
<text>
<xsl:for-each select="p">
<p>
<xsl:value-of select="text()" />
</p>
</xsl:for-each>
</text>
</xsl:template>
<!-- Note: mode = footnotes -->
<xsl:template match="text" mode="footnotes">
<xsl:for-each select=".//footnote">
<footnote>
<xsl:value-of select="text()" />
</footnote>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<text>
<p>
some text
<footnote>footnote1</footnote>
</p>
<p>
other text
<footnote>footnote2</footnote>
</p>
</text>
<text>
<p>
some text2
<footnote>footnote3</footnote>
</p>
<p>
other text2
<footnote>footnote4</footnote>
</p>
</text>
</doc>
Output XML:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<!-- Output from first iteration -->
<doc>
<text>
<p>some text</p>
<p>other text</p>
</text>
<text>
<p>some text2</p>
<p>other text2</p>
</text>
</doc>
<!-- Output from second iteration -->
<footnotes>
<footnote>footnote1</footnote>
<footnote>footnote2</footnote>
<footnote>footnote3</footnote>
<footnote>footnote4</footnote>
</footnotes>
</xml>