Java, Xalan, JAXP, xml transformations from Java String

I racked my head against the wall over and over again for several hours, unable to determine why I was getting a prolog error, when I knew dang well my XML was well formed.

I’m using JAXP, which is detecting and using xalan as my transformation implementation.  I’m not sure who’s fault it is, but when I create a new StreamSource for my transformation, and I ask it to load the xml-stylesheet from the processing instruction, it simply doesn’t work. I keep getting one of two errors, depending on the format of the java String.

The first error I was getting was “javax.xml.transform.TransformerException: Content is not allowed in prolog“. The other error I was getting, once I put my xml declaration at the beginning of the string, was “javax.xml.transform.TransformerException: The markup in the document following the root element must be well-formed“. Of course, none of these are meaningful in any way. Neither one tells me that I’m not allowed to ask xalan to resolve my xml-stylesheet automatically, while using XML from a Java String object.

I started out with this…

xmlSource = new StreamSource(
 new ByteArrayInputStream(((String) xml).getBytes()))
xsltSource =
 transFact.getAssociatedStylesheet(xmlSource, null, null, null);
...
trans.transform(xmlSource, result);

Of course, that completely fails with the miserable errors described.  But alas, after several ours of messing around, I decided to look into the source of “org.apache.xalan.xslt.Process”.  It happens to use DOMSource, rather than StreamSource.  So, I tried that out, and it worked just fine.

And, just to help you out with a nice utility function for doing xalan transformations…

    /**
     * Runs a xalan transformation of the xml, with the specified xsl.
     *
     * @param xml        a String xml document, or a java.io.File object
     *                   pointing to the file
     * @param xsl        a String filename, or a java.io.File object pointing to
     *                   the file.  A null value indicates you want to resolve
     *                   the XML's "xml-stylesheet" processing instruction as
     *                   the XSL to use.  If it does not exist, it may fail.
     * @param parameters a map of parameters to pass to the XSL
     *
     * @return the String of the transformed xml
     *
     * @throws TransformerException if a transformation error occurs
     */
    public static String xalanTransformation(final Object xml, final Object xsl,
        final Map parameters)
        throws TransformerException, ParserConfigurationException, IOException,
        SAXException
    {
        // create an instance of TransformerFactory
        final ByteArrayOutputStream byteArray;
        final Result result;
        final TransformerFactory transFact;
        final Transformer trans;
        final Set keys;
        final Iterator keyIt;
        final ExceptionErrorListener errorListener;
 
        errorListener = new ExceptionErrorListener();
        byteArray = new ByteArrayOutputStream(BUFFER_CAPACITY);
        result = new StreamResult(byteArray);
        transFact = TransformerFactory.newInstance();
        transFact.setErrorListener(errorListener);
        final Source xmlSource;
        if (xml instanceof String)
        {
            final Document doc = stringToDocument((String) xml);
            logger.debug(documentToString(doc));
            xmlSource = new DOMSource(doc);
        }
        else if (xml instanceof File)
        {
            xmlSource = new StreamSource((File) xml);
        }
        else
        {
            throw new IllegalArgumentException(
                "Only java.lang.String and java.io.File xml are supported " +
                    "for the xml parameter");
        }
 
        final Source xsltSource;
 
        if (xsl == null)
        {   // grab the XSL defined by the XML's xml-stylesheet instruction
            xsltSource =
                transFact.getAssociatedStylesheet(xmlSource, null, null, null);
        }
        else if (xsl instanceof String)
        {
            final InputStream xsltResource = Util.class.getResourceAsStream(
                (String) xsl);
            if (xsltResource == null)
            {
                throw new IllegalArgumentException(
                    xsl + " is an invalid XSL file");
            }
            xsltSource = new StreamSource(xsltResource);
        }
        else if (xsl instanceof File)
        {
            xsltSource = new StreamSource((File) xsl);
        }
        else
        {
            throw new IllegalArgumentException(
                "Only java.lang.String xsl filenames, or java.io.File " +
                    "are supported for the xsl parameter");
        }
 
        trans = transFact.newTransformer(xsltSource);
        trans.setErrorListener(errorListener);
        keys = parameters.keySet();
        keyIt = keys.iterator();
        while (keyIt.hasNext())
        {
            final String key;
            key = (String) keyIt.next();    // assume string key
            trans.setParameter(key, parameters.get(key));
        }
        trans.transform(xmlSource, result);
        return byteArray.toString();
    }
 
    /**
     * Example for retrieving the APAS institutions list
     *
     * @param xml the string representation of the XML
     *
     * @return the Document object created from the XML string representation
     *
     * @throws IOException  if an I/O error occurs
     * @throws SAXException if an XML parsing exception occurs.
     */
    public static Document stringToDocument(final String xml)
        throws SAXException, IOException
    {
        return loadXMLFrom(new ByteArrayInputStream(xml.getBytes()));
    }
 
    public static Document loadXMLFrom(final InputStream is)
        throws SAXException, IOException
    {
        final DocumentBuilderFactory factory =
            DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = null;
        try
        {
            builder = factory.newDocumentBuilder();
        }
        catch (ParserConfigurationException ignored)
        {
            // throw something here if you like.
        }
        assert builder != null;
        final Document doc = builder.parse(is);
        is.close();
        return doc;
    }

Example usage is below. Take note how I pass in null for the XSL, as the XSL can be loaded from the “xml-stylesheet” processing instruction in the XML document.  The utility method above requests resolution of the stylesheet from the XML, if the “xsl” parameter is null.

    System.out.println(Util.xalanTransformation(xml, null, new HashMap()));