I need get node if it contains text.
So when i process <p> tag - i need to check previous <topic> if it has text in body or in any child tag in <body>
With next XSL code
ancestor::topic[1]/preceding-sibling::topic[1]/body/child::node()[(self::text() and normalize-space()) or self::*][position() = last()]
But it's for some reasons not working... Why?
<topic>
<body>Topic 3 with only a paragraph, no topic title</body>
</topic>
<topic>
<body>
<p> <!-- from here -->
<image href="" />
</p> <!-- and from here -->
</body>
</topic>
<topic>
<body>Topic 5 with only a paragraph, no topic title</body>
</topic>
I think you need to tell us in more detail what you are trying and how it exactly it fails for your; when I convert your input snippet into a well-formed input document
<root>
<topic>
<body>Topic 3 with only a paragraph, no topic title</body>
</topic>
<topic>
<body>
<p> <!-- from here -->
<image href="" />
</p> <!-- and from here -->
</body>
</topic>
<topic>
<body>Topic 5 with only a paragraph, no topic title</body>
</topic>
</root>
and run it through a stylesheet matching on a p with your posted condition in a predicate it obviously finds that single p you have i.e.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:template match="p[ancestor::topic[1]/preceding-sibling::topic[1]/body/child::node()[(self::text() and normalize-space()) or self::*][position() = last()]]">
found
</xsl:template>
</xsl:stylesheet>
outputs found as the match happens.
So explain with minimal but complete samples what your are trying, which output you expect and how it fails (i.e. which error you get or which wrong output), then we can tell perhaps what is wrong.
Sorry for the non-answer, but I couldn't stuff the code example that shows your expression does seem to work into a comment.
Related
I have a document that contains comment nodes in a variety of locations. I want to move these comments to a single new location in the document, and convert them to p elements.
I am an XSLT beginner, and with the help of W3schools and StackFlow I’ve been able to get these comments converted and in the correct location. However, the converted comments are copied, not moved, so they stay in their original locations.
For example, given the following input:
<?xml version="1.0" encoding="UTF-8"?>
<!--Comment before root element-->
<concept>
<!--Comment element is parent-->
<title>Test Topic</title>
<shortdesc>This is a shortdesc element <!-- shortdesc element is parent --></shortdesc>
<conbody>
<!-- Conbody element is parent -->
<p>This is para 1 </p>
<section>
<!--Section element is parent; comment is before title-->
<title>Section 1</title>
<!--Section element is parent; comment is after title-->
<p>This is para 1 in section1</p>
</section>
</conbody>
</concept>
I want the following output:
<?xml version="1.0" encoding="UTF-8"?>
<concept>
<title>Test Topic</title>
<shortdesc>This is a shortdesc element </shortdesc>
<conbody>
<p>This is para 1 </p>
<section>
<title>Section 1</title>
<p>This is para 1 in section1</p>
</section>
<section outputclass="authorNote">
<p>Comment before root element </p>
<p>Comment element is parent</p>
<p>shortdesc element is parent</p>
<p>Conbody element is parent</p>
<p>Section element is parent; comment is before title</p>
<p>Section element is parent; comment is after title</p>
</section>
</conbody>
This is my stylesheet:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<!-- Create new section to hold converted comments -->
<xsl:template match="conbody" >
<xsl:copy>
<xsl:apply-templates/>
<section outputclass="authorNote">
<xsl:apply-templates select="//comment()"/>
</section>
</xsl:copy>
</xsl:template>
<!-- Convert comment nodes to p elements -->
<xsl:template match="//comment()">
<p><xsl:value-of select="."/></p>
</xsl:template>
</xsl:stylesheet>
Which produces the following output:
<?xml version="1.0" encoding="UTF-8"?>
<p>Comment before root element </p>
<concept>
<p>Comment element is parent</p>
<title>Test Topic</title>
<shortdesc>This is a shortdesc element <p>shortdesc element is parent</p></shortdesc>
<conbody>
<p>Conbody element is parent</p>
<p>This is para 1 </p>
<section>
<p>Section element is parent; comment is before title</p>
<title>Section 1</title>
<p>Section element is parent; comment is after title</p>
<p>This is para 1 in section1</p>
</section>
<section outputclass="authorNote">
<p>Comment before root element </p>
<p>Comment element is parent</p>
<p>shortdesc element is parent</p>
<p>Conbody element is parent</p>
<p>Section element is parent; comment is before title</p>
<p>Section element is parent; comment is after title</p>
</section>
</conbody>
</concept>
What am I doing wrong? The articles I have found so far are all about manipulating elements, rather than comment nodes. Can someone give me some tips on how to address this problem?
Separate the two tasks with modes (don't copy the comments with the default mode, transform them with a different mode):
<!-- suppress treatment of comments through default mode -->
<xsl:template match="comment()"/>
<!-- Create new section to hold converted comments -->
<xsl:template match="conbody" >
<xsl:copy>
<xsl:apply-templates/>
<section outputclass="authorNote">
<xsl:apply-templates select="//comment()" mode="transform"/>
</section>
</xsl:copy>
</xsl:template>
<!-- Convert comment nodes to p elements -->
<xsl:template match="comment()" mode="transform">
<p><xsl:value-of select="."/></p>
</xsl:template>
Online sample at http://xsltransform.net/93dEHGn.
From the below sample input and respective output, I need a XSL transformation to skip only the fist occurrence of the <dateline> field in under <body> parent tag.
<!--Given sample Input XML: -->
<content>
<data>
<datatext>
<message name="message">
<p>Test message paragraph.
<dateline name="dateline">Message datelines</dateline>?
<annotation type="note">Test message Note.</annotation>
</p>
</message>
<head name="head">
<p>Test Head paragraph <annotation type="note">Head notes </annotation> paragraph.
<dateline name="dateline">Head dateline</dateline>
</p>
</head>
<body name="body">
<p>
Test first Body paragraph.
<annotation type="note">First Body notes.</annotation>
</p>
<p>Test Second Body paragraph.</p>
<p>
<annotation type="note">Second Body notes.</annotation>
Test third Body paragraph.
<dateline name="dateline">SECOND DATELINE</dateline>
</p>
<p>Test Fouth Body paragraph.</p>
<p>
<dateline name="dateline">THIRD DATELINE</dateline>
Test fourth Body paragraph.
<annotation type="note">Third Body notes.</annotation>
</p>
</body>
</datatext>
</data>
</content>
The expected output, the first occurrence of the <dateline> tag should be removed,
<!-- Expected Output XML -->
<content>
<data>
<datatext>
<message name="message">
<p>Test message paragraph.
<dateline name="dateline">Message datelines</dateline>?
<annotation type="note">Test message Note.</annotation>
</p>
</message>
<head name="head">
<p>Test Head paragraph <annotation type="note">Head notes </annotation> paragraph.
<dateline name="dateline">Head dateline</dateline>
</p>
</head>
<body name="body">
<p>
Test first Body paragraph.
<annotation type="note">First Body notes.</annotation>
</p>
<p>Test Second Body paragraph.</p>
<p>
<annotation type="note">Second Body notes.</annotation>
Test third Body paragraph.
</p>
<p>Test Fouth Body paragraph.</p>
<p>
<dateline name="dateline">THIRD DATELINE</dateline>
Test fourth Body paragraph.
<annotation type="note">Third Body notes.</annotation>
</p>
</body>
</datatext>
</data>
</content>
skip only the fist occurrence of the <dateline> field in under
<body> parent tag
First, body is an ancestor of dateline, not the parent.
Now, since you want to copy everything except one node, it would be best to start with the identity transform template (that copies everything) as the rule, and add an exception for the node in question:
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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="body//dateline[generate-id()=generate-id(ancestor::body/descendant::dateline[1])]"/>
</xsl:stylesheet>
Why must this be so complicated:
In order to select the first dateline descendant of body, you must use the expression:
body/descendant::dateline[1]
and not:
body//dateline[1]
This is explained in the XPath specification:
NOTE: The location path //para[1] does not mean the same as the location path /descendant::para[1]. The latter selects the first
descendant para element; the former selects all descendant para
elements that are the first para children of their parents.
However, the expression:
body/descendant::dateline[1]
is not a valid match pattern. Although patterns may use the // operator, they must not use the descendant axis: https://www.w3.org/TR/xslt/#patterns
Therefore I have chosen to match any dateline that is a descendant of body, and add a predicate that compares the unique id of the current dateline with the one that is truly the first descendant of the ancestor body. This works because the descendant axis is allowed in a predicate.
Here's one possible solution.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="bdl" select="//body//dateline"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dateline[index-of($bdl,.) = 1]"/>
</xsl:stylesheet>
At first I thought you could get by with just
<xsl:template match="//body//dateline[1]"/>
But this doesn't work since the [1] predicate is focus and context dependent, and both dateline tags in the body are first under their immediate parent. This solution first builds a sequence of all body dateline tags (in $bdl) and then deletes only the one that matches the first entry in the list.
There is probably a "better" or more idiomatic way of accomplishing this, and I hope one of the XSLT gurus will answer as well.
Given the following xml inputs:
file1:
<?xml version="1.0" encoding="UTF-8"?>
<File1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<code code="file1_code" displayName="file1_display" codeSystem="file1_cs" codeSystemName="file1_csn"/>
<title>Title of file1</title>
<component typeCode="COMP">
<structuredBody classCode="DOCBODY">
<component typeCode="COMP">
<section>
<templateId root="someRoot_file1" assigningAuthorityName="someAuhthority_file1"/>
<code code="file1-sec1_code" displayName="file1_sec1_display" codeSystem="file1_sec1_cs" codeSystemName="file1_sec1_csn"/>
<title>Tile of sec 1 from file1</title>
<text>
<content styleCode="Italics">
Text of sec 1 from file1
</content>
</text>
<entry> file 1 sec 1
</entry>
</section>
</component>
<component typeCode="COMP">
<section classCode="DOCSECT">
<code code="file1_sec2_code" codeSystem="file2_sec2_cs" displayName="file2_sec2_display" codeSystemName="file2_sec2_csn"/>
<title>Tile from sec 2 file 1</title>
<text>
<content styleCode="Italics">
Text from file1 sec 2
</content>
</text>
<entry typeCode="test"> file2 sec 2
</entry>
</section>
</component>
</structuredBody>
</component>
</File1>
file2:
<?xml version="1.0"?>
<A>
<title value="Title of file2"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>File 2 Text</p>
</div>
</text>
<section>
<code>
<coding>
<system value="sec 1 file2 sys"/>
<code value="sec 1 file 2 code"/>
<display value="sec 1 file 2 display"/>
</coding>
</code>
<title>Title of sec 1 file2</title>
<text>
<content styleCode="Italics">Section 1 Text
</content>
</text>
<entry>
<someEntry>
</someEntry>
</entry>
</section>
<section>
<code>
<coding>
<system value="sec 2 file2 sys"/>
<code value="sec 2 file 2 code"/>
<display value="sec 2 file 2 display"/>
</coding>
</code>
<title>Title of sec 2 file2</title>
<text>
<content styleCode="Italics">Section 2 file2 Text
</content>
</text>
<entry>
<someEntry> entry sec 2 file 2
</someEntry>
</entry>
</section>
</A>
and the following xslt:
<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:variable name="input" select="/" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<Bundle>
<id value="test"/>
<type value="document"/>
<entry>
<resource>
<xsl:apply-templates select="document('file2.xml')/*"/>
</resource>
</entry>
</Bundle>
</xsl:template>
<xsl:template match="text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
</xsl:template>
<xsl:template match="title">
<xsl:apply-templates select="$input/File1/title"/>
</xsl:template>
<xsl:template match="section[1]">
<xsl:apply-templates select="$input/File1/component/structuredBody/component/section"/>
</xsl:template>
<xsl:template match="section[2]"/>
<xsl:template match="File1/title">
<title>
<xsl:attribute name="value">
<xsl:value-of select="." />
</xsl:attribute>
</title>
</xsl:template>
<xsl:template match = "File1/component/structuredBody/component/section">
<section>
<xsl:apply-templates/>
</section>
</xsl:template>
</xsl:stylesheet>
And this is the output:
<?xml version="1.0" encoding="UTF-8"?>
<Bundle>
<id value="test"/>
<type value="document"/>
<entry>
<resource>
<A>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<section>
<templateId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" root="someRoot_file1" assigningAuthorityName="someAuhthority_file1"/>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1-sec1_code" displayName="file1_sec1_display" codeSystem="file1_sec1_cs" codeSystemName="file1_sec1_csn"/>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</section>
<section>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1_sec2_code" codeSystem="file2_sec2_cs" displayName="file2_sec2_display" codeSystemName="file2_sec2_csn"/>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" typeCode="test"/>
</section>
</A>
</resource>
</entry>
</Bundle>
And this is the expected output:
<?xml version="1.0" encoding="UTF-8"?>
<Bundle>
<id value="test"/>
<type value="document"/>
<entry>
<resource>
<A>
<title value="Title of file1"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
<section>
<templateId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" root="someRoot_file1" assigningAuthorityName="someAuhthority_file1"/>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1-sec1_code" displayName="file1_sec1_display" codeSystem="file1_sec1_cs" codeSystemName="file1_sec1_csn"/>
<title>Tile of sec 1 from file1</title>
<text>
<content styleCode="Italics">
Text of sec 1 from file1
</content>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</section>
<section>
<code xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" code="file1_sec2_code" codeSystem="file2_sec2_cs" displayName="file2_sec2_display" codeSystemName="file2_sec2_csn"/>
<title>Tile from sec 2 file 1</title>
<text>
<content styleCode="Italics">
Text from file1 sec 2
</content>
</text>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" typeCode="test"/>
</section>
</A>
</resource>
</entry>
</Bundle>
I have the following questions:
Why is the title in the section elements coming from the main title (i.e. File1/title) when the apply templates is within File1/component/structuredBody/component/section? I was expecting that the title of the section will be output, which is what is desired. Even more confusing is that it does indeed output the elements in the section like code, entry and so on but title and text (see q2 below) seems to be treated differently and I can't for the life of me understand why.
Same with text. Why is the text for section not being output?
Here is my presumably false understanding of the process:
We start with the <xsl:template match="/"> and create elements Bundle, id etc. and then using <xsl:apply-templates select="document('file2.xml')/*"/> we match the top element of file2 (A) and since we don't have a template matching it explicitly, the identity template is called, copies it and process its child elements, which are text, title and section. For each of these child elements, it looks for a matching template. it finds them and matches them.
For element section however, it matches only the first section element because of <xsl:template match="section[1]"> and then because of <xsl:apply-templates select="$input/File1/component/structuredBody/component/section"/> in the template, it looks for a template matching children of section in FIle1, which are code, text, title and templateId. It finds no such explicitly defined template, so calls the identity templates for them, copies and processes them till the end. At least that is my understanding of it.
Why is the title in the section elements coming from the main title
Because any time the processor is instructed to apply templates to a title, it looks for the best-matching template to apply, and finds this:
<xsl:template match="title">
<xsl:apply-templates select="$input/File1/title"/>
</xsl:template>
This changes the context to the title in File1.xml, and the best-matching template for this one is:
<xsl:template match="File1/title">
<title>
<xsl:attribute name="value">
<xsl:value-of select="." />
</xsl:attribute>
</title>
</xsl:template>
and that is the result you see.
Same with text. Why is the text for section not being output?
-- edited in response to the following clarification: --
When I say text I am talking about text elements only.
The original text element (child of section in File1.xml) is not being output because you have a specific template matching it and outputting something else instead:
<xsl:template match="text">
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the text from the stylesheet </p>
</div>
</text>
</xsl:template>
# michael.hor257k and #Michael Kay yeah, that was definitely the case, that I misunderstood how xsl:apply-templates works with regards to context . I thought because I called the xsl:apply-templates from within xsl:template match = "$input/File1/component/structuredBody/component/section"> that it will only look for match templates that match the children of section. In other words, I thought it will look for templates like “<xsl:template match=”File1/component/structuredBody/component/section/title"> but that is clearly not the case.
xsl:apply-templates simply looks for the children and then looks for a match template regardless of the context from which they were called. So, it will look for title or text template that matches and if it finds them, it will match them.
The easiest solution I could find that seems to solve the problem is to add a path to the title and text templates. In other words, instead of just <xsl:template match="text"> I should have <xsl:template match="A/text">. Same for title. This way, the xsl:apply-templates will not apply <xsl:template match="A/text"> as the title in section is not a child of A. So given that no matching explicit template is defined, the identity template will be applied and will output the title of section as desired.
I've the below XML.
<chapter num="1">
<section level="sect2">
<page>22</page>
</section>
<section level="sect3">
<page>23</page>
</section>
</chapter>
here I'm trying to get the first occurrence of <page>.
I'm using the below XSLT.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ntw="Number2Word.uri" exclude-result-prefixes="ntw">
<xsl:output method="html"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="ThisDocument" select="document('')"/>
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><![CDATA[<!DOCTYPE html>]]></xsl:text>
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="chapter">
<section class="tr_chapter">
<xsl:value-of select="//page[1]/text()"/>
<div class="chapter">
</div>
</section>
</xsl:template>
</xsl:stylesheet>
but the output that I get all the page valyes printed. I only want the first one.
Current output.
<!DOCTYPE html>
<html>
<body>
<section class="tr_chapter">2223
<div class="chapter">
</div>
</section>
</body>
</html>
the page values are printed here after <section class="tr_chapter">, i want only 22 but I'm getting 2223
here I'm using //page[1]/text(), because I'm not sure that the page comes within the section, it is random.
please let me know how I can get only the first page value.
here is the transformation http://xsltransform.net/3NzcBsR
Try:
<xsl:value-of select="(//page)[1]"/>
http://xsltransform.net/3NzcBsR/1
Note that this gets the value of the first page element in the entire document.
If you want to search the contents of the chapter context element in your template for the first page descendant then use <xsl:value-of select="descendant::page[1]"/> or <xsl:value-of select="(.//page)[1]"/>.
could you please help me transforming the following snippet?
<root>
<topic> <!-- First topic -->
<p>content1</p>
<topic> <!-- Second topic -->
<p>content2</p>
</topic>
<topic> <!-- Third topic -->
<p>content3</p>
</topic>
</topic>
</root>
I need to cut the second topic and the third topic off the first topic, wrap them in a new/empty topic and attach the new topic with its children to the root node.
<root>
<topic> <!-- First topic -->
<p>content1</p>
</topic>
<topic> <!-- New topic -->
<topic> <!-- Second topic -->
<p>content2</p>
</topic>
<topic> <!-- Third topic -->
<p>content3</p>
</topic>
</topic>
</root>
Maybe there is a very simple solution. I tried to prefix the second/suffix the third topic with a tag (looks like a very dirty solution to me), but I fail moving them.
<xsl:if test="position() = 1">
<![CDATA[
<topic>
]]>
...
</xsl:if>
<xsl:if test="position() = last()">
...
<![CDATA[
</topic>
]]>
</xsl:if>
UPDATE 1
This is a more complex and detailed example:
Source - Before Transformation
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--
The root has only a
title and multiple child
topics. but no other child
elements.
-->
<title>Root</title>
<topic id="topic_1">
<!--
Allowed child elements of 'topic'
are listed in the DITA spec.:
http://bit.ly/1ruYbdq
-->
<title>First Topic - First Level</title>
</topic>
<topic id="topic_2">
<title>Second Topic - First Level</title>
<!--
This is the main problem.
A topic must not contain
child topics AND other child
elements after the
transformation.
If a topic has child topic
AND other child elements, the
topics have to be extracted.
-->
<topic id="topic_3">
<title>Third Topic - Second Level</title>
</topic>
<topic id="topic_4">
<!--
The number of topics is not limited.
-->
<title>Fourth Topic - Second Level</title>
<topic id="topic_5">
<!--
Third level topics have to
be moved to the second
hierarchy level. No topic
may reside on the third
level after transformation.
-->
<title>Fifth Topic - Third Level</title>
</topic>
</topic>
</topic>
</root>
Result - After Transformation
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title>Root</title>
<topic id="topic_1">
<title>First Topic - First Level</title>
</topic>
<topic id="topic_2">
<title>Second Topic - First Level</title>
</topic>
<!--
The third and fourth topic have
been moved extracted from the
second topic. Both (could be any
number) have been wrapped with a
dummy 'topic' element.
-->
<topic>
<!--
The second level topics have
been wrapped with a "dummy"
topic element.
-->
<topic id="topic_3">
<title>Fourth Topic - Second Level</title>
</topic>
<topic id="topic_4">
<title>Fifth Topic - Second Level</title>
</topic>
<topic id="topic_5">
<!--
The third level topic
has been moved to the
second hierarchy level.
-->
<title>Sixth Topic - Third Level</title>
</topic>
</topic>
</root>
Maybe there is a very simple solution.
Maybe there is, but it's difficult to see what's given here and what's just an example. Would this very simple solution work for you?
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:strip-space elements="*"/>
<xsl:template match="/root">
<xsl:copy>
<topic>
<xsl:copy-of select="topic/p"/>
</topic>
<topic>
<xsl:copy-of select="topic/topic"/>
</topic>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Ok, this works for me:
<xsl:template match="//topic">
<xsl:choose>
<xsl:when test="topic[not(topic)]">
<!--
Wrap first level topics in a
single <section> element.
-->
<section>
<xsl:apply-templates/>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<!--
Wrap nested topics in a
<section> container.
-->
<xsl:when test="(count(preceding-sibling::topic) = 0) and (count(following-sibling::topic) >= 1)">
<!--
Close the section of the parent <topic> element.
-->
<xsl:text disable-output-escaping="yes"></section></xsl:text>
<xsl:text disable-output-escaping="yes"><section></xsl:text>
<section>
<xsl:apply-templates/>
</section>
</xsl:when>
<xsl:otherwise>
<section>
<xsl:apply-templates/>
</section>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>