Reputation: 363
I am attempting to move a node into it's previous sibling's child, and the fact that everything is on the same level is making it a little tricky for me.
Illustration of my input:
<dl>
<dlentry>
<dt> Title 1 </dt>
<dd> Title 1's definition </dd>
<dt> Title 2 </dt>
<dd> Title 2's definition </dd>
<dt> Title 3 </dt>
<dd> Title 3's definition </dd>
</dlentry>
</dl>
<p> part of title 3's definition </p>
<p> another part of title 3's definition </p>
What I am attempting to do is to take those 2 <p>
elements at the bottom and concatenate their text to end of the last <dd>
element's text in <dlentry>
because they are a part of that definition for "Title 3".
Desired output:
<dl>
<dlentry>
<dt> Title 1 </dt>
<dd> Title 1's definition </dd>
<dt> Title 2 </dt>
<dd> Title 2's definition </dd>
<dt> Title 3 </dt>
<dd> Title 3's definition part of title 3's definition another part of title 3's definition </dd>
</dlentry>
</dl>
Another issue I'm dealing with is because of how bad the XHTML is in my source document, I need to do a regex match to on the text for those <p>
elements to make sure it doesn't hit anywhere else in the document.
I was able to successfully insert the first <p>
's text as desired but am having trouble getting it to work in so I can do my regex match and also getting that 2nd
element's text into the desired location as well.
Here is a code fragment from my stylesheet, using XSLT 2.0.
<xsl:analyze-string select="."
regex="my regex expression here">
<xsl:template match="dlentry">
<xsl:matching-substring>
<dlentry>
** <xsl:copy-of select="node()[ position() lt last()]"/>
<dd>
<xsl:copy-of select="node()[last()]/text()" />
<xsl:copy-of select=" parent::node()/following-sibling::node()[1]/text()"/>
</dd>
</dlentry>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select=".">
</xsl:non-matching-substring>
</xsl:template>
<xsl:template match="p[preceding-sibling::node()[1][self::node()[name(.)='dl']]]" />
<xsl:template match="p[preceding-sibling::node()[2][self::node()[name(.)='dl']]]" />
At the code line with the ** asterisks Saxon throws an error saying "Axis step child::node() cannont be used here: the context item is an atomic value." I am not familiar with analyze-string but if I run my copy-of selects outside of analyze-string and just in a template, it runs fine.
Sorry that this question was kind of long but I wanted to share everything I had to this point.
Thanks in advance.
Upvotes: 1
Views: 1074
Reputation: 243449
This short and simple XSLT 1.0 (and of course it is also XSLT 2.0):
<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:key name="kFollowing" match="p" use="generate-id(preceding-sibling::dl[1])"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dlentry/dd[last()]">
<dd>
<xsl:apply-templates select=
"(.|key('kFollowing', generate-id(ancestor::dl[1])))/text()"/>
</dd>
</xsl:template>
<xsl:template match="p"/>
</xsl:stylesheet>
when applied on the provided XML document:
<html>
<dl>
<dlentry>
<dt> Title 1 </dt>
<dd> Title 1's definition </dd>
<dt> Title 2 </dt>
<dd> Title 2's definition </dd>
<dt> Title 3 </dt>
<dd> Title 3's definition </dd>
</dlentry>
</dl>
<p> part of title 3's definition </p>
<p> another part of title 3's definition </p>
</html>
produces the wanted, correct result:
<html>
<dl>
<dlentry>
<dt> Title 1 </dt>
<dd> Title 1's definition </dd>
<dt> Title 2 </dt>
<dd> Title 2's definition </dd>
<dt> Title 3 </dt>
<dd> Title 3's definition part of title 3's definition another part of title 3's definition </dd>
</dlentry>
</dl>
</html>
Upvotes: 2
Reputation: 1303
Not sure about efficiency, but following xsl should produce the requested output:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/doc">
<xsl:for-each select="dl">
<dl>
<xsl:for-each select="dlentry">
<xsl:apply-templates select="dt|dd"/>
</xsl:for-each>
</dl>
</xsl:for-each>
</xsl:template>
<xsl:template match="dt">
<dt><xsl:value-of select="."/></dt>
</xsl:template>
<xsl:template match="dd">
<dd>
<xsl:value-of select="."/>
<!-- Check if this is the last element (= no dd/dd after) -->
<xsl:if test="not(following-sibling::*)">
<!-- Select dl's next sibling, if it's a <p> -->
<xsl:for-each select="../../following-sibling::*[1][name() = 'p']">
<!-- Call recursive template -->
<xsl:call-template name="concat"/>
</xsl:for-each>
</xsl:if>
</dd>
</xsl:template>
<xsl:template name="concat">
<xsl:value-of select="."/>
<!-- Select p's next sibling, if it's a <p> -->
<xsl:for-each select="following-sibling::*[1][name() = 'p']">
<!-- Call recursive template -->
<xsl:call-template name="concat"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And here's the input that I tested it with:
<doc>
<dl>
<dlentry>
<dt> Title 1 </dt>
<dd> Title 1's definition </dd>
<dt> Title 2 </dt>
<dd> Title 2's definition </dd>
<dt> Title 3 </dt>
<dd> Title 3's definition </dd>
</dlentry>
</dl>
<p> part of title 3's definition </p>
<p> another part of title 3's definition </p>
</doc>
Upvotes: 1