Search code examples
javascriptxmlxslt

XSLTProcessor not running


Have been knocking my head trying to figure why the following XSLTProcessor call is not working.

The XSL-T

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default">
    <xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
    <xsl:strip-space elements="*"/>
    <xsl:param name="include-header" select="'false'"/>
    <xsl:template match="/">
       <!--xsl:element name="row"-->
       <xsl:text>{</xsl:text>
        <xsl:apply-templates select="*"/>
        <!--/xsl:element-->
       <xsl:text>}</xsl:text>
    </xsl:template>
    <xsl:template match="*[count(descendant::*)=0]">
        <xsl:param name="parent"/>
        <xsl:variable name="quote" select="'&quot;'"/>
        <xsl:variable name="thisName" select="name()"/>
        <xsl:variable name="precedingSibling" select="count(preceding-sibling::*[name()=$thisName])+1"/>
        <xsl:variable name="parentChild" select="concat($parent, '.',$thisName, $precedingSibling)"/>
        <xsl:value-of select="concat($quote,$parentChild, $quote, ': ', $quote, ., $quote, ',')"/>
        <xsl:text>
</xsl:text>
    </xsl:template>
    <xsl:template match="*">
        <xsl:call-template name="recurse-descendents"/>
    </xsl:template>
    <xsl:template match="*[count(descendant::*)>0]">
        <xsl:call-template name="recurse-descendents"/>
    </xsl:template>
    <xsl:template name="recurse-descendents">
        <xsl:variable name="thisName" select="name()"/>
        <xsl:apply-templates select="*[count(descendant::*)=0]">
            <xsl:with-param name="parent" select="concat($thisName, count(preceding-sibling::*[name()=$thisName])+1)"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="*[count(descendant::*)>0]"/>
    </xsl:template>
</xsl:stylesheet>

Sample XML

<?xml version="1.0" encoding="UTF-8"?>
<foods>
    <meats>
        <meat>Beef</meat>
        <meat>Chicken</meat>
        <meat>Lamb</meat>
    </meats>
    <fruits>
        <fruit>Orange</fruit>
        <fruit>Apple</fruit>
        <fruit>Banana</fruit>
        <fruit>Avacado</fruit>
    </fruits>
    <vegetables>
        <vegetable>Carrot</vegetable>
        <vegetable>Cellery</vegetable>
        <vegetable>Potato</vegetable>
    </vegetables>
</foods>

Output from XML Spy (as expected)

{"meats1.meat1": "Beef",
"meats1.meat2": "Chicken",
"meats1.meat3": "Lamb",
"fruits1.fruit1": "Orange",
"fruits1.fruit2": "Apple",
"fruits1.fruit3": "Banana",
"fruits1.fruit4": "Avacado",
"vegetables1.vegetable1": "Carrot",
"vegetables1.vegetable2": "Cellery",
"vegetables1.vegetable3": "Potato",
}

Javascript code using XSLTProcessor (not working)

async function GenerateTestCaseResponse(xml, testCase) {
    const domParser = new DOMParser();
    const xsltProcessor = new XSLTProcessor();

    const xslResponse = await fetch("transformer/generate-response-json.xslt");
    const xslText = await xslResponse.text();
    const xslStylesheet = domParser.parseFromString(xslText, "application/xml");
    xsltProcessor.importStylesheet(xslStylesheet);
    var responseDomParser = new DOMParser();
    var responseDocument = responseDomParser.parseFromString(xml, "text/xml");
    var result =  xsltProcessor.transformToDocument(responseDocument);
    console.log(result.body);
    return result;
}

The call to xsltProcessor.transformToDocument returns empty with no error or exception. At a loss how to resolve, and any thoughts gratefully received.


Solution

  • As already pointed out in a comment, there is no body in arbitrary XML documents, you seem to want to use output method text and the text content anyway so use a fragment result and access its textContent property e.g.

    const xsltSource = `<?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="default">
        <xsl:output method="text" version="1.0" encoding="UTF-8" indent="no"/>
        <xsl:strip-space elements="*"/>
        <xsl:param name="include-header" select="'false'"/>
        <xsl:template match="/">
           <!--xsl:element name="row"-->
           <xsl:text>{</xsl:text>
            <xsl:apply-templates select="*"/>
            <!--/xsl:element-->
           <xsl:text>}</xsl:text>
        </xsl:template>
        <xsl:template match="*[count(descendant::*)=0]">
            <xsl:param name="parent"/>
            <xsl:variable name="quote" select="'&quot;'"/>
            <xsl:variable name="thisName" select="name()"/>
            <xsl:variable name="precedingSibling" select="count(preceding-sibling::*[name()=$thisName])+1"/>
            <xsl:variable name="parentChild" select="concat($parent, '.',$thisName, $precedingSibling)"/>
            <xsl:value-of select="concat($quote,$parentChild, $quote, ': ', $quote, ., $quote, ',')"/>
            <xsl:text>
    </xsl:text>
        </xsl:template>
        <xsl:template match="*">
            <xsl:call-template name="recurse-descendents"/>
        </xsl:template>
        <xsl:template match="*[count(descendant::*)>0]">
            <xsl:call-template name="recurse-descendents"/>
        </xsl:template>
        <xsl:template name="recurse-descendents">
            <xsl:variable name="thisName" select="name()"/>
            <xsl:apply-templates select="*[count(descendant::*)=0]">
                <xsl:with-param name="parent" select="concat($thisName, count(preceding-sibling::*[name()=$thisName])+1)"/>
            </xsl:apply-templates>
            <xsl:apply-templates select="*[count(descendant::*)>0]"/>
        </xsl:template>
    </xsl:stylesheet>`;
    
    const xmlSource = `<?xml version="1.0" encoding="UTF-8"?>
    <foods>
        <meats>
            <meat>Beef</meat>
            <meat>Chicken</meat>
            <meat>Lamb</meat>
        </meats>
        <fruits>
            <fruit>Orange</fruit>
            <fruit>Apple</fruit>
            <fruit>Banana</fruit>
            <fruit>Avacado</fruit>
        </fruits>
        <vegetables>
            <vegetable>Carrot</vegetable>
            <vegetable>Cellery</vegetable>
            <vegetable>Potato</vegetable>
        </vegetables>
    </foods>`;
    
    
    function GenerateTestCaseResponse(xmlSource, xsltSource) {
        const domParser = new DOMParser();
        const xsltProcessor = new XSLTProcessor();
    
        const xsltStylesheet = domParser.parseFromString(xsltSource, "application/xml");
        xsltProcessor.importStylesheet(xsltStylesheet);
        
        var xmlDoc = domParser.parseFromString(xmlSource, "application/xml");
        var result = xsltProcessor.transformToFragment(xmlDoc, xmlDoc).textContent;
        
        console.log(result);
        return result;
    }
    
    GenerateTestCaseResponse(xmlSource, xsltSource);