There is some problem in my xsl ,I do not know the reason
I want to use apply-templates to reverse the different sequences of XML without xsl:sort;
For example : the following is the input
<book title="XML">
<author first="P" />
<chapter title="A">
<section title="A.1" />
<section title="A.2">
<section title="A.2.1" />
<section title="A.2.2" />
</section>
<section title="A.3">
<section title="A.3.1" />
</section>
</chapter>
<chapter title="B">
<section title="B.1" />
<section title="B.2">
<section title="B.2.1" />
<section title="B.2.2" />
</section>
</chapter>
</book>
I want to get the output like this:this is my xsl.
<?xml version="1.0" encoding="UTF-8"?>
<book title="XML">
<author first="P"/>
<chapter title="A">
<section title="A.1">
<section title="A.3.1"/>
</section>
<section title="A.2">
<section title="A.2.2"/>
<section title="A.2.1"/>
</section>
<section title="A.1"/>
</chapter>
<chapter title="B">
<section title="B.2">
<section title="B.2.2"/>
<section title="B.2.1"/>
</section>
<section title="B.1"/>
</chapter>
</book>
Yes,the sections have been reversed but the chapters are not.
the following is my xsl ,there is some problem here ,could you help me to find it??
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent ="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="book">
<xsl:copy>
<xsl:sequence select="#title"/>
<xsl:sequence select="author"/>
<xsl:apply-templates select="chapter">
<xsl:with-param name="seq" select="section"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match ="chapter|section" as="element()">
<xsl:param name="seq" as="element(section)*"/>
<xsl:copy>
<xsl:sequence select="#title"/>
<xsl:if test="not(empty($seq))">
<xsl:apply-templates select="chapter">
<xsl:with-param name="seq" select="$seq"/>
</xsl:apply-templates>
<xsl:apply-templates select="$seq[1]"/>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:transform>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="section">
<xsl:apply-templates select="following-sibling::node()[1]"/>
<xsl:copy>
<xsl:apply-templates select="node()[1]|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<book title="XML">
<author first="P"></author>
<chapter title="A ">
<section title="A.3 ">
<section title="A.3.1"></section>
</section>
<section title="A.2">
<section title="A.2.2"></section>
<section title="A.2.1"></section>
</section>
<section title="A.1"></section>
</chapter>
<chapter title="B">
<section title="B.2">
<section title="B.2.2"></section>
<section title="B.2.1"></section>
</section>
<section title="B.1"></section>
</chapter>
</book>
How about this?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="2.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 ="chapter|section">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:for-each select="*">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:apply-templates select="." />
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:transform>
A couple of things to note:
match="*" template: This provides a default behavior for elements that we just need to copy with attributes and then process the children. This replaces your "book" and "/" templates and doesn't make assumptions about what elements are in it. This means we now can focus on providing template(s) for elements that are not covered by default behavior.
for-each: This is where the magic happens by enumerating the children which we then sort in descending order based on position before processing them with apply templates.
Related
I am trying to wrap all the text node in a <text> element, but facing challenge when encounter an inline elements (i, b, emphasis), that should be in same <text> node (in other words, it should be considered as text)... Please see input and desired output below:
(Note: I have to do this for specific inline elements only, hence kept it in param (it could be anything), for rest of the elements standard <text> rule should be applied. (Please see my xslt for details)
Input XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<para>XML Translation is a format that's used to <emphasis>exchange <i>localisation</i></emphasis>data</para>
<para>The process can now be reformulated with more detail as follows:<ul>
<li>Text extraction <note>Separation of translatable text from layout data</note></li>
<li>Pre-translation</li>
<li>Translation</li>
<li>Reverse conversion</li>
<li>Translation memory improvement</li>
</ul>above mentioned steps should <b>executed</b> sequentially</para>
</root>
OutPut should be:
<?xml version="1.0" encoding="utf-8"?>
<root>
<para>
<text xid="d0t3">XML Translation is a format that's used to <g ctype="emphasis">exchange <g ctype="i">localisation</g></g>data </text>
</para>
<para>
<text xid="d0t10">The process can now be reformulated with more detail as follows:</text>
<ul>
<li><text xid="d0t13">Text extraction <g ctype="note">Separation of translatable text from layout data</g></text></li>
<li><text xid="d0t17">Pre-translation</text></li>
<li><text xid="d0t19">Translation</text></li>
<li><text xid="d0t21">Reverse conversion</text></li>
<li><text xid="d0t23">Translation memory improvement</text></li>
</ul>
<text xid="d0t24">above mentioned steps should <g ctype="b">executed</g> sequentially</text>
</para>
</root>
I am trying something like this, but not able to achieve correct result:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:param name="inlineElement" select="('emphasis', 'i', 'note', 'b')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<text>
<xsl:attribute name="xid">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
<xsl:value-of select="."/>
<xsl:if test="following-sibling::node()[local-name()=$inlineElement]">
<g>
<xsl:apply-templates select="following-sibling::node()[local-name()=$inlineElement]/text()"/>
</g>
</xsl:if>
</text>
</xsl:template>
</xsl:stylesheet>
I would use for-each-group group-adjacent:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:param name="inlineElement" select="('emphasis', 'i', 'note', 'b')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(local-name() = $inlineElement)]">
<xsl:copy>
<xsl:for-each-group select="node()" group-adjacent="boolean(self::text() | self::*[local-name() = $inlineElement])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<text xid="{generate-id(current-group()[self::text()][1])}">
<xsl:apply-templates select="current-group()"/>
</text>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name() = $inlineElement]">
<g ctype="{local-name()}">
<xsl:apply-templates/>
</g>
</xsl:template>
</xsl:stylesheet>
That way, with Saxon 9.5, I get
<?xml version="1.0" encoding="UTF-8"?>
<root>
<para>
<text xid="d1t3">XML Translation is a format that's used to <g ctype="emphasis">exchange <g ctype="i">localisation</g>
</g>data</text>
</para>
<para>
<text xid="d1t10">The process can now be reformulated with more detail as follows:</text>
<ul>
<li>
<text xid="d1t13">Text extraction <g ctype="note">Separation of translatable text from layout data</g>
</text>
</li>
<li>
<text xid="d1t17">Pre-translation</text>
</li>
<li>
<text xid="d1t19">Translation</text>
</li>
<li>
<text xid="d1t21">Reverse conversion</text>
</li>
<li>
<text xid="d1t23">Translation memory improvement</text>
</li>
</ul>
<text xid="d1t24">above mentioned steps should <g ctype="b">executed</g> sequentially</text>
</para>
</root>
May be my XSL approach is wrong? please correct me the way to handle this situation
I want to grab XPATHs and Attrs from a mapping file, then use XPATH to match, and apply attributes to XML.
Here is my 3 inputs files:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map xpath="//title" class="title" others="moreToCome" />
<map xpath="//subtitle" class="subtitle" others="moreToCome" />
<map xpath="//p" class="p" others="moreToCome" />
</mappings>
Source.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title>title text</title>
<subtitle>subtitle text</subtitle>
<p>subtitle text</p>
</root>
StyleMapping.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="fMappings" select="document('mappings.xml')" />
<xsl:variable name="xpath"><xsl:text>justToMakeItGlobal</xsl:text></xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
<!-- loop thru map in mappings.xml -->
<xsl:for-each select="$fMappings//mappings/map">
<xsl:call-template name="dyn">
<xsl:with-param name="xpath" select="#xpath" />
<xsl:with-param name="class" select="#class" />
<xsl:with-param name="others" select="#others" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="$xpath" mode="dyn">
<xsl:param name="xpath"/>
<xsl:param name="class"/>
<xsl:param name="others"/>
<xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
<xsl:attribute name="others"><xsl:value-of select="$others" /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is what is planning to do, but i'm not getting the correct way of doing it in XSLT:
1. Read mappings.xml file
2. Loop thru each map tag
3. grab xpath and attr's
4. apply template match/select with above xpath
5. add attr's to above selected nodes
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title class="title" others="moreToCome">title text</title>
<subtitle class="subtitle" others="moreToCome">subtitle text</subtitle>
<p class="p" others="moreToCome">subtitle text</p>
</root>
I don't think you can have a template with a calculated match pattern. Let me suggest a different approach:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map elem="title" class="Title" others="moreToCome1" />
<map elem="subtitle" class="Subtitle" others="moreToCome2" />
<map elem="p" class="P" others="moreToCome3" />
</mappings>
stylesheet
<?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" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="mappings" select="document('mappings.xml')/mappings" />
<xsl:template match="*">
<xsl:variable name="elem" select="name()" />
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$elem = $mappings/map/#elem">
<xsl:attribute name="class">
<xsl:value-of select="$mappings/map[#elem=$elem]/#class"/>
</xsl:attribute>
<xsl:attribute name="others">
<xsl:value-of select="$mappings/map[#elem=$elem]/#others"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Testing with the following source XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title original="yes">title text</title>
<subtitle>subtitle text</subtitle>
<p>para text</p>
<nomatch attr="test">another text</nomatch>
</root>
results in:
<?xml version="1.0" encoding="utf-8"?>
<root>
<title original="yes" class="Title" others="moreToCome1">title text</title>
<subtitle class="Subtitle" others="moreToCome2">subtitle text</subtitle>
<p class="P" others="moreToCome3">para text</p>
<nomatch attr="test">another text</nomatch>
</root>
Need to transform following XML snippet into DITA using XSLT. My requirements are:
1. All the tags comes before "orderedlist" should be wrapped under "context" node.
2. All the tags comes after "orderedlist" should be in "result".
3. All the "include" tags should be wrapped under their preceding sibling nodes.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<procedure>
<para>This is first para</para>
<para>This is second para</para>
<include>This is include</include>
<orderedlist>
<listitem>this is list item</listitem>
<include>This is include</include>
<listitem>this is list item <include>this is include</include></listitem>
</orderedlist>
<observation>this is observation</observation>
<para>this is result para <include>this is include</include></para>
<include>This is include</include>
</procedure>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<task>
<context>
<p>This is first para</p>
<p>This is second para <included type='tag'>This is include</included>
</p>
</context>
<ol>
<li>this is list item <included type='tag'>This is include</included>
</li>
<li>this is list item <included type='tag'>this is include</included></li>
</ol>
<result>
<observation>this is observation</observation>
<p>this is result para <included type='tag'>this is include</included><included type='tag'>this is include</included>
</p>
</result>
</task>
My XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="procedure">
<task>
<context>
<xsl:apply-templates select="*[parent::procedure][following-sibling::orderedlist]"/>
</context>
<xsl:apply-templates select="orderedlist"/>
<result>
<xsl:apply-templates select="*[parent::procedure][preceding-sibling::orderedlist]"/>
</result>
</task>
</xsl:template>
<xsl:template match="para">
<p>
<xsl:apply-templates/>
<include>
<xsl:apply-templates select="following-sibling::include"/>
</include>
</p>
</xsl:template>
<!-- rest of the template goes here -->
<xsl:template match="listitem">
<li>
<xsl:apply-templates/>
<include>
<xsl:apply-templates select="following-sibling::include"/>
</include>
</li>
</xsl:template>
</xsl:stylesheet>
Any pointer will be a great help. Thanks.
The first thing to note is you can simplify the following expression:
<xsl:apply-templates select="*[parent::procedure][following-sibling::orderedlist]"/>
You don't need the [parent::procedure] here, because you are already positioned on a procedure element, so so you know if you select any child element, it will have that as a parent!
<xsl:apply-templates select="*[following-sibling::orderedlist]"/>
However, you might need to add an clause to ensure you don't output the include elements at this point, as you will need special code to handle them being included later
<xsl:template match="include" />
To handle the include elements, it might be worth defining a key, so you can group them by the first most proceding non-include element, like so
<xsl:key name="include" match="include" use="generate-id(preceding-sibling::*[not(self::include)][1])"/>
Then, when matching an element such as para or listitem, you can then get the include elements to include, just like this:
<xsl:copy-of select="key('include', generate-id())"/>
Note I am not sure how you want to handle multipe include elements for a single element, but in my example, it will output them separately as opposing to merging them:
Here is the full XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="include" match="include" use="generate-id(preceding-sibling::*[not(self::include)][1])"/>
<xsl:template match="procedure">
<task>
<context>
<xsl:apply-templates select="*[following-sibling::orderedlist]"/>
</context>
<xsl:apply-templates select="orderedlist"/>
<result>
<xsl:apply-templates select="*[preceding-sibling::orderedlist]"/>
</result>
</task>
</xsl:template>
<xsl:template match="orderedlist">
<ol>
<xsl:apply-templates />
</ol>
</xsl:template>
<xsl:template match="para">
<p>
<xsl:apply-templates/>
<xsl:copy-of select="key('include', generate-id())" />
</p>
</xsl:template>
<xsl:template match="listitem">
<li>
<xsl:apply-templates/>
<xsl:copy-of select="key('include', generate-id())" />
</li>
</xsl:template>
<xsl:template match="include" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<task>
<context>
<p>This is first para</p>
<p>This is second para<include>This is include</include></p>
</context>
<ol>
<li>this is list item<include>This is include</include></li>
<li>this is list item</li>
</ol>
<result>
<observation>this is observation</observation>
<p>this is result para<include>This is include</include></p>
</result>
</task>
Give this a try:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()" name="Copy">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
<xsl:call-template name="Include" />
</xsl:copy>
</xsl:template>
<xsl:template match="procedure">
<task>
<context>
<xsl:apply-templates select="*[following-sibling::orderedlist]"/>
</context>
<xsl:apply-templates select="orderedlist"/>
<result>
<xsl:apply-templates select="*[preceding-sibling::orderedlist]"/>
</result>
</task>
</xsl:template>
<xsl:template match="para">
<p>
<xsl:apply-templates/>
<xsl:call-template name="Include" />
</p>
</xsl:template>
<!-- rest of the template goes here -->
<xsl:template match="listitem">
<li>
<xsl:apply-templates/>
<xsl:call-template name="Include" />
</li>
</xsl:template>
<xsl:template name="Include">
<xsl:apply-templates
select="following-sibling::include[
generate-id(preceding-sibling::*[not(self::include)][1]) =
generate-id(current())]"
mode="performIncludes"/>
</xsl:template>
<xsl:template match="include" />
<xsl:template match="include" mode="performIncludes">
<xsl:call-template name="Copy" />
</xsl:template>
</xsl:stylesheet>
Output when run on your sample input:
<task>
<context>
<p>This is first para</p>
<p>This is second para<include>This is include</include></p>
</context>
<orderedlist>
<li>this is list item<include>This is include</include></li>
<li>this is list item</li>
</orderedlist>
<result>
<observation>this is observation</observation>
<p>this is result para<include>This is include</include></p>
</result>
</task>
XML structure:
<units>
<unit>
<lesson>
<name>Sample 1</name>
<sections>
<section type="IN">
<title> Sample Title 1 </title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 2</name>
<sections>
<section type="OF">
<title> Sample Title 2 </title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 3</name>
<sections>
<section type="IN">
<title> Sample Title 3</title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 4</name>
<sections>
<section type="AS">
<title> Sample Title 4</title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 5</name>
<sections>
<section type="IN">
<title> Sample Title 5</title>
</section>
</sections>
</lesson>
</unit>
</units>
My requirement is to get the values of title element and display as follows (Grouping similar data and display)
IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 5
I have used following-sibling option to get the expected output. Since the XML structure is huge(I have pasted only the snippet), I cannot hard-code the path using ../../ and all in XSLT. Please help me in getting the expected output.
It's better to solve this using grouping than either of the sibling axes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="bySectionType" match="section" use="#type" />
<xsl:template match="/">
<xsl:apply-templates select="units/unit/lesson/sections/section" />
</xsl:template>
<xsl:template match="section" />
<xsl:template
match="section[generate-id()=
generate-id(key('bySectionType', #type)[1])]">
<xsl:value-of select="concat(#type, ':
')" />
<xsl:apply-templates select="key('bySectionType', #type)" mode="out" />
</xsl:template>
<xsl:template match="section" mode="out">
<xsl:value-of select="concat(normalize-space(title), '
')" />
</xsl:template>
</xsl:stylesheet>
Output:
IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 4
For completeness, the following stylesheet achieves the same result using the preceding and following axes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:apply-templates select="units/unit/lesson/sections/section" />
</xsl:template>
<xsl:template match="section" />
<xsl:template
match="section[not(preceding::section[#type=current()/#type])]">
<xsl:value-of select="concat(#type, ':
')" />
<xsl:apply-templates select=".|following::section[#type=current()/#type]"
mode="out" />
</xsl:template>
<xsl:template match="section" mode="out">
<xsl:value-of select="concat(normalize-space(title), '
')" />
</xsl:template>
</xsl:stylesheet>
This is a far less efficient solution. The normal way to solve this is with the Muenchian Method for grouping, as shown above.
Here is the solution in XSLT 2.0:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each-group select="//section" group-by="#type">
<xsl:value-of select="#type, ':
'" separator=""/>
<xsl:value-of select="current-group()/title" separator="
" />
<xsl:value-of select="'
'"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I have an XML organized like below-
<section name="Parent 1 Text here" ID="1" >
<section name="Child 1 Text here" ID="11">
</section>
<section name="Child 2 Text here" ID="12">
<section name="GrandChild Text here" ID="121" >
</section>
</section>
</section>
<section name="Parent 2 Text here" ID="2" >
<section name="Child 1 Text here" ID="22">
</section>
<section name="Child 2 Text here" ID="23">
<section name="GrandChild Text here" ID="232" >
</section>
</section>
</section>
I have to produce the below output XML -
<section name="Parent 1 Text here" ID="1" >
<section name="Child 2 Text here" ID="12">
<section name="GrandChild Text here" ID="121" >
</section>
</section>
</section>
<section name="Parent 2 Text here" ID="2" >
<section name="Child 2 Text here" ID="23">
</section>
</section>
I have to achive above using XSLT 1.0 transformation. I was planning to pass a comma separated string as a parameter with value= "1,12,121,2,23"
My question- How to loop the comma separated parameter in XSLT 1.0 ?
Is there a simpler way to achieve the above. Please remember I have to do this in XSLT 1.0
Your help is appreciated.
Recursion is not necessarily the solution. Basically your comma-seperated string can be applied as a filter:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="filter">1,12,121,2,23</xsl:param>
<xsl:template match="section">
<xsl:if test="contains(concat(',', $filter, ','), concat(',', #ID, ','))">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- copy the rest -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here is an alternative to Jan Willem B's approach. I don't claim that it is either better or worse. I present it for two reasons:
You may want to see what a recursive approach would look like
It behaves differently for some input data (although it behaves the same for your example data). Specifically, if your filter includes two nodes that are both descendants of the same node, this stylesheet will output two nested data structures, each with one leaf node, while Jan Willem B's answer outputs one nested structure with two leaf nodes. Don't know which you want.
For my stylesheet, you would pass a filter listing only the leaf nodes that you want. It will find the ancestors. I have assumed for this that your root node is called <doc>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:param name="descendant-ids" select="'121,23'"/>
<xsl:template match="/">
<doc>
<xsl:call-template name="recurse-ids">
<xsl:with-param name="ids" select="concat($descendant-ids,',')"/>
</xsl:call-template>
</doc>
</xsl:template>
<xsl:template name="recurse-ids">
<xsl:param name="ids"/>
<xsl:variable name="id" select="substring-before($ids,',')"/>
<xsl:variable name="remaining-ids" select="substring-after($ids,',')"/>
<xsl:apply-templates select="/doc/section">
<xsl:with-param name="id" select="$id"/>
</xsl:apply-templates>
<xsl:if test="$remaining-ids">
<xsl:call-template name="recurse-ids">
<xsl:with-param name="ids" select="$remaining-ids"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="section">
<xsl:param name="id"/>
<xsl:if test="starts-with($id,#ID)">
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="id" select="$id"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>