Merge adjacent sibling nodes with XSLT - xslt

I have a question that caused me a terrible headache. Please help me. The input is:
<body>
<p class="section"> section 1 </p>
<p class="code"> some code </p>
<p class="code"> following code </p>
<p class="code"> following code </p>
<p class="section"> section 2 </p>
<p class="code"> other code </p>
<p class="code"> following code </p>
<p class="code"> following code </p>
<p class="section"> section 3 </p>
<p class="code"> still other code </p>
<p class="code"> following </p>
<p class="code"> following </p>
</body>
The output I'd like:
<body>
<p class="section"> section 1 </p>
<pre> some code following code following code </pre>
<p class="section"> section 2 </p>
<pre> other code following code following code </pre>
<p class="section"> section 3 </p>
<pre> still other code following following </pre>
</body>
The problem is to collapse to a <pre> tag all adjacent <p class="code"> tags. Don't find a way to do this using XSLT. Do you think a solution exists?

You don't need to rebuild your XML, take a look here: XSLT Grouping Siblings.

With XSLT 2.0 you can use for-each-group group-adjacent as follows:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="boolean(self::p[#class = 'code'])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<pre>
<xsl:apply-templates select="current-group()/node()"/>
</pre>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You can use XSLT 2.0 with Saxon 9 or with AltovaXML tools.

Something like this should work:
<xsl:template match="body">
<xsl:apply-templates select="p[#class='section']" />
</xsl:template>
<xsl:template match="p[#class='section']">
<xsl:copy-of select="."/>
<pre>
<xsl:variable name="code" select="following-sibling::p[#class='code']" />
<xsl:for-each select="following-sibling::p">
<xsl:variable name="index" select="position()"/>
<xsl:if test="generate-id(.)=generate-id($code[$index])">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</pre>
</xsl:template>

The issue is that your XML isn't structured enough for a simple XSLT solution.
The different "section"s are not really setup in a way that is easy to extract them. If you have control over the input XML see if you can change it to something like this:
<body>
<p class="section"> section 1
<p class="code"> some code </p>
<p class="code"> following code </p>
<p class="code"> following code </p>
</p>
<p class="section"> section 2
<p class="code"> other code </p>
<p class="code"> following code </p>
<p class="code"> following code </p>
</p>
<p class="section"> section 3
<p class="code"> still other code </p>
<p class="code"> following </p>
<p class="code"> following </p>
</p>
</body>
This will let you define a xsl-template for "section"s in which you can xsl-foreach over the "code" classes.

Related

Replace div tag with p tag only if it does not have direct childs as p/ol/ul tags

I need to replace div tag with p tag only if div tag doesn't contains direct child as p/ol/ul tag and if it contains p/ol/ul tag as direct child then just remove the div tag and keep the child tags as it is.
Example:
<div>
<ul>
<li>HIPAA Privacy Module Certificate</li>
</ul>
</div>
<div>
<p>
HIPAA Privacy Module Certificate
</p>
</div>
<div>
<strong>
<li>HIPAA Privacy Module Certificate</li>
</strong>
</div>
Desired output:
<ul>
<li>HIPAA Privacy Module Certificate</li>
</ul>
<p>
HIPAA Privacy Module Certificate
</p>
<p>
<strong>
<li>HIPAA Privacy Module Certificate</li>
</strong>
</p>
What i'm trying but didn't work:
<xsl:template match="div[not(div)]">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="ul[(parent::div)]">
<ul>
<xsl:apply-templates/>
</ul>
</xsl:template>
<xsl:template match="ol[(parent::div)]">
<ol>
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="p[(parent::div)]">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
Thanks in advance.
To me it sounds like doing
<xsl:template match="div[p | ol | ul]">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="div[not(p | ol | ul)]">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
together with the identity transformation.

How combine templates in xslt

I have two templates. I want to combine them together.
<xsl:template match="abc//para/c">
<p type="ccc">
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="abc/c">
<p type="ccc">
<xsl:apply-templates/>
</p>
</xsl:template>
Tried code:
<xsl:template match="abc//para/c or abc/c">
<p type="ccc">
<xsl:apply-templates/>
</p>
</xsl:template>
My tried code is not success.
Use match="abc//para/c | abc/c".

Use for each in xslt

Input :
<lq>
<ol class="- topic/ol ">
<li>Text 1
<ol class="- topic/ol ">
<li>Text 2</li>
<li>Text 3</li>
</ol>
</li>
<li>Text 4</li>
<li>Text 5</li>
</ol>
</lq>
Out should be :
<node>
<p type="extract_number_1">Text 1</p>
<p type="extract_number_2">Text 2</p>
<p type="extract_number_2">Text 3</p>
<p type="extract_number_1">Text 4</p>
<p type="extract_number_1">Text 5</p>
</node>
Tried code :
<xsl:template match="lq/ol">
<xsl:for-each select="li">
<p type="extract_number_{position()}">
<xsl:apply-templates/>
</p>
</xsl:for-each>
</xsl:template>
I have mentioned above my Input Output should be and Tried code. Here <p> #type should be <li> position.
As my tried code I am not getting expected output. I am using XSLT 2.0. How can I solve this. Thank you.
If you want the number in the type attribute to show the nesting level then use e.g.
<xsl:template match="lq/ol">
<xsl:apply-templates select=".//li"/>
</xsl:template>
<xsl:template match="lq/ol//li">
<p type="extract_number_{count(ancestor::ol)}">
<xsl:value-of select="text()"/>
</p>
</xsl:template>

XSLT For-Each Wrapping every nth item in a div

I have a series of nodes that are direct child nodes to a parent I want to loop over those nodes but have them wrapped in 'groups' of 4... I'm probably not wording this very clearly so this might help;
<span class="child01">#nodename</span>
<span class="child02">#nodename</span>
<span class="child03">#nodename</span>
<span class="child04">#nodename</span>
<span class="child05">#nodename</span>
<span class="child06">#nodename</span>
<span class="child07">#nodename</span>
<span class="child08">#nodename</span>
..
<span class="child32">#nodename</span>
<span class="child33">#nodename</span>
..and so on
Goal
<div class="group">
<span class="child01">#nodename</span>
<span class="child02">#nodename</span>
<span class="child03">#nodename</span>
<span class="child04">#nodename</span>
</div>
<div class="group">
<span class="child05">#nodename</span>
<span class="child06">#nodename</span>
<span class="child07">#nodename</span>
<span class="child08">#nodename</span>
</div>
<div class="group">
..
<span class="child32">#nodename</span>
</div>
<div class="group">
<span class="child33">#nodename</span>
..and so on
I have tried variations on this idea - wrapping the lot in the open and closing group tags and every fourth loop drop in a new close / open pair
<div class="group">
<xsl:for-each select="$currentPage/*">
<span>
<xsl:value-of select="#nodeName" />
</span>
<!--
=============================================================
After very 4th item
=============================================================
-->
<xsl:if test="position() mod 4 = 0">
<xsl:text></div><div class="page"></xsl:text>
</xsl:if>
</xsl:for-each>
</div>
But essentially it seems XSLT won't let me start with a closing unmatched tag
The clkoset solution I ahve found so far is a 'fix' in jquery Wrapping a div around every three divs but I would rather not rely on javascript to format the page.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pNumCols" select="3"/>
<xsl:template match="/*">
<xsl:apply-templates select="span[position() mod $pNumCols = 1]"/>
</xsl:template>
<xsl:template match="span">
<div>
<xsl:copy-of select=
".|following-sibling::span[not(position() > $pNumCols -1)]"/>
</div>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<t>
<span class="child01">#nodename</span>
<span class="child02">#nodename</span>
<span class="child03">#nodename</span>
<span class="child04">#nodename</span>
<span class="child05">#nodename</span>
<span class="child06">#nodename</span>
<span class="child07">#nodename</span>
<span class="child08">#nodename</span> ..
<span class="child32">#nodename</span>
<span class="child33">#nodename</span>
</t>
produces the wanted result:
<div>
<span class="child01">#nodename</span>
<span class="child02">#nodename</span>
<span class="child03">#nodename</span>
</div>
<div>
<span class="child04">#nodename</span>
<span class="child05">#nodename</span>
<span class="child06">#nodename</span>
</div>
<div>
<span class="child07">#nodename</span>
<span class="child08">#nodename</span>
<span class="child32">#nodename</span>
</div>
<div>
<span class="child33">#nodename</span>
</div>
If like me you need to transform the source elements that are being divided by position, use xsl:for-each instead of xsl:copy:
<xsl:template match="span">
<ol>
<xsl:for-each select=".|following-sibling::span[not(position() > $pNumCols -1)]"/>
<li><xsl:value-of select="./text()"/></li>
</xsl:for-each>
</ol>
</xsl:template>
Faced by the same problem, that is wanting to output
<div class="container">
<div class="row">
<div class="col">...</div>
<div class="col"/>...</div>
</div>
<div class="row">
...
</div>
</div>
from a CXML (Collection XML) file (http://gallery.clipflair.net/collection/activities.cxml - the data behind the PivotViewer display at http://gallery.clipflair.net/activity)
I coined up the following, based on other suggestions here, but using "mode" attribute of "template" and "apply-templates" XSL tags instead which make it cleaner I believe:
<?xml version="1.0" encoding="UTF-8"?>
<?altova_samplexml http://gallery.clipflair.net/collection/activities.cxml?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cxml="http://schemas.microsoft.com/collection/metadata/2009"
exclude-result-prefixes="cxml"
>
<xsl:output method="html" version="4.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="COLUMNS" select="2"/>
<!-- ########################### -->
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>ClipFlair Activities</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<!-- ########################### -->
<xsl:template match="cxml:Collection">
<div class="container">
<xsl:apply-templates/>
</div>
</xsl:template>
<!-- ########################### -->
<xsl:template match="cxml:Items">
<xsl:apply-templates select="cxml:Item[position() mod $COLUMNS = 1]" mode="row"/>
</xsl:template>
<!-- ########################### -->
<xsl:template match="cxml:Item" mode="row">
<div class="row">
<div>----------</div>
<xsl:apply-templates select=".|following-sibling::cxml:Item[position() < $COLUMNS]" mode="col"/>
</div>
</xsl:template>
<xsl:template match="cxml:Item" mode="col">
<xsl:variable name="URL" select="#Href"/>
<xsl:variable name="FILENAME" select="cxml:Facets/cxml:Facet[#Name='Filename']/cxml:String/#Value"/>
<div class="col">
<xsl:value-of select="$FILENAME"/> --- <xsl:value-of select="$URL"/>
</div>
</xsl:template>
<!-- ########################### -->
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="text()|#*">
</xsl:template>
</xsl:stylesheet>
the output from the above when run in Altova XMLSpy tool (note that it uses altova_samplexml processor instruction to find the XML data) is:
2DaysInParis-OpenActivity-CapRev-FR-EN.clipflair --- http://studio.clipflair.net/?activity=2DaysInParis-OpenActivity-CapRev-FR-EN.clipflair
Abu_Dukhan-CapRev-A1-AR.clipflair --- http://studio.clipflair.net/?activity=Abu_Dukhan-CapRev-A1-AR.clipflair
----------
AFarewellToArms-RevCap-C2-EN.clipflair --- http://studio.clipflair.net/?activity=AFarewellToArms-RevCap-C2-EN.clipflair
agComhaireamhCountingRND.clipflair --- http://studio.clipflair.net/?activity=agComhaireamhCountingRND.clipflair
----------
Al-imtihan-CapRev-B1-AR.clipflair --- http://studio.clipflair.net/?activity=Al-imtihan-CapRev-B1-AR.clipflair
AlBar-Cap-B1-B2-IT.clipflair --- http://studio.clipflair.net/?activity=AlBar-Cap-B1-B2-IT.clipflair
...

XSLT replace tag by generated one

I'm quite new to XSLT.
This is the problem I'm trying to solve for hours now:
I auto-generate a table of contents for a xml document which works great so far. However I'd like to replace a placeholder tag in my source xml with that just generated toc code.
So the output should include the whole document with replaced placeholder-toc-tag with the auto-generated toc xml.
This is what I've tried:
Let's say I have my placeholderTag anywhere in the document and want to replace this/those. I thought that I could loop through all nodes by node() and check if the node name equals my placeholder tag:
<xsl:template match="node()">
<xsl:choose>
<xsl:when test="divGen">
<!-- apply other template to generate toc-->
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="node()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
However the if statement won't match like this.
edit:
Ok, here's the source document (TEI coded - TEI namespace removed):
<TEI>
<teiHeader>
<fileDesc>
<titleStmt>
<title>Title</title>
</titleStmt>
<publicationStmt>
<p>Publication information</p>
</publicationStmt>
<sourceDesc>
<p>Information about the source</p>
</sourceDesc>
</fileDesc>
</teiHeader>
<text>
<front>
<titlePage>
<byline>title page details</byline>
</titlePage>
</front>
<body>
<divGen type="toc"/>
<div type="part">
<div type="section">
<head>heading1</head>
</div>
<div type="section">
<head>heading2</head>
</div>
</div>
<div type="part">
<div type="section">
<head>heading3</head>
</div>
<div type="section">
<head>heading4</head>
</div>
<div type="section">
<head>heading5</head>
</div>
</div>
</body>
<back> </back>
</text>
I'd like to auto-generate the toc from the head values and replace the divGen tag by the auto-produced toc code. However please notice that the divGen tag can be anywhere in the document but not outside of body.
Any ideas?
Chris
Good question, +1.
Here is a complete transformation (with mock TOC generation to be replaced by real one) that shows how to do this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vTOC">
<xsl:apply-templates mode="TOC"/>
<mockTOC>
<xsl:comment>The real TOC generated here</xsl:comment>
</mockTOC>
</xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="divGen[#type='toc']">
<xsl:copy-of select="$vTOC"/>
</xsl:template>
<xsl:template match="text()" mode="TOC"/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<TEI>
<teiHeader>
<fileDesc>
<titleStmt>
<title>Title</title>
</titleStmt>
<publicationStmt>
<p>Publication information</p>
</publicationStmt>
<sourceDesc>
<p>Information about the source</p>
</sourceDesc>
</fileDesc>
</teiHeader>
<text>
<front>
<titlePage>
<byline>title page details</byline>
</titlePage>
</front>
<body>
<divGen type="toc"/>
<div type="part">
<div type="section">
<head>heading1</head>
</div>
<div type="section">
<head>heading2</head>
</div>
</div>
<div type="part">
<div type="section">
<head>heading3</head>
</div>
<div type="section">
<head>heading4</head>
</div>
<div type="section">
<head>heading5</head>
</div>
</div>
</body>
<back>
</back>
</text>
</TEI>
the correct, wanted output is produced, in which any occurences of <divGen type="toc"/> are replaced by the generated TOC:
<TEI>
<teiHeader>
<fileDesc>
<titleStmt>
<title>Title</title>
</titleStmt>
<publicationStmt>
<p>Publication information</p>
</publicationStmt>
<sourceDesc>
<p>Information about the source</p>
</sourceDesc>
</fileDesc>
</teiHeader>
<text>
<front>
<titlePage>
<byline>title page details</byline>
</titlePage>
</front>
<body>
<mockTOC><!--The real TOC generated here--></mockTOC>
<div type="part">
<div type="section">
<head>heading1</head>
</div>
<div type="section">
<head>heading2</head>
</div>
</div>
<div type="part">
<div type="section">
<head>heading3</head>
</div>
<div type="section">
<head>heading4</head>
</div>
<div type="section">
<head>heading5</head>
</div>
</div>
</body>
<back/>
</text>
</TEI>
Explanation: Use of modes to pre-generate the TOC in a variable, then overriding the identity rule for any TOC placeholder.
Im guessing somewhere u have a template like
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
then u want the template to only match your placeholderTag
then the others will default to your other nodes!
<xsl:template match="placeholderTag">
<!-- applying generate toc thing-->
</xsl:template>
<xsl:template match="node()">
<xsl:copy-of select="node()"/>
</xsl:template>