XSLT copy parent element with attributes and change child elements - xslt

This piece of HTML table
<tr>
<td>
<p>only one cell without break</p>
</td>
<td colspan="3">
<p>text 1</p>
<p>text 2</p>
<p>text 3</p>
</td>
<td colspan="3">
<list>
<list-item>
<p>list item 1</p>
</list-item>
<list-item>
<p>list item 2</p>
</list-item>
<list-item>
<p>list item 3</p>
</list-item>
</list>
</td>
</tr>
has to be translated into:
<tr>
<td>
only one cell without break
</td>
<td colspan="3">
text 1<break/>
text 2<break/>
text 3
</td>
<td colspan="3">
<list>
<list-item>
<p>list item 1</p>
</list-item>
<list-item>
<p>list item 2</p>
</list-item>
<list-item>
<p>list item 3</p>
</list-item>
</list>
</td>
</tr>
I'm using this piece of XSLT code:
<xsl:template match="td">
<td>
<xsl:for-each select="p">
<xsl:apply-templates/>
<xsl:if test="position() != last()">
<break/>
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="* except p"/>
</td>
</xsl:template>
I have to select <td> as parent node so I can change all <p> tags within one cell with <break/> tag for all elements except the last one, where there are more than one <p> inside a cell. Where there's only one <p> inside a cell, the <p> tag has to be removed as well, but <break/> doesn't need to be added. All other tags (i.e. list) stay intact. It works, but I am losing "colspan" attribute with this code.
For obvious reasons (manually setting up <td>) the attribute colspan isn't copied from parent.
Is it possible somehow to "fix" the code and copy parent <td> element with all attributes to output table somehow?

The only transformation represented by the samples is
<xsl:template match="tr/td/p">
<x>
<xsl:apply-templates select="#* | node()"/>
</x>
</xsl:template>
the rest can be handled by the identity transformation (e.g. <xsl:mode on-no-match="shallow-copy"/> in XSLT 3 or a template spelling that out in XSLT 2 or 1).
After the edit of the question the task is completely different but still in my view to be solved with push style apply-templates and template matching, the two transformations you want are expressed as two templates:
<xsl:template match="tr/td/p">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="tr/td/p[not(position() = last())]">
<xsl:apply-templates/>
<break/>
</xsl:template>
the rest will be handled by the identity transformation.
https://xsltfiddle.liberty-development.net/pNmCztv

Related

xsl:for-each inside xsl:for-each in table rows and columns

I've just started learning XML/XSL and I've hit a roadblock in one of my assignments. Tried Googling and searching over here but I can't seem to find a question that has a solution that is basic. so what I am trying is to display rows of bucket-type and room-types associated with it. can somebody please help
<list-inventory list-count="2">
<list list-type="Standard" list-order = "1" count-Types = "3">
<types type="BEN2D"></room>
<types type="BESH2D"></room>
<types type="HNK"></room>
</list>
<list list-type="Deluxe" list-order = "2" count-Types = "3">
<types type="SNK"></room>
<types type="TESTKD"></room>
<types type="TESTKD"></room>
</list>
<list-inventory>
I want table as below
Standard | Deluxe
BEN2D |SNK
BESH2D |TESTKD
HNK |TESTKD
I tried below xsl code but i see all list-type in single column and only 1st is being printing for all list-type:
<xsl:for-each select="/contents/list-inventory/list">
<tr>
<td class="alt-th" style="border:1px solid black">
<xsl:value-of select="#list-type"/>
</td>
</tr>
<tr>
<td style="border:1px solid black">
<xsl:for-each select="/contents/list-inventory/list/types">
<span><xsl:value-of select="#type"/></span>
<xsl:if test="position()!=last()">
<br/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
Can someone help me with xsl:for-each inside a xsl:for-each
It's too bad you did not post your expected result as code. I would assume that you want a separate row for each pair of values. As I stated in the comments, this is far from being trivial.
However, you could make it simpler if you are willing to settle for a single data row, where each cell contains all the values of the corresponding list (your attempt seems to suggest that this is what you actually tried to accomplish).
So, given a well-formed XML input:
XML
<list-inventory list-count="2">
<list list-type="Standard" list-order = "1" count-Types = "3">
<types type="BEN2D"/>
<types type="BESH2D"/>
<types type="HNK"/>
</list>
<list list-type="Deluxe" list-order = "2" count-Types = "3">
<types type="SNK"/>
<types type="TESTKD"/>
<types type="TESTKD"/>
</list>
</list-inventory>
you could do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/list-inventory">
<table border="1">
<!-- header -->
<tr>
<xsl:for-each select="list">
<th>
<xsl:value-of select="#list-type"/>
</th>
</xsl:for-each>
</tr>
<!-- body -->
<tr>
<xsl:for-each select="list">
<td>
<xsl:for-each select="types">
<xsl:value-of select="#type"/>
<br/>
</xsl:for-each>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
to get:
Result
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<th>Standard</th>
<th>Deluxe</th>
</tr>
<tr>
<td>BEN2D<br/>BESH2D<br/>HNK<br/>
</td>
<td>SNK<br/>TESTKD<br/>TESTKD<br/>
</td>
</tr>
</table>
which would render as:
I'm not sure how general you want your solution to be (i.e what inputs does it have to handle other than the example shown), but I would do something like:
<xsl:template match="list-inventory">
<xsl:variable name="list2" select="list[2]/types"/>
<xsl:for-each select="list[1]/types">
<xsl:variable name="position" select="position()"/>
<tr>
<td><xsl:value-of select="#type"/></td>
<td><xsl:value-of select="$list2[$position]/#type"/></td>
</tr>
</xsl:for-each>
</xsl:template>
Both the variables here are needed to avoid problems with context: the effect of an XPath expression depends on the context at the time it is evaluated, so you can evaluate a variable to capture information at the time you're in the right context.

XSLT: multiple tables

I come across this small problem while creating a xslt file... I have this generic xml file:
<data>
<folder>
<file>
<name>file1</name>
<date>2000</date>
<index1>1</index1>
<index2>1</index2>
</file>
<file>
<name>file2</name>
<date>2001</date>
<index1>1</index1>
<index2>1</index2>
</file>
<file>
<name>file3</name>
<date>2004</date>
<index1>2</index1>
<index2>1</index2>
</file>
</folder>
</data>
Given this abstract example, I have to transform it into something like:
<table>
<tr>
<td>Name</td>
<td>Date</td>
</tr>
<tr>
<td>file1</td>
<td>2000</td>
</tr>
<tr>
<td>file2</td>
<td>2001</td>
</tr>
</table>
<table>
<tr>
<td>Name</td>
<td>Date</td>
</tr>
<tr>
<td>file3</td>
<td>2004</td>
</tr>
</table>
I have to group the file elements per table based on their index1 and index2 (like an ID pair). I am able to create a table for every separated file, but I can't come with a solution to create a table for every file sharing index1 and index2. Any idea or suggestion?
Since you are using XSLT 2.0 you can use the xsl:for-each-group statement. You have two choices here, depending on whether you wish to keep groups together and respect the sequence or whether you just want to group regardless of sequence.
That is, given aabaab would you want groups of (aaaa, bb) or (aa, b, aa, b)?
This first groups all file elements with the same index1 and index2 regardless of order in the document (I've put in the body element just to make it well-formed)
<xsl:template match="folder">
<body>
<xsl:for-each-group select="file" group-by="concat(index1, '-', index2)">
<!-- xsl:for-each-group sets the first element in the group as the context node -->
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</body>
</xsl:template>
<xsl:template match="file">
<table>
<tr>
<td>Name</td>
<td>Date</td>
</tr>
<xsl:apply-templates select="current-group()" mode="to-row"/>
</table>
</xsl:template>
<xsl:template match="file" mode="to-row">
<tr>
<xsl:apply-templates select="name|date"/>
</tr>
</xsl:template>
<xsl:template match="name|date">
<td><xsl:apply-templates/></td>
</xsl:template>
The second version would only need the first template changed to:
<xsl:template match="folder">
<body>
<xsl:for-each-group select="file" group-adjacent="concat(index1, '-', index2)">
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</body>
</xsl:template>

xsl 1.0 Why won't a node match return data

I'm using the following xml information:
<section>
<...>
</section>
<section>
<templateId root="2.16.840.1.113883.10.20.22.2.10" />
<text>
<table id="Appointments">
<tr>
<td id="heading">Appointments</td>
</tr>
<tr>
<td id="content">No future appointments scheduled.</td>
</tr>
</table>
<br />
<table id="Referrals">
<tr>
<td id="heading">Referrals</td>
</tr>
<tr>
<td id="content">No referrals available.</td>
</tr>
</table>
<br />
</text>
<section>
<section>
<...>
</section>
There are multiple section nodes (with their own child nodes, including templateId) within the document. I'm having trouble with this one so I wanted to be specific in the xml information.
and in my xslt file I want to get one particular table out. I'm referencing it the following way (I'm trying to use templates and I'm new to XSL so please bear with me)
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//section[templateId/#root='2.16.840.1.113883.10.20.22.2.17']/text/table[#id='Appointments']" />
</xsl: template>
<xsl:template match="section[templateId/#root='2.16.840.1.113883.10.20.22.2.17']/text/table[#id='Appointments']">
<div style="float: left; width: 50%;">
<span style="font-weight: bold;">
<xsl:value-of select="tr/td[#id='heading']"/>:
</span>
<br />
<xsl:call-template name="replace">
<xsl:with-param name="string" select="tr/td[#id='content']"/>
</xsl:call-template>
</div>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:choose>
<xsl:when test="contains($string,'
')">
<xsl:value-of select="substring-before($string,'
')"/>
<br/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In this particular xml example, the output should be:
Appointments:
No future appointments scheduled.
I'm thinking the match and select need some tweaking but not sure what part.
Also, if the template can be tweaked so that I could pass a parameter with the table/#id value so that I could reuse this one template for a couple of items,that would be even more beneficial (the output for referrals and appointments that are in this example would be the same).
Thanks for any help
This is your XML section root attribute (cut and paste from your XML):
root="2.16.840.1.113883.10.20.22.2.10"
This is your test XSL:
root='2.16.840.1.113883.10.20.22.2.17'
Of course they do not match, one ends with "10", the other with "17"
Changing the data to "17" and correcting the other errors in my comments yields:
<div style="float: left; width: 50%;"><span style="font-weight: bold;">Appointments:
</span><br>No future appointments scheduled.
</div

The variable or parameter 'Rows' is either not defined or it is out of scope

How do i achieve this through xslt?
I am getting an error from the cde above given by you
( sharepoint 2010 xslt dataview : modify table structure to display list item )
The error is: The variable or parameter 'Rows' is either not defined or it is out of scope.
Please help why am i getting this error:(
My full code would go like this after the code by you has been added:
<xsl:template match='dsQueryResponse'>
<table cellpadding="10" cellspacing="0" border="1" style="padding:25px;">
<!--table for head-->
<tr>
<!--table row-->
<td colspan='2'>
<!--table definition-->
<b style="font-size:25px;">ELearning List</b>
<!-- heading-->
</td>
</tr>
<xsl:apply-templates select='Rows/Row'/>
</table>
</xsl:template>
<xsl:template match='Row'>
<!-- template is defined above -->
<xsl:for-each select="$Rows[position() mod 3 = 1]">
<!-- 3 recods in one row should be displayed -->
<tr>
<xsl:variable name="i" select="position() - 1" />
<xsl:for-each select="$Rows[(position() > ($i * 3)) and (position() <= (($i + 1) * 3))]">
<td>
<img src="../PublishingImages/FLDRNEW.GIF" width="50px" height="50px" style="padding-right:20px;"></img>
<!-- image is to display folder below which the hyperlinked text has been populated -->
<br/>
<a href="{#FileRef}" style="font-weight:bold;">
<!-- it is the anchor tag which drives to the corresponding documents -->
<xsl:value-of select="substring-after(string(#FileRef),'/Docs/')"/>
<!-- fetches the value of Name column -->
</a>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</xsl:template>
The error is: The variable or parameter 'Rows' is either not defined or it is out of scope.
Please help
Could you give this a try?
<xsl:template match='dsQueryResponse'>
<table cellpadding="10" cellspacing="0" border="1" style="padding:25px;">
<!--table for head-->
<tr>
<!--table row-->
<td colspan='2'>
<!--table definition-->
<b style="font-size:25px;">ELearning List</b>
<!-- heading-->
</td>
</tr>
<xsl:apply-templates
select='Rows/Row[position() mod 3 = 1]' mode="group" />
</table>
</xsl:template>
<xsl:template match='Row' mode="group">
<tr>
<xsl:apply-templates
select=". | following-sibling::Row[position() < 3]" />
</tr>
</xsl:template>
<xsl:template match="Row">
<td>
<!-- image is to display folder below which the hyperlinked text has
been populated -->
<img src="../PublishingImages/FLDRNEW.GIF" width="50px" height="50px"
style="padding-right:20px;" />
<br/>
<!-- the anchor tag which drives to the corresponding documents -->
<a href="{#FileRef}" style="font-weight:bold;">
<!-- fetches the value of Name column -->
<xsl:value-of select="substring-after(string(#FileRef),'/Docs/')"/>
</a>
</td>
</xsl:template>

How to access a root attribute from a deeper level in XSLT

I'm calling a template:
<table>
<xsl:apply-templates select="data/pics/row"/>
</table>
The template is
<xsl:template match="row">
<tr>
<xsl:for-each select="td">
<td border="0">
<a href="{#referencddePage}">
<img src="{pic/#src}" width="{pic/#width}" height="{pic/#height}"/>
</a>
</td>
</xsl:for-each>
</tr>
</xsl:template>
My XML is:
<?xml version="1.0" encoding="iso-8859-8"?>
<?xml-stylesheet type="text/xsl" href="xslFiles\smallPageBuilder.xsl"?>
<data pageNo="3" referencePage="xxxxxxxxxxxxxxx.xml">
<pics>
<row no="0">
<td col="0">
<pic src="A.jpg" width="150" height="120"></pic>
</td>
</row>
</pics>
</data>
I want the line :a h r e f="{#referencddePage}" to get the input from
the root, :a h r e f= "{#referencddePage}"..., but I'm already in the <td level>.
I want the line :a h r e
f="{#referencddePage}" to get the
input from the root :a h r e f=
"{#referencddePage}"... but I'm
already in the <td level>
In case it is a rule that the #referencePage attribute is always an attribute of the top element, then it can always be accessed as:
/*/#referencePage
Therefore, in your code you'll have:
<a href="{/*/#referencePage}">
I would recommend not to use <xsl:for-each> and to use only and`. In this way the resulting XSLT code is more understandable and can be more easily modified in the future:
<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:template match="row">
<tr>
<xsl:apply-templates/>
</tr>
</xsl:template>
<xsl:template match="td">
<td border="0">
<a href="{/*/#referencePage}">
<xsl:apply-templates/>
</a>
</td>
</xsl:template>
<xsl:template match="pic">
<img src="{#src}" width="{#width}" height="{#height}"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document,
<data pageNo="3" referencePage="xxxxxxxxxxxxxxx.xml">
<pics>
<row no="0">
<td col="0">
<pic src="A.jpg" width="150" height="120"></pic>
</td>
</row>
</pics>
</data>
the wanted output is produced:
<tr>
<td border="0">
<a href="xxxxxxxxxxxxxxx.xml">
<img src="A.jpg" width="150" height="120"/>
</a>
</td>
</tr>
See how each template is so very simple. Also, the code is further simplified.
Now, instead of:
<img src="{pic/#src}" width="{pic/#width}" height="{pic/#height}"/>
we have only:
<img src="{#src}" width="{#width}" height="{#height}"/>
Use an XPATH that "jumps" to the top of the document with a leading slash, then walk down the tree:
/data/#referencePage
Applying it to your stylesheet:
<xsl:template match="row">
<tr>
<xsl:for-each select="td">
<td border="0">
<a href="{/data/#referencePage}">
<img src="{pic/#src}" width="{pic/#width}" height="{pic/#height}"/>
</a>
</td>
</xsl:for-each>
</tr>
</xsl:template>