XSLTC creating classes with illegal method names

60 Views Asked by At

I'm trying to compile an XSLT transformation using XSLTC, but the resulting class is not useable, because it contains methods with illegal names.

For illustration purposes, here is a (simplified) version of the stylesheet I use:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns1="http://some.weird/Namespace#/Definition"
    xmlns="http://another.strange/Namespace#/Definition">

    <xsl:template match="ns1:*">
        <xsl:element name="{local-name()}">
            <xsl:apply-templates />
        </xsl:element>
    </xsl:template>

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" />
        </xsl:copy>
    </xsl:template>

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

What this is trying to accomplish is to change the namespace of all the elements of an XML file to a different one, so I'm basically using a modified identity transformation. However, XSLTC seems to have a problem with the '#' characters in the namespace URIs (even though as far as I understand, they should be legal). Because the class file that XSLTC creates when compiling the above looks like this (decompiled, of course):

import org.apache.xalan.xsltc.DOM;
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.dom.UnionIterator;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xalan.xsltc.runtime.BasisLibrary;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;

public class ChangeNamespace extends AbstractTranslet {
    public DOM _dom;
    protected static String[] _sNamesArray = new String[0];
    protected static String[] _sUrisArray = new String[0];
    protected static int[] _sTypesArray = new int[0];
    protected static String[] _sNamespaceArray = new String[0];

    public void buildKeys(DOM var1, DTMAxisIterator var2, SerializationHandler var3, int var4) throws TransletException {
    }

    public void topLevel(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
        boolean var4 = false;
    }

    public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
        this._dom = this.makeDOMAdapter(var1);
        boolean var4 = false;
        this.transferOutputSettings(var3);
        this.buildKeys(this._dom, var2, var3, 0);
        this.topLevel(this._dom, var2, var3);
        var3.startDocument();
        this.applyTemplates(this._dom, var2, var3);
        var3.endDocument();
    }

    public void http$colon$$slash$$slash$another$dot$strange$slash$Namespace#$slash$Definition$colon$template$dot$0(DOM var1, DTMAxisIterator var2, SerializationHandler var3, int var4) {
        String var5 = BasisLibrary.getLocalName(var1.getNodeName(var4));
        BasisLibrary.checkQName(var5);
        String var10001 = BasisLibrary.startXslElement(var5, (String)null, var3, var1, var4);
        this.applyTemplates(var1, var1.getChildren(var4), var3);
        var3.endElement(var10001);
    }

    public void http$colon$$slash$$slash$another$dot$strange$slash$Namespace#$slash$Definition$colon$template$dot$1(DOM var1, DTMAxisIterator var2, SerializationHandler var3, int var4) {
        String var5;
        if((var5 = var1.shallowCopy(var4, var3)) != null) {
            int var6 = var5.length();
            this.applyTemplates(var1, (new UnionIterator(var1)).addIterator(var1.getAxisIterator(2)).addIterator(var1.getAxisIterator(3)).setStartNode(var4), var3);
            if(var6 != 0) {
                var3.endElement(var5);
            }
        }

    }

    public void http$colon$$slash$$slash$another$dot$strange$slash$Namespace#$slash$Definition$colon$template$dot$2(DOM var1, DTMAxisIterator var2, SerializationHandler var3, int var4) {
        var1.characters(var4, var3);
    }

    public final void applyTemplates(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
        int var4;
        while((var4 = var2.next()) >= 0) {
            switch(var1.getExpandedTypeID(var4)) {
            case 0:
            case 9:
                this.applyTemplates(var1, var1.getChildren(var4), var3);
                break;
            case 1:
                if(var1.getNamespaceName(var4).equals("http://some.weird/Namespace#/Definition")) {
                    this.http$colon$$slash$$slash$another$dot$strange$slash$Namespace#$slash$Definition$colon$template$dot$0(var1, var2, var3, var4);
                    break;
                } else {
                    var4 = var4;
                }
            case 2:
            case 7:
            case 8:
                this.http$colon$$slash$$slash$another$dot$strange$slash$Namespace#$slash$Definition$colon$template$dot$1(var1, var2, var3, var4);
                break;
            case 3:
                this.http$colon$$slash$$slash$another$dot$strange$slash$Namespace#$slash$Definition$colon$template$dot$2(var1, var2, var3, var4);
            case 4:
            case 5:
            case 6:
            case 10:
            case 11:
            case 12:
            case 13:
            }
        }

    }

    public ChangeNamespace() {
        super.namesArray = _sNamesArray;
        super.urisArray = _sUrisArray;
        super.typesArray = _sTypesArray;
        super.namespaceArray = _sNamespaceArray;
        super.transletVersion = 101;
    }
}

Note the '#' in some of the generated method names, which is illegal according to the Java spec, and no surprise that it results in a ClassFormatException courtesy of the classloader when I actually try to use the class.

Any ideas if I can somehow get XSLTC to compile the XSL into something valid? Can I perhaps modify my stylesheet somehow to accomplish the same thing that doesn't cause this problem?

And no, I can not change the namespaces themselves, they're fixed because they belong to an external system that I have to deal with but have no influence on.

1

There are 1 best solutions below

0
On

Investigating the problem further by debugging into the code of XSLTC, I managed to narrow the cause down to a call to a method named escape in the class org.apache.xalan.xsltc.compiler.util.Util.

This is what it looks like in the actual source code (Xalan 2.7.2):

public static String escape(String input) {
    return replace(input, ".-/:", new String[]{"$dot$", "$dash$", "$slash$", "$colon$"});
}

It is called from several places when trying to create method names from strings (like, in my case, namespaces), although this naive implementation is shockingly insufficient for that purpose, as demonstrated by my case. The returned string is used without any further modification or checks to ensure that the resulting name is actually valid in Java.

My solution (which I will not go into detail on here, because it is not a good solution - it is not for the faint-hearted and anyone trying to do the same thing should have sufficient know-how to replicate it without additional instruction, or stay far away from such trickery), was to actually use bytecode manipulation to replace the body of above method with something that also handles '#' characters. It's a desperate move, but one that works for me, at least for now.