Firefox XPCOM addon: "unexpected error" for xslt transform

2

I'm trying to update/refactor a legacy Firefox extension that makes extensive use of the XPCOM API. We do a lot of XSLT transforms and client-side file IO for a specialized application.

I keep getting strange errors, though, when processing XSLT transforms.

transformiix start
name of first node in the file file:///Users/sabrina/Documents/tmp/foo.xml: foo
name of first node in the file file:///Users/sabrina/Documents/xslt/foostyle.xslt: xsl:stylesheet
transformiix error: [Exception... "Unexpected error"  nsresult: "0x8000ffff (NS_ERROR_UNEXPECTED)"  location: "JS frame :: chrome://xulschoolhello/content/test.js :: ProcTest.Test.makeAndShow :: line 30"  data: no]

What the heck is "Unexpected error" and what could be causing it? Before I was getting the even more baffling "NS_ERROR_FAILURE (0x80004005)" which is the equivalent of Firefox throwing up its hands and shrugging.

Any ideas or suggestions are very welcome! Thanks in advance. :)

Edit to add: I should have said before, as this could affect the answer: I'm running this on Mac OSX in Firefox 38 ESR. I'm going to try calling an external xsltproc process next, though I'd prefer to use Firefox's native processor. I wonder if this could be some kind of file permissions problem? Please let me know what you think.

I've modified the Helloworld2 extension available here to include this code:

ProcTest.Test = {
    base: "file:///Users/sabrina/Documents/",
    consoleService: Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService),

    makeAndShow: function() {
        var outXML = null;

        ProcTest.Test.consoleService.logStringMessage("transformiix start");
        try {
            // load xml
            var xmlDoc = ProcTest.Test.readXML(ProcTest.Test.base + "tmp/foo.xml");
        } catch (x) {
            ProcTest.Test.consoleService.logStringMessage("error loading xml: " + x);
            return false;
        }

        try {
            // get xslt processor
            var xsltProc = ProcTest.Test.getXSLTProc(ProcTest.Test.base + "xslt/foostyle.xslt");
        } catch (x) {
            ProcTest.Test.consoleService.logStringMessage("error getting processor: " + x);
            return false;
        }

        // send in the params
        xsltProc.setParameter(null, "blah", "thingy!")

        try {
            // transform!
            outXML = xsltProc.transformToDocument(xmlDoc);
        } catch (x) {
            ProcTest.Test.consoleService.logStringMessage("transformiix error: " + x);
            return false;
        }

        try {
            // save
            ProcTest.Test.saveXML(outXML, ProcTest.Test.base + "xhtml/login.xhtml");
        } catch (x) {
            ProcTest.Test.consoleService.logStringMessage("error saving xml: " + x);
            return false;
        }
        ProcTest.Test.consoleService.logStringMessage("looks like it worked!")
        content.window.location.replace(ProcTest.Test.base + "xhtml/login.xhtml")
    },

    readXML: function(filepath) {
        var myXMLHTTPRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
        myXMLHTTPRequest.open("GET", filepath, false);
        myXMLHTTPRequest.send(null);

        ProcTest.Test.consoleService.logStringMessage("name of first node in the file " + filepath + ": " + myXMLHTTPRequest.responseXML.firstChild.nodeName)
        return myXMLHTTPRequest.responseXML;
    },

    getXSLTProc: function(xslt) {
        var xslStylesheet = ProcTest.Test.readXML(xslt);

        var xsltProcessor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"].createInstance(Components.interfaces.nsIXSLTProcessor)
        xsltProcessor.importStylesheet(xslStylesheet);

        return xsltProcessor
    },

    saveXML: function (doc, path) {
        try {
            var serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"].createInstance(Components.interfaces.nsIDOMSerializer);

            // get or create a file object
            var file = ProcTest.Test.fileObject(path)

            // prepare the xml object
            var xml = doc
            xml = content.document.implementation.createDocument('', '', doc.doctype)
            xml.appendChild(xml.importNode(doc.documentElement, true))

            // prepare the output stream object
            var output = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream)
            output.init(file, 0x20 | 0x02 | 0x08, 0665, 0)

            // send the file to the output stream
            serializer.serializeToStream(xml, output, 'UTF-8')
            output.flush()
            output.close()
        } catch(e) {
            ProcTest.Test.consoleService.logStringMessage("error saving xml: " + e)
        }
    },

    fileObject: function(path) {
        var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile)

        try {
            file.initWithPath(ProcTest.Test.base)
            var splitPath = path.split('/')
            for (var i = 0; i < splitPath.length; ++i) {
                file.appendRelativePath(splitPath[i])
                if (i < splitPath.length - 1) {
                    if (file.exists()) {
                        if (!file.isDirectory)
                            throw "expecting directory: " + file.path
                        } else {
                            file.create(Components.interfaces.nsILocalFile.DIRECTORY_TYPE, 0775)
                        }
                }
            }
        } catch (e) {
            ProcTest.Test.consoleService.logStringMessage("error making file " + path + ": " + e)
            throw e
        }
        return file
    },
}

Here are my raw files: foo.xml

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <bar>apples</bar>
    <bar>oranges</bar>
</foo>

foostyle.xslt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output encoding="UTF-8" method="xml" indent="yes"/>

    <xsl:param name="blah"/>

    <xsl:template match="bar">
        <option value="text()">
            <xsl:value-of select="text()"/>
        </option>
    </xsl:template>

    <xsl:template match="foo">
        <select id="bars">
            <xsl:apply-templates select="bar"/>
        </select>
    </xsl:template>


    <xsl:template match="/">
        <html>
            <head>
                <title>Foo</title>
            </head>

            <body>
                <div id="foo">
                    <xsl:apply-templates select="foo"/>
                </div>
                <div>blah: <xsl:value-of select="$blah"/></div>

            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Edit. See my answer below, where I solved the problem using an external process to run the transform. When I tried porting this to my 'real' code, I found that I am still getting exit status of 0, but the file is not being produced. When I run the transform manually, using the very same args the code puts together, the file gets created just fine. Is there some kind of permissions limitation on file IO by processes called by XPCOM? It worked in my proof-of-concept so I think not? But I've spent days and days now trying to solve this problem and I'm grasping for an answer.

xml
firefox
xslt
firefox-addon
xpcom
asked on Stack Overflow Oct 22, 2015 by Sabrina S • edited Oct 29, 2015 by Sabrina S

2 Answers

2

You do xsltProc.setParameter(null, "blah", "thingy!") so you pass in a primitive string value for the parameter named blah, yet then you do <xsl:apply-templates select="$blah"/>, meaning you try to apply-templates to a primitive string value. That is not going to work in XSLT 1.0 (which Firefox supports), nor in the current XSLT 2.0 version. It will be possible in XSLT 3.0 I think. In XSLT 1.0 you can only apply-templates to a node set.

I am not sure what you want to achieve, I think doing <xsl:value-of select="$blah"/> is all that makes sense with a string value in a parameter.

answered on Stack Overflow Oct 23, 2015 by Martin Honnen
1

This solution works, but it uses an entirely different approach: it calls an external process to perform the transform and save the xhtml output. Same xml and (corrected) xslt as shown above.

Console output:

transformiix start
xsltproc exit status code: 0
looks like it worked!

It uses a different xsltproc binary from the one bundled with OSX. This one describes itself as Using libxml 20900, libxslt 10128 and libexslt 817

ProcTest.Test = {
    base: "file:///Users/sabrina/Documents/",
    consoleService: Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService),

    makeAndShow: function() {
        var xmlPath = ProcTest.Test.base + "tmp/foo.xml"
        var xsltPath = ProcTest.Test.base + "xslt/foostyle.xslt"
        var outPath = ProcTest.Test.base + "xhtml/foo.xhtml"

        var xsltProc = ProcTest.Test.base + "osx/bin/xsltproc"
        var pid = {}

        var FileUtils = Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils

        ProcTest.Test.consoleService.logStringMessage("transformiix start");

        var args = []
        var proc = Components.classes['@mozilla.org/process/util;1'].createInstance(Components.interfaces.nsIProcess)

        // initialize the external proc call
        try {
            var file = new FileUtils.File( "/Users/sabrina/Documents/osx/bin/xsltproc" )
            proc.init(file)
        } catch (x) {
            ProcTest.Test.consoleService.logStringMessage("error initializing file object for proc: " + x);
            return false;
        }

        // set up the commandline args
        args.push('--output', outPath)
        args.push('--stringparam', 'blah', "thingy!")
        args.push(xsltPath, xmlPath)

        try {
            // transform!
            proc.run(true, args, args.length, pid);
        } catch (x) {
            ProcTest.Test.consoleService.logStringMessage("transformiix error: " + x);
            return false;
        }

        ProcTest.Test.consoleService.logStringMessage("xsltproc exit status code: " + proc.exitValue)
        if (proc.exitValue == 0) {
            ProcTest.Test.consoleService.logStringMessage("looks like it worked!")
            content.window.location.replace(outPath)
        } else {
            ProcTest.Test.consoleService.logStringMessage("xsltproc exit status code: " + proc.exitValue)
        }
    },

}
answered on Stack Overflow Oct 23, 2015 by Sabrina S

User contributions licensed under CC BY-SA 3.0