Transforming HTML nodes with XSLT in chrome/webkit

2020-07-26 18:07发布

问题:

I want to transform HTML nodes from a XHTML page with an XSL stylesheet.

here's the function I use:

function XSLT(xml,xsl) {
        var tmpElt = document.createElement("div");

        if (window.ActiveXObject){
            //Version IE
            tmpElt.innerHTML = xml.transformNode(xsl);
        }else{
            //Version ECMA
            var xsltProcessor = new XSLTProcessor();
            xsltProcessor.importStylesheet(xsl);
            var result = xsltProcessor.transformToFragment(xml, document);

            var serializer = new XMLSerializer();
            var str = serializer.serializeToString( result );

            tmpElt.innerHTML = str;
        }

        return tmpElt;
}

this will work on chrome as long as xml is not an HTMLElement. precisely, this line:

var result = xsltProcessor.transformToFragment(xml, document);

returns null on chromium[most of the time]. On Firefox it behaves properly.


I tried this workaround:

  • serialize xml with outerHTML ( and even with XMLSerializer::serializeToString() )
  • Parse it with a new DOMParser()
  • XSLT the result

but unfortunately outerHTML and innerHTML won't generate proper XHTML, and don't render valid XML ( it won't close single tags like <hr /> ).,XMLSerializer also won't close single tags, which is quite surprising to me.


I noticed the HTML node transformations will exceptionnaly works when the actual HTML doesn't contain any self closing tags. So I supect Chrome to internally serialize the HTML with XMLSerializer or outerHTML before the XSLT.

Here's the JSFiddle: http://jsfiddle.net/eVbv7/

 

So, how can I XSLT HTML nodes on Chrome? I need either a way to serialize XHTML nodes properly or any workaround will do.

回答1:

I pondered the problem again and then speculated whether you might not need custom cloning or serialization for Chrome or Webkit if you simply create a dummy XML DOM document and then importNode the element from the HTML DOM document. So I wrote http://jsfiddle.net/5TNH9/ and indeed both Chrome 13 and Safari 5.1 at least perform the transformation. So that might be another option to avoid implementing serialization or cloning completely with your own script code.

Note however that in that test case on jsfiddle both Chrome and Safari exhibit a problem with duplicated br elements that does not occur with Mozilla or Opera. I am currently not able to tell whether that problem is a general one with the approach I have chosen or something specific to that small test case.



回答2:

One more thing to beware of is that the "id" attribute must be unique. Simply passing some HTML through xsl:copy will result in duplicates (see the result of e.g. Martin's jsfiddle):

<div id="d1">
    <h2 align="center">Test</h2>
    <p>This is a test.<br>
        This is a test.<br>
        This is a test.
    </p>
</div>
<div xmlns="http://www.w3.org/1999/xhtml" id="d1">
    <h2 align="center">Test</h2>
    <p>This is a test.<br><br>
        This is a test.<br><br>
        This is a test.
    </p>
</div>

Duplicate id's might be handled in odd ways by different browsers, and so will the namespace issues. Not necessarily a bug, but certainly skating at the borders of the specifications.



回答3:

I think Chrome and Safari can do what you want if the XHTML nodes are part of an document parsed with the XML parser, meaning if you serve the original document you have as application/xhtml+xml instead of text/html then you can apply XSLT to the XHTML elements. Of course doing that rules out any IE version but IE 9 unless you are able to do content negotiation on the server to serve text/html to old IE versions and application/xhtml+xml to other browsers.

If the nodes you want to transform are part of a document parsed as text/html then I currently don't know a solution, other than writing your own code to serialize as needed according to XML rules or to write your own code cloning the HTML DOM nodes into X(HT)ML DOM nodes you can pass to the transformToFragment.

I know this answer is not directly solving your problem but based on the comments when earlier incompatibilities like https://bugs.webkit.org/show_bug.cgi?id=53375 where reported I don't think there is an easy fix. WebKit seems to do some heavy lifting there, on the one hand trying to implement the Mozilla XSLTProcessor API, on the other hand catering for libxslt as its XSLT processor, that way causing several incompatibilities.