-2

Looking to transform an XML file to a valid DTD topic. To do this I need to insert a body element as shown.

<topic>
<title>My Title</title>
==><body>
<para>Intro text goes here.</para>
  <section/>
  <section/>
==></body>
</topic>
Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
mikey
  • 115
  • 8
  • This is not a simple operation, such as grabbing a single element and wrapping it in a body element. I need to match a group of different elements and wrap them. – mikey May 10 '23 at 15:54

1 Answers1

1

If you need to add a body element as a child of topic, add a template that matches topic and add the element there.

The only slightly tricky part is that you want the title outside of body, so you'll have to separate that from the rest of the children.

Here's an XSLT 3.0 example. (I also added an id attribute to topic and changed para to p so it would be valid.)

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" doctype-public="-//OASIS//DTD DITA Topic//EN" doctype-system="topic.dtd"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:mode on-no-match="shallow-copy"/>
    
    <xsl:template match="topic">
        <topic id="{generate-id()}">
            <xsl:apply-templates select="@*|title"/>
            <body>
                <xsl:apply-templates select="node() except title"/>
            </body>
        </topic>
    </xsl:template>
    
    <xsl:template match="para">
        <p>
            <xsl:apply-templates select="@*|node()"/>
        </p>
    </xsl:template>
    
</xsl:stylesheet>

If you can't use XSLT 3.0, here's a 1.0 version (without xsl:mode and except)...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" doctype-public="-//OASIS//DTD DITA Topic//EN" doctype-system="topic.dtd"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="topic">
        <topic id="{generate-id()}">
            <xsl:apply-templates select="@*|title"/>
            <body>
                <xsl:apply-templates select="node()[not(self::title)]"/>
            </body>
        </topic>
    </xsl:template>
    
    <xsl:template match="para">
        <p>
            <xsl:apply-templates select="@*|node()"/>
        </p>
    </xsl:template>
    
</xsl:stylesheet>

Fiddle: http://xsltfiddle.liberty-development.net/3Nzb5iZ

Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
  • Thanks for the reply Daniel. That looks easier than I thought. I just have to make sure I do not process the title element. – mikey May 10 '23 at 16:39
  • I am using xslt 1.0. – mikey May 10 '23 at 16:39
  • Could you explain the match="topic" step, please? @*|title means all attributes or title, doesn't it? node()[not(self::title)] is something I cannot understand. – mikey May 10 '23 at 16:48
  • 1
    @mikey - `|` operator in xpath is union ([see here](https://www.w3.org/TR/xpath-10/#node-sets)); not "or". So really you can read that as all attributes and title child elements. The `node()[not(self::title)]` means any node() (element, text, comment, or processing-instruction that isn't a `title`. The `[ ]` after node() is a [predicate](https://www.w3.org/TR/xpath-10/#NT-Predicate). The `self::` is an [axis](https://www.w3.org/TR/xpath-10/#axes). This is all standard xpath so it might help to study up on xpath before XSLT. – Daniel Haley May 10 '23 at 17:33