Package org.exist.storage.serializers

Source Code of org.exist.storage.serializers.Serializer$ErrorListener

/*
*  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.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.Subject;
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.SequenceIterator;
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");

    // required for XQJ/typed information implementation
    // -----------------------------------------
    protected final static QName ELEM_DOC_QNAME = new QName("document", Namespaces.EXIST_NS, "exist");
    protected final static QName ELEM_ATTR_QNAME = new QName("attribute", Namespaces.EXIST_NS, "exist");
    protected final static QName ELEM_TEXT_QNAME = new QName("text", Namespaces.EXIST_NS, "exist");

    protected final static QName ATTR_URI_QNAME = new QName("uri", Namespaces.EXIST_NS, "exist");
    protected final static QName ATTR_TNS_QNAME = new QName("target-namespace", Namespaces.EXIST_NS, "exist");
    protected final static QName ATTR_LOCAL_QNAME = new QName("local", Namespaces.EXIST_NS, "exist");
    protected final static QName ATTR_PREFIX_QNAME = new QName("prefix", Namespaces.EXIST_NS, "exist");
    protected final static QName ATTR_HAS_ELEMENT_QNAME = new QName("has-element", 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 Subject 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 = "yes".equals(option);
    if ((option =
      (String) config.getProperty(PROPERTY_TAG_MATCHING_ATTRIBUTES))
      != null)
      tagAttributes = "yes".equals(option);
    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(final 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<String, Object> table)
    throws SAXNotRecognizedException, SAXNotSupportedException {
    if(table == null)
      {return;}
    for(final Map.Entry<String, Object> entry : table.entrySet()) {
      setProperty(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 ("element".equals(value))
                {showId = EXIST_ID_ELEMENT;}
            else if ("all".equals(value))
                {showId = EXIST_ID_ALL;}
            else
                {showId = EXIST_ID_NONE;}
        } else {
      outputProperties.put(prop, value);
    }
  }

  public String getProperty(String key, String defaultValue) {
    final String value = outputProperties.getProperty(key, defaultValue);
    return value;
  }
 
  public boolean isStylesheetApplied() {
    return templates != null;
  }
 
  protected int getHighlightingMode() {
    final String option =
      getProperty(EXistOutputKeys.HIGHLIGHT_MATCHES, "elements");
    if ("both".equals(option) || "all".equals(option))
      {return TAG_BOTH;}
    else if ("elements".equals(option))
      {return TAG_ELEMENT_MATCHES;}
    else if ("attributes".equals(option))
      {return TAG_ATTRIBUTE_MATCHES;}
    else
      {return TAG_NONE;}
  }

  /**
   *  If an XSL stylesheet is present, plug it into
   *  the chain.
   */
  protected void applyXSLHandler(Writer writer) {
    final StreamResult result = new StreamResult(writer);
    xslHandler.setResult(result);
    if ("yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "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(Subject user) {
    this.user = user;
  }

  /**
   * Get the current User.
   */
  public Subject 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
    final 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?
      final DocumentImpl doc = broker.getResource(XmldbURI.create(systemId), Permission.READ);
      if (doc == null)
        {throw new SAXException("document " + systemId + " not found in database");}
      else
        {LOG.debug("serializing " + doc.getFileURI());}

      toSAX(doc);
    } catch (final 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 {
    final 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 (final TransformerConfigurationException e) {
                throw new SAXException(e.getMessage(), e);
            }
        }
    if (templates != null)
      {applyXSLHandler(writer);}
    else {
      //looking for serializer properties in <?exist-serialize?>
        final NodeList children = doc.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          final StoredNode node = (StoredNode) children.item(i);
          if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
              && "exist-serialize".equals(node.getNodeName())) {

                  final String params[] = ((ProcessingInstructionImpl)node).getData().split(" ");
                  for(final String param : params) {
                      final String opt[] = Option.parseKeyValuePair(param);
                      if (opt != null)
                        {outputProperties.setProperty(opt[0], opt[1]);}
                  }
          }
        }

      setPrettyPrinter(writer, "no".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")),
                    null, true); //setPrettyPrinter(writer, false);
    }
   
    serializeToReceiver(doc, true);
    releasePrettyPrinter();
  }

  public String serialize(NodeValue n) throws SAXException {
    final StringWriter out = new StringWriter();
    serialize(n,out);
    return out.toString();
  }
 
  public void serialize(NodeValue n, Writer out) throws SAXException {
        try {
            setStylesheetFromProperties(n.getOwnerDocument());
        } catch (final TransformerConfigurationException e) {
            throw new SAXException(e.getMessage(), e);
        }
    if (templates != null)
      {applyXSLHandler(out);}
    else
      setPrettyPrinter(out, "no".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")),
                    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 {
    final StringWriter out = new StringWriter();
    serialize(p,out);
    return out.toString();
  }
 
  public void serialize(NodeProxy p, Writer out) throws SAXException {
        try {
            setStylesheetFromProperties(p.getOwnerDocument());
        } catch (final TransformerConfigurationException e) {
            throw new SAXException(e.getMessage(), e);
        }
    if (templates != null)
      {applyXSLHandler(out);}
    else
      setPrettyPrinter(out, "no".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")),
                    p, false); //setPrettyPrinter(out, false);
    serializeToReceiver(p, false);
    releasePrettyPrinter();
  }

  public void prepareStylesheets(DocumentImpl doc) throws TransformerConfigurationException {
    if ("yes".equals(outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no"))) {
      final 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 ("yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
      xinclude.setReceiver(toSAX);
      receiver = xinclude;
    } else
      {receiver = toSAX;}
  }

    public void setReceiver(Receiver receiver) {
        this.receiver = receiver;
    }

    public void setReceiver(Receiver receiver, boolean handleIncludes) {
        if (handleIncludes && "yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
      xinclude.setReceiver(receiver);
      this.receiver = xinclude;
    } else
      {this.receiver = receiver;}
    }

    public XIncludeFilter getXIncludeFilter() {
        return xinclude;
    }
   
  /* (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 ("yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes"))) {
      xinclude.setReceiver(xmlout);
      receiver = xinclude;
    } else
      {receiver = xmlout;}
        if (root != null && getHighlightingMode() != TAG_NONE) {
            final IndexController controller = broker.getIndexController();
            MatchListener listener = controller.getMatchListener(root);
            if (listener != null) {
                final 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) {
        final Receiver oldReceiver = receiver;
        if (getHighlightingMode() != TAG_NONE) {
            final IndexController controller = broker.getIndexController();
            MatchListener listener = controller.getMatchListener(p);
            if (listener != null) {
                final 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;}
    final 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(final Enumeration<?> e = outputProperties.propertyNames(); e.hasMoreElements(); ) {
      String property = (String)e.nextElement();
      if(property.startsWith(EXistOutputKeys.STYLESHEET_PARAM)) {
        final 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;
    }
    final 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 (final URISyntaxException e) {
            //could be an external URI!
            try {
                externalUri = new URI(stylesheet);
            } catch (final URISyntaxException ee) {
                throw new IllegalArgumentException("Stylesheet URI could not be parsed: "+ee.getMessage());
            }
        }
        // does stylesheet point to an external resource?
        if (externalUri!=null) {
            final 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 = broker.getResource(stylesheetUri, Permission.READ);
            } catch (final PermissionDeniedException e) {
                throw new TransformerConfigurationException("permission denied to read " + stylesheetUri);
            }
            if (xsl == null) {
                throw new TransformerConfigurationException("stylesheet not found: " + 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());
            final TemplatesHandler handler = factory.newTemplatesHandler();
            receiver = new ReceiverToSAX(handler);
            try {
                this.serializeToReceiver(xsl, true);
                templates = handler.getTemplates();
            } catch (final 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) {
      final SAXResult result = new SAXResult();
      boolean processXInclude =
        "yes".equals(getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "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) {
            final IndexController controller = broker.getIndexController();
            MatchListener listener = controller.getMatchListener(root);
            if (listener != null) {
                final 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 ("yes".equals(outputProperties.getProperty(EXistOutputKeys.PROCESS_XSL_PI, "no"))) {
      final String stylesheet = hasXSLPi(doc);
      if (stylesheet != null)
                try {
                    setStylesheet(doc, stylesheet);
                } catch (final TransformerConfigurationException e) {
                    throw new SAXException(e.getMessage(), e);
                }
        }
        try {
            setStylesheetFromProperties(doc);
        } catch (final TransformerConfigurationException e) {
            throw new SAXException(e.getMessage(), e);
        }
        setXSLHandler(null, true);
    serializeToReceiver(
      doc,
      "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
  }

  public void toSAX(NodeValue n) throws SAXException {
        try {
            setStylesheetFromProperties(n.getOwnerDocument());
        } catch (final TransformerConfigurationException e) {
            throw new SAXException(e.getMessage(), e);
        }
        setXSLHandler(n.getImplementationType() == NodeValue.PERSISTENT_NODE ? (NodeProxy)n : null, false);
    serializeToReceiver(
        n,
        "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
  }
 
  public void toSAX(NodeProxy p) throws SAXException {
      try {
    setStylesheetFromProperties(p.getOwnerDocument());
      } catch (final TransformerConfigurationException e) {
    throw new SAXException(e.getMessage(), e);
      }
      setXSLHandler(p, false);
      if (p.getNodeId() == NodeId.DOCUMENT_NODE) {
    serializeToReceiver(p.getDocument(), "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
      } else {
    serializeToReceiver(p, "true".equals(getProperty(GENERATE_DOC_EVENTS, "false")));
      }
  }

  /**
   * 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:
   *
   * &lt;exist:result hits="sequence length" start="value of start" count="value of count">
   *
   * @param seq
   * @param start
   * @param count
   * @param wrap Indicates whether the output should be wrapped
         * @param typed Indicates whether the output types should be wrapped
   * @throws SAXException
   */
  public void toSAX(Sequence seq, int start, int count, boolean wrap, boolean typed) throws SAXException {
        try {
            setStylesheetFromProperties(null);
        } catch (final TransformerConfigurationException e) {
            throw new SAXException(e.getMessage(), e);
        }
        setXSLHandler(null, false);
    final 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;
                        }
                       
      itemToSAX(item, typed, wrap, attrs);
    }
   
    if(wrap) {
      receiver.endElement(ELEM_RESULT_QNAME);
      receiver.endPrefixMapping("exist");
    }
    receiver.endDocument();
  }
       
        /**
   * 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:
   *
   * &lt;exist:result hits="sequence length" start="value of start" count="value of count">
   *
   */
    public void toSAX(Sequence seq) throws SAXException {
        try {
            setStylesheetFromProperties(null);
        } catch (final TransformerConfigurationException e) {
            throw new SAXException(e.getMessage(), e);
        }
       
        setXSLHandler(null, false);

        receiver.startDocument();

        try {
            Item item;
            final SequenceIterator itSeq = seq.iterate();
            while(itSeq.hasNext()) {
                item = itSeq.nextItem();
                itemToSAX(item, false, false, null);
            }
        } catch(final XPathException xpe) {
            throw new SAXException(xpe.getMessage(), xpe);
        }

        receiver.endDocument();
    }
       
        /**
   * 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:
   *
   * &lt;exist:result hits="sequence length" start="value of start" count="value of count">
   *
   * @param wrap Indicates whether the output should be wrapped
         * @param typed Indicates whether the output types should be wrapped
   * @throws SAXException
   */
  public void toSAX(Item item, boolean wrap, boolean typed) throws SAXException {
            try {
                setStylesheetFromProperties(null);
            } catch (final TransformerConfigurationException e) {
                throw new SAXException(e.getMessage(), e);
            }
           
            setXSLHandler(null, false);
            final AttrList attrs = new AttrList();
            attrs.addAttribute(ATTR_HITS_QNAME, "1");
            attrs.addAttribute(ATTR_START_QNAME, "1");
            attrs.addAttribute(ATTR_COUNT_QNAME, "1");
            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);
            }
   
                       
            itemToSAX(item, typed, wrap, attrs);
   
            if(wrap) {
                receiver.endElement(ELEM_RESULT_QNAME);
                receiver.endPrefixMapping("exist");
            }
           
            receiver.endDocument();
  }
       
        private void itemToSAX(Item item, boolean typed, boolean wrap, AttrList attrs) throws SAXException {
           

            if(Type.subTypeOf(item.getType(), Type. NODE)) {
                final NodeValue node = (NodeValue) item;

                if(typed) {
                    //TODO the typed and wrapped stuff should ideally be replaced
                    //with Marshaller.marshallItem
                    //unfortrunately calling Marshaller.marshallItem(broker, item, new SAXToReceiver(receiver))
                    //results in a stack overflow
                    //TODO consider a full XDM serializer in place of this for these special needs

                    serializeTypePreNode(node);
                    if(node.getType() == Type.ATTRIBUTE) {
                        serializeTypeAttributeValue(node);
                    } else {
                        serializeToReceiver(node, false);
                    }
                    serializeTypePostNode(node);
                } else {
                    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 (final XPathException e) {
                        throw new SAXException(e.getMessage(), e);
                }
                if(wrap) {
                        receiver.endElement(ELEM_VALUE_QNAME);
                }
            }
        }

    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;}
       
    final 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
        && "xml-stylesheet".equals(((ProcessingInstruction)node).getTarget())) {
        // 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;
  }


    /**
     * Quick code fix for the remote XQJ API implementation.
     *
     * attribute name { "value" } ---> goes through fine.
     *
     * fn:doc($expr)/element()/attribute() ---> fails, as this is
     * contained within the Database (not an in memory attribute).
     *
     * @param item a NodeValue
     * @throws SAXException
     * @author Charles Foster
     */
    protected void serializeTypeAttributeValue(NodeValue item) throws SAXException {
        try {
            receiver.characters(item.getStringValue());
        } catch (final XPathException e) {
            LOG.error("XPath error trying to retrieve attribute value. " + e.getMessage(), e);
        }
    }

    /**
     * Writes a start element for DOCUMENT, ATTRIBUTE and TEXT nodes.
     * This is required for the XQJ API implementation.
     *
     * @param item a NodeValue which will be wrapped in a element.
     * @throws SAXException
     * @author Charles Foster
     */
    protected void serializeTypePreNode(NodeValue item) throws SAXException {
        AttrList attrs = null;

        switch(item.getType()) {
            case Type.DOCUMENT:

                final String baseUri = item.getOwnerDocument().getBaseURI();

                attrs = new AttrList();
                if(baseUri != null && baseUri.length() > 0){
                    attrs.addAttribute(ATTR_URI_QNAME, baseUri);
                }
                if(item.getOwnerDocument().getDocumentElement() == null) {
                    attrs.addAttribute(ATTR_HAS_ELEMENT_QNAME, "false");
                }

                receiver.startElement(ELEM_DOC_QNAME, attrs);
                break;

            case Type.ATTRIBUTE:
                attrs = new AttrList();

                String attributeValue;
                if((attributeValue = item.getNode().getLocalName()) != null && attributeValue.length() > 0){
                    attrs.addAttribute(ATTR_LOCAL_QNAME, attributeValue);
                }
                if((attributeValue = item.getNode().getNamespaceURI()) != null && attributeValue.length() > 0) {
                    attrs.addAttribute(ATTR_TNS_QNAME, attributeValue);
                }
                if((attributeValue = item.getNode().getPrefix()) != null && attributeValue.length() > 0) {
                    attrs.addAttribute(ATTR_PREFIX_QNAME, attributeValue);
                }

                receiver.startElement(ELEM_ATTR_QNAME, attrs);
                break;

            case Type.TEXT:
                receiver.startElement(ELEM_TEXT_QNAME, null);
                break;

            default:
        }
    }

    /**
     * Writes an end element for DOCUMENT, ATTRIBUTE and TEXT nodes.
     * This is required for the XQJ API implementation.
     *
     * @param item the item which will be wrapped in an element.
     * @throws SAXException
     * @author Charles Foster
     */
    protected void serializeTypePostNode(NodeValue item) throws SAXException {
        switch(item.getType()) {
            case Type.DOCUMENT:
                receiver.endElement(ELEM_DOC_QNAME);
                break;
            case Type.ATTRIBUTE:
                receiver.endElement(ELEM_ATTR_QNAME);
                break;
            case Type.TEXT:
                receiver.endElement(ELEM_TEXT_QNAME);
                break;
            default:
        }
    }


  /**
   *  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
      final URI baseURI = URI.create(collectionId + "/");
      final URI uri = URI.create(href);
      href = baseURI.resolve(uri).toString();
      final 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;
        }
    }
}
TOP

Related Classes of org.exist.storage.serializers.Serializer$ErrorListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.