/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-04 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.storage.serializers;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger;
import org.exist.Namespaces;
import org.exist.dom.DocumentImpl;
import org.exist.dom.NodeProxy;
import org.exist.dom.ProcessingInstructionImpl;
import org.exist.dom.QName;
import org.exist.dom.StoredNode;
import org.exist.dom.XMLUtil;
import org.exist.http.servlets.RequestWrapper;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.http.servlets.SessionWrapper;
import org.exist.indexing.IndexController;
import org.exist.indexing.MatchListener;
import org.exist.numbering.NodeId;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.User;
import org.exist.storage.DBBroker;
import org.exist.util.Configuration;
import org.exist.util.MimeType;
import org.exist.util.serializer.AttrList;
import org.exist.util.serializer.Receiver;
import org.exist.util.serializer.ReceiverToSAX;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
import org.exist.xquery.Option;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
import org.exist.xslt.TransformerFactoryAllocator;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
/**
* Serializer base class, used to serialize a document or document fragment
* back to XML. A serializer may be obtained by calling DBBroker.getSerializer().
*
* The class basically offers two overloaded methods: serialize()
* and toSAX(). serialize() returns the XML as a string, while
* toSAX() generates a stream of SAX events. The stream of SAX
* events is passed to the ContentHandler set by setContentHandler().
*
* Internally, both types of methods pass events to a {@link org.exist.util.serializer.Receiver}.
* Subclasses thus have to implement the various serializeToReceiver() methods.
*
* Output can be configured through properties. Property keys are defined in classes
* {@link javax.xml.transform.OutputKeys} and {@link org.exist.storage.serializers.EXistOutputKeys}
*
*@author Wolfgang Meier <wolfgang@exist-db.org>
*/
public abstract class Serializer implements XMLReader {
protected final static Logger LOG = Logger.getLogger(Serializer.class);
public static final String CONFIGURATION_ELEMENT_NAME = "serializer";
public static final String ENABLE_XINCLUDE_ATTRIBUTE = "enable-xinclude";
public static final String PROPERTY_ENABLE_XINCLUDE = "serialization.enable-xinclude";
public static final String ENABLE_XSL_ATTRIBUTE = "enable-xsl";
public static final String PROPERTY_ENABLE_XSL = "serialization.enable-xsl";
public static final String INDENT_ATTRIBUTE = "indent";
public static final String PROPERTY_INDENT = "serialization.indent";
public static final String COMPRESS_OUTPUT_ATTRIBUTE = "compress-output";
public static final String PROPERTY_COMPRESS_OUTPUT = "serialization.compress-output";
public static final String ADD_EXIST_ID_ATTRIBUTE = "add-exist-id";
public static final String PROPERTY_ADD_EXIST_ID = "serialization.add-exist-id";
public static final String TAG_MATCHING_ELEMENTS_ATTRIBUTE = "match-tagging-elements";
public static final String PROPERTY_TAG_MATCHING_ELEMENTS = "serialization.match-tagging-elements";
public static final String TAG_MATCHING_ATTRIBUTES_ATTRIBUTE = "match-tagging-attributes";
public static final String PROPERTY_TAG_MATCHING_ATTRIBUTES = "serialization.match-tagging-attributes";
public static final String PROPERTY_SESSION_ID = "serialization.session-id";
// constants to configure the highlighting of matches in text and attributes
public final static int TAG_NONE = 0x0;
public final static int TAG_ELEMENT_MATCHES = 0x1;
public final static int TAG_ATTRIBUTE_MATCHES = 0x2;
public final static int TAG_BOTH = 0x3;
public final static int EXIST_ID_NONE = 0;
public final static int EXIST_ID_ELEMENT = 1;
public final static int EXIST_ID_ALL = 2;
protected int showId = EXIST_ID_NONE;
public final static String GENERATE_DOC_EVENTS = "sax-document-events";
public final static String ENCODING = "encoding";
protected final static QName ATTR_HITS_QNAME = new QName("hits", Namespaces.EXIST_NS, "exist");
protected final static QName ATTR_START_QNAME = new QName("start", Namespaces.EXIST_NS, "exist");
protected final static QName ATTR_COUNT_QNAME = new QName("count", Namespaces.EXIST_NS, "exist");
protected final static QName ELEM_RESULT_QNAME = new QName("result", Namespaces.EXIST_NS, "exist");
protected final static QName ATTR_SESSION_ID = new QName("session", Namespaces.EXIST_NS, "exist");
protected final static QName ATTR_TYPE_QNAME = new QName("type", Namespaces.EXIST_NS, "exist");
protected final static QName ELEM_VALUE_QNAME = new QName("value", Namespaces.EXIST_NS, "exist");
protected DBBroker broker;
protected String encoding = "UTF-8";
private EntityResolver entityResolver = null;
private ErrorHandler errorHandler = null;
protected SAXTransformerFactory factory;
protected boolean createContainerElements = false;
protected Properties defaultProperties = new Properties();
protected Properties outputProperties;
protected Templates templates = null;
protected TransformerHandler xslHandler = null;
protected XIncludeFilter xinclude;
protected CustomMatchListenerFactory customMatchListeners;
protected Receiver receiver = null;
protected SAXSerializer xmlout = null;
protected LexicalHandler lexicalHandler = null;
protected User user = null;
protected HttpContext httpContext = null;
public class HttpContext
{
private RequestWrapper request = null;
private ResponseWrapper response = null;
private SessionWrapper session = null;
public RequestWrapper getRequest() {
return request;
}
public void setRequest(RequestWrapper request) {
this.request = request;
}
public ResponseWrapper getResponse() {
return response;
}
public void setResponse(ResponseWrapper response) {
this.response = response;
}
public SessionWrapper getSession() {
return session;
}
public void setSession(SessionWrapper session) {
this.session = session;
}
}
public void setHttpContext(HttpContext httpContext)
{
this.httpContext = httpContext;
}
public Serializer(DBBroker broker, Configuration config) {
this.broker = broker;
factory = TransformerFactoryAllocator.getTransformerFactory(broker.getBrokerPool());
xinclude = new XIncludeFilter(this);
customMatchListeners = new CustomMatchListenerFactory(broker, config);
receiver = xinclude;
String option = (String) config.getProperty(PROPERTY_ENABLE_XSL);
if (option != null)
defaultProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, option);
else
defaultProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, "no");
option = (String) config.getProperty(PROPERTY_ENABLE_XINCLUDE);
if (option != null) {
defaultProperties.setProperty(EXistOutputKeys.EXPAND_XINCLUDES, option);
}
option = (String) config.getProperty(PROPERTY_INDENT);
if (option != null)
defaultProperties.setProperty(OutputKeys.INDENT, option);
option = (String) config.getProperty(PROPERTY_COMPRESS_OUTPUT);
if (option != null)
defaultProperties.setProperty(EXistOutputKeys.COMPRESS_OUTPUT, option);
option = (String) config.getProperty(PROPERTY_ADD_EXIST_ID);
if (option != null)
defaultProperties.setProperty(EXistOutputKeys.ADD_EXIST_ID, option);
boolean tagElements = true, tagAttributes = false;
if ((option =
(String) config.getProperty(PROPERTY_TAG_MATCHING_ELEMENTS))
!= null)
tagElements = option.equals("yes");
if ((option =
(String) config.getProperty(PROPERTY_TAG_MATCHING_ATTRIBUTES))
!= null)
tagAttributes = option.equals("yes");
if (tagElements && tagAttributes)
option = "both";
else if (tagElements)
option = "elements";
else if (tagAttributes)
option = "attributes";
else
option = "none";
defaultProperties.setProperty(EXistOutputKeys.HIGHLIGHT_MATCHES, option);
defaultProperties.setProperty(GENERATE_DOC_EVENTS, "true");
outputProperties = new Properties(defaultProperties);
}
public void setProperties(Properties properties)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (properties == null)
return;
String key;
for(Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) {
key = (String)e.nextElement();
if(key.equals(Namespaces.SAX_LEXICAL_HANDLER))
lexicalHandler = (LexicalHandler)properties.get(key);
else
setProperty(key, properties.getProperty(key));
}
}
public void setProperties(HashMap table)
throws SAXNotRecognizedException, SAXNotSupportedException {
if(table == null)
return;
for(Iterator i = table.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry) i.next();
setProperty((String)entry.getKey(), entry.getValue().toString());
}
}
public void setProperty(String prop, Object value)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (prop.equals(Namespaces.SAX_LEXICAL_HANDLER)) {
lexicalHandler = (LexicalHandler) value;
} else if (EXistOutputKeys.ADD_EXIST_ID.equals(prop)) {
if (value.equals("element"))
showId = EXIST_ID_ELEMENT;
else if (value.equals("all"))
showId = EXIST_ID_ALL;
else
showId = EXIST_ID_NONE;
} else {
outputProperties.put(prop, value);
}
}
public String getProperty(String key, String defaultValue) {
String value = outputProperties.getProperty(key, defaultValue);
return value;
}
public boolean isStylesheetApplied() {
return templates != null;
}
protected int getHighlightingMode() {
String option =
getProperty(EXistOutputKeys.HIGHLIGHT_MATCHES, "elements");
if (option.equals("both") || option.equals("all"))
return TAG_BOTH;
else if (option.equals("elements"))
return TAG_ELEMENT_MATCHES;
else if (option.equals("attributes"))
return TAG_ATTRIBUTE_MATCHES;
else
return TAG_NONE;
}
/**
* If an XSL stylesheet is present, plug it into
* the chain.
*/
protected void applyXSLHandler(Writer writer) {
StreamResult result = new StreamResult(writer);
xslHandler.setResult(result);
if (getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes")
.equals("yes")) {
xinclude.setReceiver(new ReceiverToSAX(xslHandler));
receiver = xinclude;
} else
receiver = new ReceiverToSAX(xslHandler);
}
/**
* Return my internal EntityResolver
*
*@return The entityResolver value
*/
public EntityResolver getEntityResolver() {
return entityResolver;
}
public ErrorHandler getErrorHandler() {
return errorHandler;
}
/**
* Set the current User. A valid user is required to
* process XInclude elements.
*/
public void setUser(User user) {
this.user = user;
}
/**
* Get the current User.
*/
public User getUser() {
return user;
}
public boolean getFeature(String name)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (name.equals(Namespaces.SAX_NAMESPACES)
|| name.equals(Namespaces.SAX_NAMESPACES_PREFIXES))
throw new SAXNotSupportedException(name);
throw new SAXNotRecognizedException(name);
}
public Object getProperty(String name)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (name.equals(Namespaces.SAX_LEXICAL_HANDLER))
return lexicalHandler;
throw new SAXNotRecognizedException(name);
}
public String getStylesheetProperty(String name) {
if (xslHandler != null)
return xslHandler.getTransformer().getOutputProperty(name);
return null;
}
public void parse(InputSource input) throws IOException, SAXException {
// only system-ids are handled
String doc = input.getSystemId();
if (doc == null)
throw new SAXException("source is not an eXist document");
parse(doc);
}
protected void setDocument(DocumentImpl doc) {
xinclude.setDocument(doc);
}
protected void setXQueryContext(XQueryContext context) {
if (context != null)
xinclude.setModuleLoadPath(context.getModuleLoadPath());
}
public void parse(String systemId) throws IOException, SAXException {
try {
// try to load document from eXist
//TODO: this systemId came from exist, so should be an unchecked create, right?
DocumentImpl doc = (DocumentImpl) broker.getXMLResource(XmldbURI.create(systemId));
if (doc == null)
throw new SAXException("document " + systemId + " not found in database");
else
LOG.debug("serializing " + doc.getFileURI());
if(!doc.getPermissions().validate(broker.getUser(), Permission.READ))
throw new PermissionDeniedException("Not allowed to read resource");
toSAX(doc);
} catch (PermissionDeniedException e) {
throw new SAXException("permission denied");
}
}
/**
* Reset the class to its initial state.
*/
public void reset() {
receiver = xinclude;
xinclude.setModuleLoadPath(null);
xinclude.setReceiver(null);
xslHandler = null;
templates = null;
outputProperties.clear();
showId = EXIST_ID_NONE;
httpContext = null;
}
public String serialize(DocumentImpl doc) throws SAXException {
StringWriter writer = new StringWriter();
serialize(doc, writer);
return writer.toString();
}
/**
* Serialize a document to the supplied writer.
*/
public void serialize(DocumentImpl doc, Writer writer) throws SAXException {
serialize(doc, writer, true);
}
public void serialize(DocumentImpl doc, Writer writer, boolean prepareStylesheet) throws SAXException {
if (prepareStylesheet) {
try {
prepareStylesheets(doc);
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
}
if (templates != null)
applyXSLHandler(writer);
else {
//looking for serializer properties in <?exist-serialize?>
NodeList children = doc.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
StoredNode node = (StoredNode) children.item(i);
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
&& node.getNodeName().equals("exist-serialize")) {
String params[] = ((ProcessingInstructionImpl)node).getData().split(" ");
for(String param : params) {
String opt[] = Option.parseKeyValuePair(param);
if (opt != null)
outputProperties.setProperty(opt[0], opt[1]);
}
}
}
setPrettyPrinter(writer, outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes").equals("no"),
null, true); //setPrettyPrinter(writer, false);
}
serializeToReceiver(doc, true);
releasePrettyPrinter();
}
public String serialize(NodeValue n) throws SAXException {
StringWriter out = new StringWriter();
serialize(n,out);
return out.toString();
}
public void serialize(NodeValue n, Writer out) throws SAXException {
try {
setStylesheetFromProperties(n.getOwnerDocument());
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
if (templates != null)
applyXSLHandler(out);
else
setPrettyPrinter(out, outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes").equals("no"),
n.getImplementationType() == NodeValue.PERSISTENT_NODE ? (NodeProxy)n : null, false); //setPrettyPrinter(out, false);
serializeToReceiver(n, true);
releasePrettyPrinter();
}
/**
* Serialize a single NodeProxy.
*
*@param p Description of the Parameter
*@return Description of the Return Value
*@exception SAXException Description of the Exception
*/
public String serialize(NodeProxy p) throws SAXException {
StringWriter out = new StringWriter();
serialize(p,out);
return out.toString();
}
public void serialize(NodeProxy p, Writer out) throws SAXException {
try {
setStylesheetFromProperties(p.getOwnerDocument());
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
if (templates != null)
applyXSLHandler(out);
else
setPrettyPrinter(out, outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes").equals("no"),
p, false); //setPrettyPrinter(out, false);
serializeToReceiver(p, false);
releasePrettyPrinter();
}
public void prepareStylesheets(DocumentImpl doc) throws TransformerConfigurationException {
if (outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no").equals("yes")) {
String stylesheet = hasXSLPi(doc);
if (stylesheet != null)
setStylesheet(doc, stylesheet);
}
setStylesheetFromProperties(doc);
}
/**
* Set the ContentHandler to be used during serialization.
*
*@param contentHandler The new contentHandler value
*/
public void setSAXHandlers(ContentHandler contentHandler, LexicalHandler lexicalHandler) {
ReceiverToSAX toSAX = new ReceiverToSAX(contentHandler);
toSAX.setLexicalHandler(lexicalHandler);
if (getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes")
.equals("yes")) {
xinclude.setReceiver(toSAX);
receiver = xinclude;
} else
receiver = toSAX;
}
public void setReceiver(Receiver receiver) {
this.receiver = receiver;
}
/* (non-Javadoc)
* @see org.xml.sax.XMLReader#setContentHandler(org.xml.sax.ContentHandler)
*/
public void setContentHandler(ContentHandler handler) {
setSAXHandlers(handler, null);
}
/**
* Required by interface XMLReader. Always returns null.
*
* @see org.xml.sax.XMLReader#getContentHandler()
*/
public ContentHandler getContentHandler() {
return null;
}
/**
* Sets the entityResolver attribute of the Serializer object
*
*@param resolver The new entityResolver value
*/
public void setEntityResolver(EntityResolver resolver) {
entityResolver = resolver;
}
/**
* Sets the errorHandler attribute of the Serializer object
*
*@param handler The new errorHandler value
*/
public void setErrorHandler(ErrorHandler handler) {
errorHandler = handler;
}
/**
* Sets the feature attribute of the Serializer object
*
*@param name The new feature value
*@param value The new feature value
*@exception SAXNotRecognizedException Description of the Exception
*@exception SAXNotSupportedException Description of the Exception
*/
public void setFeature(String name, boolean value)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (name.equals(Namespaces.SAX_NAMESPACES)
|| name.equals(Namespaces.SAX_NAMESPACES_PREFIXES))
throw new SAXNotSupportedException(name);
throw new SAXNotRecognizedException(name);
}
protected void setPrettyPrinter(Writer writer, boolean xmlDecl, NodeProxy root, boolean applyFilters) {
outputProperties.setProperty(
OutputKeys.OMIT_XML_DECLARATION,
xmlDecl ? "no" : "yes");
xmlout = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
xmlout.setOutput(writer, outputProperties);
if (getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes")
.equals("yes")) {
xinclude.setReceiver(xmlout);
receiver = xinclude;
} else
receiver = xmlout;
if (root != null && getHighlightingMode() != TAG_NONE) {
IndexController controller = broker.getIndexController();
MatchListener listener = controller.getMatchListener(root);
if (listener != null) {
MatchListener last = (MatchListener) listener.getLastInChain();
last.setNextInChain(receiver);
receiver = listener;
}
}
if (root == null && applyFilters && customMatchListeners.getFirst() != null) {
customMatchListeners.getLast().setNextInChain(receiver);
receiver = customMatchListeners.getFirst();
}
}
protected Receiver setupMatchListeners(NodeProxy p) {
Receiver oldReceiver = receiver;
if (getHighlightingMode() != TAG_NONE) {
IndexController controller = broker.getIndexController();
MatchListener listener = controller.getMatchListener(p);
if (listener != null) {
MatchListener last = (MatchListener) listener.getLastInChain();
last.setNextInChain(receiver);
receiver = listener;
}
}
return oldReceiver;
}
protected void releasePrettyPrinter() {
if (xmlout != null)
SerializerPool.getInstance().returnObject(xmlout);
xmlout = null;
}
protected void setStylesheetFromProperties(Document doc) throws TransformerConfigurationException {
if(templates != null)
return;
String stylesheet = outputProperties.getProperty(EXistOutputKeys.STYLESHEET);
if(stylesheet != null) {
if(doc instanceof DocumentImpl)
setStylesheet((DocumentImpl)doc, stylesheet);
else
setStylesheet(null, stylesheet);
}
}
protected void checkStylesheetParams() {
if(xslHandler == null)
return;
for(Enumeration e = outputProperties.propertyNames(); e.hasMoreElements(); ) {
String property = (String)e.nextElement();
if(property.startsWith(EXistOutputKeys.STYLESHEET_PARAM)) {
String value = outputProperties.getProperty(property);
property = property.substring(EXistOutputKeys.STYLESHEET_PARAM.length() + 1);
xslHandler.getTransformer().setParameter(property, value);
}
}
}
/**
* Plug an XSL stylesheet into the processing pipeline.
* All output will be passed to this stylesheet.
*/
public void setStylesheet(DocumentImpl doc, String stylesheet) throws TransformerConfigurationException {
if (stylesheet == null) {
templates = null;
return;
}
long start = System.currentTimeMillis();
xslHandler = null;
XmldbURI stylesheetUri = null;
URI externalUri = null;
try {
stylesheetUri = XmldbURI.xmldbUriFor(stylesheet);
if(!stylesheetUri.toCollectionPathURI().equals(stylesheetUri)) {
externalUri = stylesheetUri.getXmldbURI();
}
} catch (URISyntaxException e) {
//could be an external URI!
try {
externalUri = new URI(stylesheet);
} catch (URISyntaxException ee) {
throw new IllegalArgumentException("Stylesheet URI could not be parsed: "+ee.getMessage());
}
}
// does stylesheet point to an external resource?
if (externalUri!=null) {
StreamSource source = new StreamSource(externalUri.toString());
templates = factory.newTemplates(source);
// read stylesheet from the database
} else {
// if stylesheet is relative, add path to the
// current collection and normalize
if(doc != null) {
stylesheetUri = doc.getCollection().getURI().resolveCollectionPath(stylesheetUri).normalizeCollectionPath();
}
// load stylesheet from eXist
DocumentImpl xsl = null;
try {
xsl = (DocumentImpl) broker.getXMLResource(stylesheetUri);
} catch (PermissionDeniedException e) {
throw new TransformerConfigurationException("permission denied to read " + stylesheetUri);
}
if (xsl == null) {
throw new TransformerConfigurationException("stylesheet not found: " + stylesheetUri);
}
if(!xsl.getPermissions().validate(broker.getUser(), Permission.READ)) {
throw new TransformerConfigurationException("permission denied to read " + stylesheetUri);
}
//TODO: use xmldbURI
if (xsl.getCollection() != null) {
factory.setURIResolver(
new InternalURIResolver(xsl.getCollection().getURI().toString()));
}
// save handlers
Receiver oldReceiver = receiver;
// compile stylesheet
factory.setErrorListener(new ErrorListener());
TemplatesHandler handler = factory.newTemplatesHandler();
receiver = new ReceiverToSAX(handler);
try {
this.serializeToReceiver(xsl, true);
templates = handler.getTemplates();
} catch (SAXException e) {
throw new TransformerConfigurationException(e.getMessage(), e);
}
// restore handlers
receiver = oldReceiver;
factory.setURIResolver(null);
}
LOG.debug(
"compiling stylesheet took " + (System.currentTimeMillis() - start));
if(templates != null)
xslHandler = factory.newTransformerHandler(templates);
// xslHandler.getTransformer().setOutputProperties(outputProperties);
checkStylesheetParams();
}
/**
* Set stylesheet parameter
**/
public void setStylesheetParam(String param, String value) {
if (xslHandler != null)
xslHandler.getTransformer().setParameter(param, value);
}
protected void setXSLHandler(NodeProxy root, boolean applyFilters) {
if (templates != null && xslHandler != null) {
SAXResult result = new SAXResult();
boolean processXInclude =
getProperty(
EXistOutputKeys.EXPAND_XINCLUDES,
"yes").equals(
"yes");
ReceiverToSAX filter;
if (processXInclude) {
filter = (ReceiverToSAX)xinclude.getReceiver();
} else {
filter = (ReceiverToSAX) receiver;
}
result.setHandler(filter.getContentHandler());
result.setLexicalHandler(filter.getLexicalHandler());
filter.setLexicalHandler(xslHandler);
filter.setContentHandler(xslHandler);
xslHandler.setResult(result);
if (processXInclude) {
xinclude.setReceiver(new ReceiverToSAX(xslHandler));
receiver = xinclude;
} else
receiver = new ReceiverToSAX(xslHandler);
}
if (root != null && getHighlightingMode() != TAG_NONE) {
IndexController controller = broker.getIndexController();
MatchListener listener = controller.getMatchListener(root);
if (listener != null) {
MatchListener last = (MatchListener) listener.getLastInChain();
last.setNextInChain(receiver);
receiver = listener;
}
}
if (applyFilters && root == null && customMatchListeners.getFirst() != null) {
customMatchListeners.getLast().setNextInChain(receiver);
receiver = customMatchListeners.getFirst();
}
}
public void toSAX(DocumentImpl doc) throws SAXException {
if (outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no").equals("yes")) {
String stylesheet = hasXSLPi(doc);
if (stylesheet != null)
try {
setStylesheet(doc, stylesheet);
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
}
try {
setStylesheetFromProperties(doc);
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
setXSLHandler(null, true);
serializeToReceiver(
doc,
getProperty(GENERATE_DOC_EVENTS, "false").equals("true"));
}
public void toSAX(NodeValue n) throws SAXException {
try {
setStylesheetFromProperties(n.getOwnerDocument());
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
setXSLHandler(n.getImplementationType() == NodeValue.PERSISTENT_NODE ? (NodeProxy)n : null, false);
serializeToReceiver(
n,
getProperty(GENERATE_DOC_EVENTS, "false").equals("true"));
}
public void toSAX(NodeProxy p) throws SAXException {
try {
setStylesheetFromProperties(p.getOwnerDocument());
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
setXSLHandler(p, false);
if (p.getNodeId() == NodeId.DOCUMENT_NODE) {
serializeToReceiver(p.getDocument(), getProperty(GENERATE_DOC_EVENTS, "false").equals("true"));
} else {
serializeToReceiver(p, getProperty(GENERATE_DOC_EVENTS, "false").equals("true"));
}
}
/**
* Serialize the items in the given sequence to SAX, starting with item start. If parameter
* wrap is set to true, output a wrapper element to enclose the serialized items. The
* wrapper element will be in namespace {@link org.exist.Namespaces#EXIST_NS} and has the following form:
*
* <exist:result hits="sequence length" start="value of start" count="value of count">
*
* @param seq
* @param start
* @param count
* @param wrap
* @throws SAXException
*/
public void toSAX(Sequence seq, int start, int count, boolean wrap) throws SAXException {
try {
setStylesheetFromProperties(null);
} catch (TransformerConfigurationException e) {
throw new SAXException(e.getMessage(), e);
}
setXSLHandler(null, false);
AttrList attrs = new AttrList();
attrs.addAttribute(ATTR_HITS_QNAME, Integer.toString(seq.getItemCount()));
attrs.addAttribute(ATTR_START_QNAME, Integer.toString(start));
attrs.addAttribute(ATTR_COUNT_QNAME, Integer.toString(count));
if (outputProperties.getProperty(PROPERTY_SESSION_ID) != null) {
attrs.addAttribute(ATTR_SESSION_ID, outputProperties.getProperty(PROPERTY_SESSION_ID));
}
receiver.startDocument();
if(wrap) {
receiver.startPrefixMapping("exist", Namespaces.EXIST_NS);
receiver.startElement(ELEM_RESULT_QNAME, attrs);
}
Item item;
for(int i = --start; i < start + count; i++) {
item = seq.itemAt(i);
if (item == null) {
LOG.debug("item " + i + " not found");
continue;
}
if (Type.subTypeOf(item.getType(), Type. NODE)) {
NodeValue node = (NodeValue) item;
serializeToReceiver(node, false);
} else {
if(wrap) {
attrs = new AttrList();
attrs.addAttribute(ATTR_TYPE_QNAME, Type.getTypeName(item.getType()));
receiver.startElement(ELEM_VALUE_QNAME, attrs);
}
try {
receiver.characters(item.getStringValue());
} catch (XPathException e) {
throw new SAXException(e.getMessage(), e);
}
if(wrap) {
receiver.endElement(ELEM_VALUE_QNAME);
}
}
}
if(wrap) {
receiver.endElement(ELEM_RESULT_QNAME);
receiver.endPrefixMapping("exist");
}
receiver.endDocument();
}
public void toReceiver(NodeProxy p, boolean highlightMatches) throws SAXException {
toReceiver(p, highlightMatches, true);
}
public void toReceiver(NodeProxy p, boolean highlightMatches, boolean checkAttributes) throws SAXException {
Receiver oldReceiver = highlightMatches ? setupMatchListeners(p) : receiver;
serializeToReceiver(p, false, checkAttributes);
receiver = oldReceiver;
}
protected abstract void serializeToReceiver(NodeProxy p, boolean generateDocEvent, boolean checkAttributes) throws SAXException;
protected abstract void serializeToReceiver(DocumentImpl doc, boolean generateDocEvent)
throws SAXException;
protected void serializeToReceiver(NodeValue v, boolean generateDocEvents)
throws SAXException {
if(v.getImplementationType() == NodeValue.PERSISTENT_NODE)
serializeToReceiver((NodeProxy)v, generateDocEvents, true);
else
serializeToReceiver((org.exist.memtree.NodeImpl)v, generateDocEvents);
}
protected void serializeToReceiver(org.exist.memtree.NodeImpl n, boolean generateDocEvents)
throws SAXException {
if (generateDocEvents)
receiver.startDocument();
setDocument(null);
setXQueryContext(n.getDocument().getContext());
n.streamTo(this, receiver);
if (generateDocEvents)
receiver.endDocument();
}
/**
* Inherited from XMLReader. Ignored.
*
* @see org.xml.sax.XMLReader#setDTDHandler(org.xml.sax.DTDHandler)
*/
public void setDTDHandler(DTDHandler handler) {
}
/**
* Inherited from XMLReader. Ignored. Returns always null.
*
* @see org.xml.sax.XMLReader#getDTDHandler()
*/
public DTDHandler getDTDHandler() {
return null;
}
/**
* Check if the document has an xml-stylesheet processing instruction
* that references an XSLT stylesheet. Return the link to the stylesheet.
*
* @param doc
* @return link to the stylesheet
*/
public String hasXSLPi(Document doc) {
boolean applyXSLPI =
outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no").equalsIgnoreCase("yes");
if (!applyXSLPI) return null;
NodeList docChildren = doc.getChildNodes();
Node node;
String xsl, type, href;
for (int i = 0; i < docChildren.getLength(); i++) {
node = docChildren.item(i);
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
&& ((ProcessingInstruction) node).getTarget().equals("xml-stylesheet")) {
// found <?xml-stylesheet?>
xsl = ((ProcessingInstruction) node).getData();
type = XMLUtil.parseValue(xsl, "type");
if(type != null && (type.equals(MimeType.XML_TYPE.getName()) || type.equals(MimeType.XSL_TYPE.getName()) || type.equals(MimeType.XSLT_TYPE.getName()))) {
href = XMLUtil.parseValue(xsl, "href");
if (href == null)
continue;
return href;
}
}
}
return null;
}
/**
* URIResolver is called by the XSL transformer to handle <xsl:include>,
* <xsl:import> ...
*
*@author Wolfgang Meier <meier@ifs.tu-darmstadt.de>
*/
private class InternalURIResolver implements URIResolver {
private String collectionId = null;
public InternalURIResolver(String collection) {
collectionId = collection;
}
public Source resolve(String href, String base) throws TransformerException {
LOG.debug("resolving stylesheet ref " + href);
if (href.indexOf(':') != Constants.STRING_NOT_FOUND)
// href is an URL pointing to an external resource
return null;
///TODO : use dedicated function in XmldbURI
URI baseURI = URI.create(collectionId + "/");
URI uri = URI.create(href);
href = baseURI.resolve(uri).toString();
Serializer serializer = broker.newSerializer();
return new SAXSource(serializer, new InputSource(href));
}
}
/**
* An error listener that just rethrows the exception
*/
private class ErrorListener implements javax.xml.transform.ErrorListener {
public void warning(TransformerException exception) throws TransformerException {
LOG.warn("Warning while applying stylesheet: " + exception.getMessage(), exception);
}
public void error(TransformerException exception) throws TransformerException {
throw exception;
}
public void fatalError(TransformerException exception) throws TransformerException {
throw exception;
}
}
}