xslt-2.0dita-ot

How to Record and Print messages in DITA-OT logs?


I have created a custom plugin in DITA-OT 3.4 named com.myorg.dita.html5, which is an extension of the base plugin org.dita.html5. The plugin is set up correctly and functioning as expected.

In the main custom XSLT file of this plugin, I process each topic by matching the root node using <xsl:template match="/">. There are other XSLTs in plugin to apply relavant customizations.

My Current challenge is; I need to find all the elements that has class=topic/xref that has <data> element as child or descendant and does not have any value in @value attribute. I need to record all such instances during the entire dita-ot process. If any error recorded then print it and terminate the dita-ot process. I need to do this in this XSL if possible.

My Original xsl:

<?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" exclude-result-prefixes="xs" version="2.0">
    <xsl:output method="html" encoding="utf-8" indent="no"/>
    <!-- root rule -->
    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>
</xsl:stylesheet>

What I'm trying:

<?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:exsl="http://exslt.org/common" exclude-result-prefixes="xs exsl" version="2.0">
    <xsl:output method="html" encoding="utf-8" indent="no"/>
    <xsl:variable name="errorLog" as="element()*"/>
    <!-- root rule -->
    <xsl:template match="/">
        <xsl:apply-templates/>
        <xsl:apply-templates select="//*[contains(@class, ' topic/xref ')]" mode="test"/>
        <xsl:if test="$errorLog">
            <!-- Print the errors -->
            <xsl:message terminate="Yes">
                <xsl:text>List of all the errors </xsl:text>
                <xsl:copy-of select="$errorLog"/>
            </xsl:message>
        </xsl:if>
    </xsl:template>
    <xsl:template match="*[contains(@class, 'topic/xref')]" mode="test">
        <xsl:if test="exists(//data) and not(//data/@value ='')">
            <!-- Append the error to the errorLog variable -->
            <xsl:copy-of select="."/>
            <xsl:sequence select="$errorLog, current()"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

How to achieve this?


Solution

  • This sounds as if an XSLT 3.0 accumulator could help. XSLT 2 and DITA-OT are usually run with Saxon, the currently supported versions of Saxon are 10, 11, 12, which are all XSLT 3.0 processors anyway and just support XSLT 2 stylesheets through backwards compatibility.

    So you could try e.g.

    <?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:exsl="http://exslt.org/common" exclude-result-prefixes="xs exsl" version="3.0">
        <xsl:output method="html" encoding="utf-8" indent="no"/>
    
        <xsl:accumulator name="errorLog" as="element()*" initial-value="()">
          <xsl:accumulator-rule match="*[contains(@class, 'topic/xref')][exists(//data) and not(//data/@value ='')]" select="$value, ."/>
        </xsl:accumulator>
    
        <!-- root rule -->
        <xsl:template match="/">
            <xsl:apply-templates/>
            <xsl:apply-templates select="//*[contains(@class, ' topic/xref ')]" mode="test"/>
            <xsl:if test="accumulator-after('errorLog')">
                <!-- Print the errors -->
                <xsl:message terminate="Yes">
                    <xsl:text>List of all the errors </xsl:text>
                    <xsl:copy-of select="accumulator-after('errorLog')"/>
                </xsl:message>
            </xsl:if>
        </xsl:template>
    
        <xsl:mode name="test" on-no-match="shallow-skip" use-accumulators="errorLog"/>
    
        <xsl:mode use-accumulators="errorLog"/>
    
    </xsl:stylesheet>
    

    Note that I have used the conditions from your original code in the accumulator rule with e.g.

    <xsl:accumulator name="errorLog" as="element()*" initial-value="()">
      <xsl:accumulator-rule match="*[contains(@class, 'topic/xref')][exists(//data) and not(//data/@value ='')]" select="$value, ."/>
    </xsl:accumulator>
    

    but I suspect you rather want

    <xsl:accumulator name="errorLog" as="element()*" initial-value="()">
      <xsl:accumulator-rule match="*[contains(@class, 'topic/xref')][exists(.//data) and not(.//data/@value ='')]" select="$value, ."/>
    </xsl:accumulator>