Pass DITA Map Directory to Custom XSLT Refactoring Operation

Are you missing a feature? Request its implementation here.
dreifsnider
Posts: 171
Joined: Thu Aug 30, 2018 10:06 pm

Pass DITA Map Directory to Custom XSLT Refactoring Operation

Post by dreifsnider »

Hi Oxygen Support,

My team wants a custom refactoring operation that converts "empty" or "stub" topics (topics whose body is empty) into topicheads.

To accomplish this, I wrote the following XSLT:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:dita-ot="http://dita-ot.sourceforge.net/ns/201007/dita-ot"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:fn="http://www.playstation.com/functions"
    xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/"
    exclude-result-prefixes="xs dita-ot xsi fn ditaarch"
    version="3.0">
    
    <xsl:output doctype-public="-//OASIS//DTD DITA Map//EN" doctype-system="map.dtd" 
        indent="yes" method="xml"/>
    
    <xsl:variable name="ditamap-path">
        <xsl:value-of select="document-uri()"/>
    </xsl:variable>
    
    <xsl:variable name="resolved-ditamap-directory">
        <xsl:value-of select="replace($ditamap-path, '\/[\w\.]*$', '', ';j')"/>
    </xsl:variable>
    
    <xsl:template match="*[contains(@class, ' map/topicref ')]">
        <xsl:choose>
            <xsl:when test="document(concat($resolved-ditamap-directory, '/', @href))//body//*">
                <xsl:element name="topicref">
                    <xsl:apply-templates select="@*"/>
                    <xsl:if test="descendant::*[contains(@class, ' map/topicref ')]">
                        <xsl:for-each select="descendant::*[contains(@class, ' map/topicref ')]">
                            <xsl:call-template name="process-topicref"/>
                        </xsl:for-each>
                    </xsl:if>
                </xsl:element>
            </xsl:when>
            <xsl:otherwise>
                <xsl:element name="topichead">
                    <xsl:attribute name="navtitle">
                        <xsl:value-of select="document(concat($resolved-ditamap-directory, '/', @href))/topic/title"/>
                    </xsl:attribute>
                    <xsl:if test="descendant::*[contains(@class, ' map/topicref ')]">
                        <xsl:for-each select="descendant::*[contains(@class, ' map/topicref ')]">
                            <xsl:call-template name="process-topicref"/>
                        </xsl:for-each>
                    </xsl:if>
                </xsl:element>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    <xsl:template name="process-topicref">
        <xsl:choose>
            <xsl:when test="document(concat($resolved-ditamap-directory, '/', @href))//body//*">
                <xsl:element name="topicref">
                    <xsl:apply-templates select="@*"/>
                </xsl:element>
            </xsl:when>
            <xsl:otherwise>
                <xsl:element name="topichead">
                    <xsl:attribute name="navtitle">
                        <xsl:value-of select="document(concat($resolved-ditamap-directory, '/', @href))/topic/title"/>
                    </xsl:attribute>
                </xsl:element>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    <xsl:template match="@class | @domains | @xtrf | @xtrc | @ditaarch:DITAArchVersion"
        priority="10"/>
    
    <xsl:template match="processing-instruction('workdir') |
        processing-instruction('workdir-uri') |
        processing-instruction('path2project') |
        processing-instruction('path2project-uri') |
        processing-instruction('ditaot') |
        processing-instruction('doctype-public') |
        processing-instruction('doctype-system') |
        @dita-ot:* |
        @mapclass"
        priority="10"/>
    
    <xsl:template match="*[number(@ditaarch:DITAArchVersion) &lt; 1.3]/@cascade"/>

    <xsl:template match="*[@class]" priority="-5">
        <xsl:element name="{tokenize(tokenize(normalize-space(@class), '\s+')[last()], '/')[last()]}"
            namespace="{namespace-uri()}">
            <xsl:apply-templates select="node() | @*"/>
        </xsl:element>
    </xsl:template>
    
    <xsl:template match="*" priority="-10">
        <xsl:element name="{name()}" namespace="{namespace-uri()}">
            <xsl:apply-templates select="node() | @*"/>
        </xsl:element>
    </xsl:template>
    
    <xsl:template match="node() | @*" priority="-15">
        <xsl:copy copy-namespaces="no">
            <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
The idea being the XSLT goes into each topicref and checks whether or not the topic's body has child elements. If not, it converts the topicref into a topichead and uses the topic's title as the topichead's navtitle. Otherwise, it preserves the topicref as-is.

This works whenever I use Oxygen XML Editor's debugging Run operation; however, it doesn't seem to work when I run it as a refactoring operation. I'm suspecting it might have something to do with how the current DITA Map's file path is being captured.

I also attempted to define a parameter in the refactoring description file that would be used to specify the current file directory via the ${cfd} editor variable, and then pass this along to the XSLT, but this didn't work either. I believe it was because Oxygen wasn't expanding the ${cfd} variable and seemed to pass this value in as plain text.

Is there a way that I can supply the current DITA Map file directory to my refactoring XSLT so that the XSLT can walk through every referenced topic?

Thanks!
Radu
Posts: 9434
Joined: Fri Jul 09, 2004 5:18 pm

Re: Pass DITA Map Directory to Custom XSLT Refactoring Operation

Post by Radu »

Hi,

An Oxygen XML refactoring action can indeed apply an XSLT stylesheet over the target document/documents:
https://www.oxygenxml.com/doc/versions/ ... ample.html

The XSLT refactoring stylesheet is applied over the XML document without expanding any default attributes (like @class) and by perfectly preserving the DOCTYPE declaration of the XML document over which it is applied.

So a couple of remarks about your using current stylesheet with an XML refactoring operation:

(1)
This is not necessary as the XML refactoring will make sure the DOCTYPE of the XML document is fully preserved:

Code: Select all

<xsl:output doctype-public="-//OASIS//DTD DITA Map//EN" doctype-system="map.dtd" 
        indent="yes" method="xml"/>
(2)
This will not match anything as default attribute values like @class are not expanded when the XML refactoring is applied:

Code: Select all

 <xsl:template match="*[contains(@class, ' map/topicref ')]">
(3)
You should try using directly document(@href), it automatically tries to resolve the @href to the base URI:
https://www.saxonica.com/html/documenta ... ument.html

Code: Select all

<xsl:when test="document(concat($resolved-ditamap-directory, '/', @href))//body//*">
Also the use of "body" in the xpath means that you might not match other topic types like <concept> or <task> which have other names for the <body> element (for example concept uses <conbody>).

Anyway, here's a suggestion for re-writing the stylesheet (but I have not tried if it works):

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:dita-ot="http://dita-ot.sourceforge.net/ns/201007/dita-ot"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:fn="http://www.playstation.com/functions"
    xmlns:ditaarch="http://dita.oasis-open.org/architecture/2005/"
    exclude-result-prefixes="xs dita-ot xsi fn ditaarch"
    version="3.0">
    
    <xsl:template match="node() | @*">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="*[@href]">
        <xsl:choose>
            <xsl:when test="document(@href)//*//*">
                <xsl:copy>
                    <xsl:apply-templates select="@*"/>
                    <xsl:apply-templates select="node()"/>
                </xsl:copy>
            </xsl:when>
            <xsl:otherwise>
                <xsl:element name="topichead">
                    <xsl:attribute name="navtitle">
                        <xsl:value-of select="document(@href)/*/title"/>
                    </xsl:attribute>
                    <xsl:apply-templates select="node()"/>
                </xsl:element>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
About the decision to replace empty referenced topics with <topichead> not sure if I fully concur with it, maybe you could go the other way around and add some useful content in those now empty topics, for example a small overview. Because those empty topics can be referenced as links but once you switch to topicheads, you can no longer link to a topic head in the html output.
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
Post Reply