/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.xsl; import com.caucho.java.LineMap; import com.caucho.util.L10N; import com.caucho.vfs.Encoding; import com.caucho.vfs.IOExceptionWrapper; import com.caucho.vfs.Path; import com.caucho.vfs.Vfs; import com.caucho.vfs.WriteStream; import com.caucho.xml.*; import com.caucho.xpath.XPathFun; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.ext.LexicalHandler; import javax.xml.transform.ErrorListener; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.Properties; public class TransformerImpl extends javax.xml.transform.Transformer { protected static L10N L = new L10N(TransformerImpl.class); public final static String LINE_MAP = "caucho.line-map"; public final static String CACHE_DEPENDS = "caucho.cache.depends"; public final static String GENERATE_LOCATION = "caucho.generate.location"; protected StylesheetImpl _stylesheet; protected HashMap<String,Object> _properties = new HashMap<String,Object>(); protected HashMap<String,Object> _parameters; private URIResolver _uriResolver; private ErrorListener _errorListener; private Properties _output; protected LineMap _lineMap; protected ArrayList<Path> _cacheDepends = new ArrayList<Path>(); protected TransformerImpl(StylesheetImpl stylesheet) { _stylesheet = stylesheet; _uriResolver = stylesheet.getURIResolver(); } /** * Returns the URI to filename resolver. */ public URIResolver getURIResolver() { return _uriResolver; } /** * Sets the URI to filename resolver. */ public void setURIResolver(URIResolver uriResolver) { _uriResolver = uriResolver; } /** * Returns the error listener. */ public ErrorListener getErrorListener() { return _errorListener; } /** * Sets the error listener. */ public void setErrorListener(ErrorListener errorListener) { _errorListener = errorListener; } public boolean getFeature(String name) { if (name.equals(DOMResult.FEATURE)) return true; else if (name.equals(DOMSource.FEATURE)) return true; else if (name.equals(StreamSource.FEATURE)) return true; else if (name.equals(StreamResult.FEATURE)) return true; else if (name.equals(SAXSource.FEATURE)) return true; else if (name.equals(SAXResult.FEATURE)) return true; else return false; } public void setFeature(String name, boolean enable) { if (name.equals(GENERATE_LOCATION)) _stylesheet.setGenerateLocation(enable); } public StylesheetImpl getStylesheet() { return _stylesheet; } public Object getProperty(String name) { Object property = _properties.get(name); if (property != null) return property; if (name.equals(CACHE_DEPENDS)) return _cacheDepends; else if (name.equals(LINE_MAP)) return _lineMap; else return _stylesheet.getProperty(name); } public void setProperty(String name, Object value) { _properties.put(name, value); } /** * Sets a parameter that XPath expressions in the stylesheet can * use as $name. * * @param name the name of the XPath variable. * @param value the value for the variable. */ public void setParameter(String name, Object value) { if (_parameters == null) _parameters = new HashMap<String,Object>(); _parameters.put(name, value); } /** * Returns a copy of the xsl:output properties. * * @return a copy of the properties. */ public Properties getOutputProperties() { if (_output == null) _output = (Properties) _stylesheet.getOutputProperties().clone(); return (Properties) _output.clone(); } /** * Sets the output properties. * * @param properties the new output properties. */ public void setOutputProperties(Properties properties) { _output = properties; } /** * Sets a single xsl:output property. * * @param name the name of the property. * @param value the value of the property. */ public void setOutputProperty(String name, String value) { if (_output == null) _output = (Properties) _stylesheet.getOutputProperties().clone(); _output.put(name, value); } /** * Returns the value of a single named xsl:output property. * * @param name the name of the property. */ public String getOutputProperty(String name) { if (_output == null) _output = (Properties) _stylesheet.getOutputProperties().clone(); return (String) _output.get(name); } /** * Returns the named stylesheet parameter. * * @param name the name of the parameter. * * @ return the value of the named parameter. */ public Object getParameter(String name) { if (_parameters == null) return null; else return _parameters.get(name); } /** * Clears all the external stylesheet parameters. */ public void clearParameters() { if (_parameters != null) _parameters.clear(); if (_cacheDepends != null) _cacheDepends.clear(); } /** * Adds a new custom function. * * @param name the name of the function. * @param fun the new function. */ public void addFunction(String name, XPathFun fun) { _stylesheet.addFunction(name, fun); } /** * Transforms the source into the result. * * @param source descriptor specifying the input source. * @param result descriptor specifying the output result. */ public void transform(Source source, Result result) throws TransformerException { try { Node node = parseDocument(source); if (result instanceof StreamResult) { StreamResult stream = (StreamResult) result; if (stream.getOutputStream() != null) transform(node, stream.getOutputStream(), null, result.getSystemId()); else if (stream.getWriter() != null) { Writer writer = stream.getWriter(); WriteStream os = Vfs.openWrite(writer); if (writer instanceof OutputStreamWriter) { String javaEncoding = ((OutputStreamWriter) writer).getEncoding(); String mimeEncoding = Encoding.getMimeName(javaEncoding); transform(node, os, mimeEncoding, result.getSystemId()); } else transform(node, os, null, result.getSystemId()); os.flush(); os.free(); } else { WriteStream os = Vfs.lookup(result.getSystemId()).openWrite(); try { transform(node, os, null, result.getSystemId()); } finally { os.close(); } } } else if (result instanceof DOMResult) { DOMResult domResult = (DOMResult) result; Node resultNode = domResult.getNode(); domResult.setNode(transform(node, resultNode)); } else if (result instanceof SAXResult) { SAXResult sax = (SAXResult) result; transform(node, sax.getHandler(), sax.getLexicalHandler()); } else throw new TransformerException(String.valueOf(result)); } catch (TransformerException e) { throw e; } catch (Exception e) { throw new TransformerExceptionWrapper(e); } } public void transform(Node node, OutputStream os) throws TransformerException { if (os instanceof WriteStream) { String encoding = ((WriteStream) os).getEncoding(); if (encoding == null) encoding = "ISO-8859-1"; transform(node, os, encoding, null); } else transform(node, os, null, null); } /** * Transforms from a DOM node to an output stream. * * @param node the source node * @param os the destination stream */ public void transform(Node node, OutputStream os, String encoding, String systemId) throws TransformerException { if (node == null) throw new IllegalArgumentException("can't transform null node"); try { _lineMap = null; Properties output = getOutputProperties(); WriteStream ws; if (os instanceof WriteStream) ws = (WriteStream) os; else { ws = Vfs.openWrite(os); if (systemId != null) ws.setPath(Vfs.lookup(systemId)); else if (node instanceof QNode) { String baseURI = ((QNode) node).getBaseURI(); if (baseURI != null) ws.setPath(Vfs.lookup(baseURI)); } } XmlPrinter out = new XmlPrinter(ws); String method = (String) output.get(OutputKeys.METHOD); out.setMethod(method); if (encoding == null) encoding = (String) output.get(OutputKeys.ENCODING); if (encoding == null && ! (os instanceof WriteStream) && ! "html".equals(method)) encoding = "UTF-8"; if (encoding != null) out.setEncoding(encoding); out.setMimeType((String) output.get(OutputKeys.MEDIA_TYPE)); String omit = (String) output.get(OutputKeys.OMIT_XML_DECLARATION); if (omit == null || omit.equals("false") || omit.equals("no")) out.setPrintDeclaration(true); out.setStandalone((String) output.get(OutputKeys.STANDALONE)); out.setSystemId((String) output.get(OutputKeys.DOCTYPE_SYSTEM)); out.setPublicId((String) output.get(OutputKeys.DOCTYPE_PUBLIC)); String indent = (String) output.get(OutputKeys.INDENT); if (indent != null) out.setPretty(indent.equals("true") || indent.equals("yes")); String jsp = (String) output.get("caucho.jsp"); if (jsp != null) out.setJSP(jsp.equals("true") || jsp.equals("yes")); out.setVersion((String) output.get(OutputKeys.VERSION)); String includeContentType = (String) output.get("include-content-type"); if (includeContentType != null) out.setIncludeContentType(includeContentType.equals("true") || includeContentType.equals("yes")); if (! _stylesheet.getGenerateLocation()) { } else if (node instanceof CauchoNode) { String filename = ((CauchoNode) node).getFilename(); if (filename != null) out.setLineMap(filename); else out.setLineMap("anonymous.xsl"); } else out.setLineMap("anonymous.xsl"); //out.beginDocument(); _stylesheet.transform(node, out, this); //out.endDocument(); _lineMap = out.getLineMap(); if (os != ws) { ws.flush(); ws.free(); } } catch (TransformerException e) { throw e; } catch (Exception e) { throw new TransformerExceptionWrapper(e); } } /** * Transforms from the source node to the destination node, returning * the destination node. */ public Node transform(Node sourceNode, Node destNode) throws SAXException, IOException { _lineMap = null; if (destNode == null) destNode = Xml.createDocument(); DOMBuilder out = new DOMBuilder(); out.init(destNode); try { out.startDocument(); _stylesheet.transform(sourceNode, out, this); //out.endDocument(); } catch (Exception e) { throw new IOExceptionWrapper(e); } return destNode; } /** * Transforms from the source node to the sax handlers. */ public void transform(Node sourceNode, ContentHandler contentHandler, LexicalHandler lexicalHandler) throws SAXException, IOException, TransformerException { if (contentHandler == null) throw new IllegalArgumentException(L.l("null content handler")); _lineMap = null; SAXBuilder out = new SAXBuilder(); out.setContentHandler(contentHandler); out.startDocument(); _stylesheet.transform(sourceNode, out, this); //out.endDocument(); } /** * Parses the source XML document from the source. * * @param source the JAXP source. * * @return the parsed document. */ protected Node parseDocument(Source source) throws IOException, SAXException, TransformerException { if (source instanceof StreamSource) { StreamSource stream = (StreamSource) source; InputSource in = new InputSource(); in.setSystemId(stream.getSystemId()); in.setByteStream(stream.getInputStream()); in.setCharacterStream(stream.getReader()); XmlParser parser = Xml.create(); Node node = parser.parseDocument(in); parser.free(); return node; // return new QDocument(); } else if (source instanceof DOMSource){ Node node = ((DOMSource) source).getNode(); return node != null ? node : new QDocument(); } else if (source instanceof StringSource) { String string = ((StringSource) source).getString(); if (string != null) return parseStringDocument(string, source.getSystemId()); else return new QDocument(); } else if (source instanceof SAXSource) { SAXSource saxSource = (SAXSource) source; XMLReader reader = saxSource.getXMLReader(); if (reader == null) return new QDocument(); InputSource inputSource = saxSource.getInputSource(); Document doc = new QDocument(); DOMBuilder builder = new DOMBuilder(); builder.init(doc); reader.setContentHandler(builder); reader.parse(inputSource); return doc; } else throw new TransformerException(L.l("unknown source {0}", source)); } /** * Parses the source XML document from the input stream. * * @param is the source input stream. * @param systemId the path of the source * * @return document DOM node for the parsed XML. */ protected Node parseDocument(InputStream is, String systemId) throws IOException, SAXException { XmlParser parser = Xml.create(); Node node = parser.parseDocument(is); parser.free(); return node; } /** * Parses the source document specified by a URL * * @param url path to the document to be parsed. * * @return the parsed document. */ protected Node parseDocument(String url) throws IOException, SAXException { XmlParser parser = Xml.create(); Node node = parser.parseDocument(url); parser.free(); return node; } /** * Parses a string as an XML document. * * @param source the string to use as the XML source * @param systemId the URL for the string document. * * @return the parsed document. */ protected Node parseStringDocument(String source, String systemId) throws IOException, SAXException { XmlParser parser = Xml.create(); Node node = parser.parseDocumentString(source); parser.free(); return node; } public void addCacheDepend(Path path) { _cacheDepends.add(path); } protected void addCacheDepend(String path) { _cacheDepends.add(Vfs.lookup(path)); } public ArrayList<Path> getCacheDepends() { return _cacheDepends; } }