XSLT/Diazo: create element for each pair of elements - xslt

I'm using Diazo/XSLT for theming a Plone website. On the homepage, Plone gives me the following structure:
<dl>
<dt>
The news item title
</dt>
<dd>
The content of the news item
</dd>
(and so on for the following ones)
</dl>
Which I want to transform into something like that:
<div id="news_items">
<div>
<h2>The news item title</h2>
The content of the news item.
<a class="readmore" href="<the HREF taken from the dt/a tag)">Read more</a>
</div>
(and so on for the following items)
</div>
I'm not really familiar with XSLT and Diazo (and more used to rewrite some pieces of existing themes) but I tried a few solutions.
The first one was to do this in two times. First look for each "dd" tags, creates the structure and after that update by parsing all "dt" tags:
<copy css:theme="#news_items">
<xsl:for-each css:select="#content-core dl dd">
<xsl:element name="div">
<xsl:element name="h2">
</xsl:element>
<xsl:copy-of select="./*" />
<xsl:element name="a">
<xsl:attribute name="class">readmore</xsl:attribute>
Read more
</xsl:element>
</xsl:element>
</xsl:for-each>
</copy>
It creates the structure correctly, but I don't know how to write the second part. The idea would be something like that:
<xsl:for-each css:select="#content-core dl dt">
<!-- Copy the link in the 'dd' tag into the h2 tag, based on the position -->
<copy css:theme="#news_item div:nth-child(position()) h2" css:select="a" />
<!-- Copy the a tag's href in the 'Read more' tag -->
<copy css:theme="#news_item div:nth-child(position()) a.readmore">
<xsl:attribute name="class">
<xsl:value-of select="#class" />
</xsl:attribute>
</copy>
</xsl:for-each>
I know it does not make that much sense, but I hope you get the idea of it:
I look into each "dd" tag
I find, based on the position in the loop, the 'h2' tag created in the previous step and I copy the link inside the "dd" tag.
I find (based on the position again) the "a.readmore" tag and copy the href.
The second solution I've been thinking about is a bit dirtier (and does not work either). The idea is to create the content in one step:
<xsl:for-each css:select="#content-core dl > *">
<xsl:if test="name = dt">
<!-- That the dt tag, we start the 'div' tag and add the h2 tag -->
</xsl:if>
<xsl:if test="name = dt">
<!-- That the dt tag, we copy the content and close the 'div' tag -->
</xsl:if>
</xsl:foreach>
But I really don't like the solution (and I don't see how to create the 'Read more' link).
Do you think the first solution can be done ? (especially the second step to populate the "h2" tag and add the "href" attribute on the "Read more" link).
Is there a way better solution available ?
I'd prefer to use only Diazo, but if it's not doable I can directly override the view in Plone (which would be simpler I think, at least I know how to do that)
Thanks for your help.

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:template match="/*">
<div id="news_items">
<xsl:apply-templates select="dt"/>
</div>
</xsl:template>
<xsl:template match="dt">
<div>
<h2><xsl:copy-of select="a"/></h2>
<xsl:apply-templates select="following-sibling::dd[1]"/>
<a class="readmore" href="{a/#href}">Read more</a>
</div>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (with the same structure as the provided one but with more items):
<dl>
<dt>
The news item1 title
</dt>
<dd>
The content of the news item1
</dd>
<dt>
The news item2 title
</dt>
<dd>
The content of the news item2
</dd>
</dl>
produces the wanted, correct result:
<div id="news_items">
<div>
<h2>
The news item1 title
</h2>
The content of the news item1
<a class="readmore" href="someUrl1">Read more</a>
</div>
<div>
<h2>
The news item2 title
</h2>
The content of the news item2
<a class="readmore" href="someUrl2">Read more</a>
</div>
</div>

Related

Remove child element based on some attribute criteria of child element xslt 1

I need to remove the child element on the bases of the value of an id attribute. If there is no value in id attribute or there is no id attribute at all in contentblock tag then just remove the element and parent element will remain same if present. Also if content block is direct child of root and that content block doesn't have value in id attribute or there is no id attribute then remove that element also.
Example:
<root>
<div>
<contentblock class="align-center" id="" />
</div>
<p>
<contentblock class="align-center" />
</p>
<h2>
<contentblock class="align-center" id="623a7a1f87dd1975ce084ac7"/>
</h2>
<contentblock class="align-center" id=""/>
<contentblock class="align-center" id="623a7a1f87dd1975ce084ac7"/>
<contentblock class="align-center"/>
</root>
Expected Result:
<root>
<div>
</div>
<p>
</p>
<h2>
<contentblock class="align-center" id="623a7a1f87dd1975ce084ac7"/>
</h2>
<contentblock class="align-center" id="623a7a1f87dd1975ce084ac7"/>
</root>
Thanks in advance for the help.
What i tried but didn't give the expected result:
<!--<xsl:template match="contentblock[not(parent::root)] | contentblock[(parent::root)]">-->
<!--<xsl:choose>-->
<!-- <xsl:if test="contentblock/#id[string-length(.) =0]">-->
<!-- <xsl:apply-templates/>-->
<!-- </xsl:if>-->
<!--</xsl:choose>-->
<!--</xsl:template>-->
Another try:
<xsl:template match="div[contentblock] | p[descendant::contentblock] | h2[descendant::contentblock] | h3[descendant::contentblock]">
<xsl:choose>
<xsl:when test="contentblock[#id!='']">
<xsl:apply-templates/>
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="contentblock[#id=''] | contentblock[not(#id)]">
<xsl:copy> <xsl:apply-templates select="#*"/></xsl:copy>
</xsl:when>
</xsl:choose>
</xsl:template>
I can't match <xsl:template match="contentblock[not(parent::root)]"> as it performs the transformation on content block element itself which gives me different result. And also above solution does not work when i get the xml like this. When contentblock have multiple level parents like here it's p and span are parents of contentblock.
<p id="5c3692c8af8fe1f061518abc">
<span bulb-font-face="museo-sans, sans-serif">
<contentblock class="align-center block-full-width" id="5c3686fdcb7de304cd51696f" />
</span>
</p>
This template would match contentblock elements with an id attribute that has some value:
<xsl:template match="contentblock[#id!='']"/>
You would have to apply this template to all relevant contentblock elements, and in the template you would have to return whatever you want to return, like
<xsl:copy-of select="."/>

Change div tag's attribute based on position()

I'm trying to parse a xml in which I need to Change div tag's attribute based on position() for the first one alone. As you can see below, I've already used a apply template for the parent div. How can I apply the condition for just the first div alone inside apply template?
<div class="maintitle">
<xsl:apply-templates select="title"/>
</div>
<xsl:template match="title">
<h3 class="sub title">
<span>
</span>
</h3>
<div class="slide">
<!-- ... html chunk ... -->
</div>
</xsl:template>
but for the first div in apply template should come as
<h3 class="sub title">
<span>
</span>
</h3>
<div class="first slide">
<!-- ... html chunk ... -->
</div>
Since you haven't shown what your input looks like, any answer will involve some guesswork.
But in general, there are two ways to make XSLT handle the first element of a series differently from others.
First, you can make two different templates, one for the first title element and one for the others:
<xsl:template match="title">
<!--* handling for most title elements *-->
</
<xsl:template match="title[1]">
<!--* handling for the first title *-->
</
In this case, that probably involves more duplication of the template code than one would like.
Second, you can use a conditional to surround the relevant code. Here, using an XSL attribute constructor instead of a literal result attribute will simplify things:
<xsl:template match="title">
<h3 class="sub title">
<span>
</span>
</h3>
<div>
<xsl:attribute name="class">
<xsl:if test="position() = 1">
<xsl:text>first</xsl:text>
</xsl:if>
<xsl:text> slide</xsl:text>
</xsl:attribute>
<!-- ... html chunk ... -->
</div>
</xsl:template>
These assume that:
each title element in your input turns into one slide in your output;
the title elements are siblings;
the title elements are not intermixed with elements of other types.
If these assumptions don't hold, the way you write the match patterns or the test conditions will differ from what's shown.

XSLT: templates without mode ignored in result-documents

I'm learning to use the mode attribute and must being doing something wrong. Apologies if this has been answered before but I'm not finding it here.
I want to process "title" elements separately depending on their context. For the main document, I want to add an "a" element inside of it:
<xsl:template match="title">
<div>
<xsl:value-of select="."/>
<a href="some_URL">some text
</a>
</div>
</xsl:template>
But elsewhere I'm creating result-documents where I just want the title:
<xsl:tamplate match="title" mode="print">
<div class="title">
<xsl:value-of select="."/>
</div>
</xsl:template>
In my main template match="/" I'm doing a for-each for each section, creating a result-document for each one:
<xsl:for-each select="/topic/body/bodydiv/section">
<xsl:result-document href="{$printoutfile}">
<html><head>some stuff</head><body>
<div class="section">
<xsl:apply-templates mode="print"/>
</div>
... more stuff...
</body</html>
</xsl:result-document>
</xsl:for-each>
Then I call everything else for the main document:
<html><head>stuff</head>
<body>
<div>
<xsl:apply-templates/>
</div>
</body>
</html>
The problem is this works for the result-document title but none of the rest of the result-document templates are used, since they don't have mode="print" on them. So the rest of the result-document all comes out as text.
Any idea what I need to do here? I'm obviously missing something basic.
thank you
Scott
You have not shown any of the other templates but if you expect the <xsl:apply-templates mode="print"/> to apply them then you need to have a mode="#all" on them. And if they do additional apply-templates then you need to use <xsl:apply-templates select="#current"/>. See http://www.w3.org/TR/xslt20/#modes for details.

XSL: Using a dynamic select list to generate a filtered list

I have a XML file of names and phone numbers and the cities associated with them (could be multiple cities). What I want to do is generate a unique list of cities and then let the user select one of the cities. When they do a list of all the names that are associated with that city is displayed.
I can generate the unique list of cities. I can generate the names associated with a hardcoded city. I can make the name/phone list visible or invisible when the city is selected. What I can't do is figure out how to use the select list "selected" value to filter the names. I realize this is probably trivial, but be kind to a newb and tell me how it's done!
Here's my code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<script type="text/javascript">
var showList = function() {
document.getElementById("swarms").style.visibility = "visible";
<xsl:variable name="thisOne" select="'document.allCities.select.options[document.allCities.select.selectedIndex].value'" />
}
</script>
</head>
<body>
<!-- Display list of cities -->
<div id="city_list">
<xsl:variable name="unique-list" select="swarmlist/member/cities/city[not(.=following::city)]" />
<form name="allCities">
<strong>Select A City and Click the Button to Display the List: </strong>
<select name="select">
<option value="all">All</option>
<xsl:for-each select="$unique-list">
<xsl:sort select="."/>
<option>
<xsl:attribute name="value"><xsl:value-of select="."/></xsl:attribute>
<xsl:value-of select="."/>
</option>
</xsl:for-each>
</select>
<input name="submit" type="button" value="Display" onclick="showList();" />
</form>
</div>
<!-- End of City list -->
<!-- Display list of volunteers -->
<table border="1" id="swarms" style="visibility:hidden">
<tr bgcolor="#9acd32">
<th>Name</th>
<th>Phone</th>
</tr>
<xsl:for-each select="swarmlist/member">
<xsl:choose>
<xsl:when test="cities/city = $thisOne">
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="phone"/></td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</table>
<!-- End of volunteer list -->
</body>
</html>
</xsl:template>
</xsl:stylesheet>
You're trying to define a XSLT variable based on a Javascript expression. As far as I know, that will not work because XSLT is processed before the page is rendered, and before any Javascript is executed. In other words, you can't use XSLT to dynamically change the content of a page based on user actions.
You could try to change your button to a real <input type="submit"> and let the page to reload when the user clics on it. This way, you would have the selected city as a request parameter, and you could read it into your $thisOne variable.
I've never captured request parameters in XSLT myself, but hopefully, this may help you:
querystring using xslt
Another approach, much easier (and extremely inefficient) would be to parse the whole list into different <div> blocks, and then use Javascript to hide them and show the correct one.

Using XSLT to process an HTML page and move a DIV from one place to another

I have a HTML page (fully valid) that is post-processed by XSLT.
Let's say the relevant section of code looks like this:
<div id="content"> ... </div>
...
<div id="announcement"> ... </div>
The XSLT needs to transform it to look like this:
<div id="content"> <div id="announcement"> ... </div> ... </div>
Any ideas? I'm stuck.
Edit: Indicated that <div id="content"> and <div id="announcement"> are separated by other code.
If the announcement div directly follows the content div, use:
<xsl:template match="div[#id='content' and following-sibling::div[1][#id='announcement']">
<!-- copy the content div and its attributes -->
<xsl:copy>
<xsl:copy-of select="#*" />
<!-- now make a copy of the directly following announcement div -->
<xsl:copy-of select="following-sibling::div[1][#id='announcement']" />
<!-- process the rest of the contents -->
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<!-- the empty template mutes an announcement div that follows a content div -->
<xsl:template match="div[#id='announcement' and preceding-sibling::div[1][#id='content']" />
The above is specific enough not to touch any other divs that might be in your document. If your situation allows, you can make it simpler/less specific to increase readability.