javascriptxsltwkhtmltopdf

How can I pass an XSLT variable to a JavaScript function in an XSLT template?


I am working with XSLT and need to pass an XSLT variable to a JavaScript function to dynamically update the content on the page. Specifically, I have a variable in my XSLT template that I want to use in a JavaScript function to modify an HTML element.

Here is my XSLT template:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:outline="http://wkhtmltopdf.org/outline"
                xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
                doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
                indent="yes" />    

    <xsl:template match="outline:outline">        
        <html>
            <head>
                <title>TOC</title>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
            </head>
             <body>
                <!-- Note: I want to dynamically update the following h1 by JS -->
                <h1 id="toc">Table of Contents</h1>
                <ul id="top">
                    <xsl:apply-templates select="outline:item/outline:item"/>
                </ul>
                <script type="text/javascript">
                    const languageMap = {
                        'ENG': 'Table of Contents',
                        'JPN': '目次',
                        'CHS': '目录',
                        'CHT': '目錄'
                    };

                    <!-- Note: the following function gets the languageTitle from XSLT and replaces h1 with matched value -->
                    function updateTOCHeader(languageTitle) {                        
                        const tocHeader = document.getElementById('toc');
                        tocHeader.style.color = "green";
                       
                        if(languageMap[languageTitle]) {
                            tocHeader.innerText = languageMap[languageTitle];
                        }
                    }
                </script>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="outline:item">
        <xsl:choose>
            <!-- Note: the following if test checks if the ancestor item has a title starting with 'language_title:' -->
            <xsl:when test="ancestor::outline:item[starts-with(@title, 'language_title:')]">
                <xsl:variable name="language_title" select="normalize-space(substring-after(ancestor::outline:item[@title and starts-with(@title, 'language_title:')]/@title, 'language_title:'))"/>
                <script type="text/javascript">
                    <!-- Note: call the following function to update the TOC header based on language title -->
                    updateTOCHeader("<xsl:value-of select="$language_title"/>");
                </script>
            </xsl:when>
           
            <xsl:otherwise>
                <li>
                    <xsl:if test="@title != ''">
                        <div class="{if (outline:item) then 'category-item' else ''}">
                            <span class="toc-link">
                                <a>
                                    <xsl:if test="@link">
                                        <xsl:attribute name="href"><xsl:value-of select="@link"/></xsl:attribute>
                                    </xsl:if>
                                    <xsl:value-of select="@title"/>
                                </a>
                            </span>
                            <span class="page-no">
                                <xsl:value-of select="@page"/>
                            </span>
                        </div>
                    </xsl:if>
                    <ul>
                        <xsl:apply-templates select="outline:item"/>
                    </ul>
                </li>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

Here is sample XML:

<?xml version="1.0" encoding="UTF-8"?>
<outline xmlns="http://wkhtmltopdf.org/outline">
   <item title="" page="0"/>
   <!-- Note: I want to pass the substring of the item title (e.g. 'JPN') to JS -->
   <item title="language_title:JPN" page="1">
      <item title="Table of Contents" page="2"/>
   </item>
   <item title="document" page="2">
      <item title="introduction" page="1">
         <item title="Chapter1" page="2">
            <item title="Section1" page="3"/>            
         </item>
         <item title="Chapter2" page="4">
            <item title="Section2" page="5"/>            
         </item>
      </item>
   </item>
   <item title="" page="20"/>
</outline>


Solution

  • I think your current code tries to call the function before it is defined, change the code to e.g.

                <script type="text/javascript">
                    <!-- Note: call the following function to update the TOC header based on language title -->
                    document.addEventListener('DOMContentLoaded', function(evt) { updateTOCHeader("<xsl:value-of select="$language_title"/>"); });
                </script>
    

    that way the function is defined first and is called later once a DOM has been loaded.

    A different approach would be to ensure the block declaring the map and the function is included before there is an attempt to call the function e.g. by putting the

                 <script type="text/javascript">
                    const languageMap = {
                        'ENG': 'Table of Contents',
                        'JPN': '目次',
                        'CHS': '目录',
                        'CHT': '目錄'
                    };
    
                    <!-- Note: the following function gets the languageTitle from XSLT and replaces h1 with matched value -->
                    function updateTOCHeader(languageTitle) {                        
                        const tocHeader = document.getElementById('toc');
                        tocHeader.style.color = "green";
                       
                        if(languageMap[languageTitle]) {
                            tocHeader.innerText = languageMap[languageTitle];
                        }
                    }
                </script>
    

    into the head section of the HTML you want to create.