/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, Lotus
 * Development Corporation., http://www.lotus.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.xalan.transformer;

import java.util.Enumeration;

import org.apache.xalan.templates.Stylesheet;
import org.apache.xalan.templates.StylesheetRoot;
import org.apache.xalan.templates.ElemTemplate;
import org.apache.xalan.templates.ElemTemplateElement;
import org.apache.xalan.trace.TraceManager;
import org.apache.xalan.trace.GenerateEvent;
import org.apache.xml.utils.MutableAttrListImpl;
import org.apache.xml.utils.QName;
import org.apache.xml.utils.TreeWalker;
import org.apache.xml.utils.ObjectPool;
import org.apache.xml.utils.XMLCharacterRecognizer;
import org.apache.xpath.objects.XObject;
import org.apache.xpath.XPathContext;
import org.apache.xml.dtm.DTM;
import org.apache.xml.dtm.DTMIterator;
import org.apache.xml.dtm.DTMFilter;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;
import org.xml.sax.ErrorHandler;

import org.xml.sax.helpers.NamespaceSupport;
import org.apache.xml.utils.NamespaceSupport2;
import org.xml.sax.Locator;

import javax.xml.transform.TransformerException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Transformer;

/**
 * This class is a layer between the direct calls to the result
 * tree content handler, and the transformer.  For one thing,
 * we have to delay the call to
 * getContentHandler().startElement(name, atts) because of the
 * xsl:attribute and xsl:copy calls.  In other words,
 * the attributes have to be fully collected before you
 * can call startElement.
 */
public class ResultTreeHandler extends QueuedEvents
        implements ContentHandler, LexicalHandler, TransformState,
        org.apache.xml.dtm.ref.dom2dtm.DOM2DTM.CharacterNodeHandler,
        ErrorHandler
{

  /** Indicate whether running in Debug mode */
  private static final boolean DEBUG = false;

  /**
   * Null constructor for object pooling.
   */
  public ResultTreeHandler(){}

  /**
   * Create a new result tree handler.  The real content
   * handler will be the ContentHandler passed as an argument.
   *
   * @param transformer non-null transformer instance
   * @param realHandler Content Handler instance
   */
  public ResultTreeHandler(TransformerImpl transformer,
                           ContentHandler realHandler)
  {
    init(transformer, realHandler);
  }

  /**
   * Initializer method.
   *
   * @param transformer non-null transformer instance
   * @param realHandler Content Handler instance
   */
  public void init(TransformerImpl transformer, ContentHandler realHandler)
  {

    m_transformer = transformer;

    // m_startDoc.setTransformer(m_transformer);
    TraceManager tracer = transformer.getTraceManager();

    if ((null != tracer) && tracer.hasTraceListeners())
      m_tracer = tracer;
    else
      m_tracer = null;

    // m_startDoc.setTraceManager(m_tracer);
    m_contentHandler = realHandler;

    // m_startDoc.setContentHandler(m_contentHandler);
    if (m_contentHandler instanceof LexicalHandler)
      m_lexicalHandler = (LexicalHandler) m_contentHandler;
    else
      m_lexicalHandler = null;

    m_isTransformClient = (m_contentHandler instanceof TransformerClient);

    m_cloner = new ClonerToResultTree(transformer, this);

    // The stylesheet is set at a rather late stage, so I do 
    // this here, though it would probably be better done elsewhere.
    if (null != m_transformer)
      m_stylesheetRoot = m_transformer.getStylesheet();

    pushDocumentEvent();  // not pending yet.
  }

  /**
   * Bottleneck the startDocument event.
   *
   * @throws org.xml.sax.SAXException
   */
  public void startDocument() throws org.xml.sax.SAXException{}

  /**
   * Bottleneck the endDocument event.  This may be called
   * more than once in order to make sure the pending start
   * document is called.
   *
   * @throws org.xml.sax.SAXException
   */
  public void endDocument() throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (!m_docEnded)
    {
      m_contentHandler.endDocument();

      if (null != m_tracer)
      {
        GenerateEvent ge =
          new GenerateEvent(m_transformer,
                            GenerateEvent.EVENTTYPE_ENDDOCUMENT, null);

        m_tracer.fireGenerateEvent(ge);
      }

      m_docEnded = true;
      m_docPending = false;
    }
  }

  /**
   * Bottleneck the startElement event.  This is used to "pend" an
   * element, so that attributes can still be added to it before
   * the real "startElement" is called on the result tree listener.
   *
   * @param ns Namespace URI of element
   * @param localName Local part of qname of element
   * @param name Name of element
   * @param atts List of attributes for the element
   *
   * @throws org.xml.sax.SAXException
   */
  public void startElement(
          String ns, String localName, String name, Attributes atts)
            throws org.xml.sax.SAXException
  {

    if (DEBUG)
    {
      if (m_elemIsPending)
        System.out.println("(ResultTreeHandler#startElement - pended: "
                           + m_url + "#" + m_localName);

      System.out.println("ResultTreeHandler#startElement: " + ns + "#"
                         + localName);

      //      if(null == ns)
      //      {
      //        (new RuntimeException(localName+" has a null namespace!")).printStackTrace();
      //      }
    }

    if(m_docPending)
      checkForSerializerSwitch(ns, localName);
      
    flushPending(true);

    if (!m_nsContextPushed)
    {
      if (DEBUG)
        System.out.println(
          "ResultTreeHandler#startElement - push(startElement)");

      m_nsSupport.pushContext();

      m_nsContextPushed = true;
    }
    
    if (ns != null)
      ensurePrefixIsDeclared(ns, name);

    m_name = name;
    m_url = ns;
    m_localName = localName;

    if (null != atts)
      m_attributes.addAttributes(atts);

    m_elemIsPending = true;
    m_elemIsEnded = false;
    
    if(m_isTransformClient && (null != m_transformer))
    {
      m_snapshot.m_currentElement = m_transformer.getCurrentElement();
      m_snapshot.m_currentTemplate = m_transformer.getCurrentTemplate();
      m_snapshot.m_matchedTemplate = m_transformer.getMatchedTemplate();
      int currentNodeHandle = m_transformer.getCurrentNode();
      DTM dtm = m_transformer.getXPathContext().getDTM(currentNodeHandle);
      m_snapshot.m_currentNode = dtm.getNode(currentNodeHandle);
      m_snapshot.m_matchedNode = m_transformer.getMatchedNode();
      m_snapshot.m_contextNodeList = m_transformer.getContextNodeList(); // TODO: Need to clone
    }
    // initQSE(m_startElement);

    m_eventCount++;
  }

  /**
   * Bottleneck the endElement event.
   *
   * @param ns Namespace URI of element
   * @param localName Local part of qname of element
   * @param name Name of element
   *
   * @throws org.xml.sax.SAXException
   */
  public void endElement(String ns, String localName, String name)
          throws org.xml.sax.SAXException
  {

    if (DEBUG)
    {
      if (m_elemIsPending)
        System.out.println("(ResultTreeHandler#endElement - pended: "
                           + m_url + "#" + m_localName);

      System.out.println("ResultTreeHandler#endElement: " + ns + "#"
                         + localName);
    }

    flushPending(true);
    m_contentHandler.endElement(ns, localName, name);

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_ENDELEMENT,
                                           name, (Attributes)null);

      m_tracer.fireGenerateEvent(ge);
    }

    sendEndPrefixMappings();
    popEvent();

    if (DEBUG)
      System.out.println("ResultTreeHandler#startElement pop: " + localName);

    m_nsSupport.popContext();
  }

  /** Indicate whether a namespace context was pushed */
  boolean m_nsContextPushed = false;

  /**
   * Begin the scope of a prefix-URI Namespace mapping.
   *
   * <p>The information from this event is not necessary for
   * normal Namespace processing: the SAX XML reader will
   * automatically replace prefixes for element and attribute
   * names when the http://xml.org/sax/features/namespaces
   * feature is true (the default).</p>
   *
   * <p>There are cases, however, when applications need to
   * use prefixes in character data or in attribute values,
   * where they cannot safely be expanded automatically; the
   * start/endPrefixMapping event supplies the information
   * to the application to expand prefixes in those contexts
   * itself, if necessary.</p>
   *
   * <p>Note that start/endPrefixMapping events are not
   * guaranteed to be properly nested relative to each-other:
   * all startPrefixMapping events will occur before the
   * corresponding startElement event, and all endPrefixMapping
   * events will occur after the corresponding endElement event,
   * but their order is not guaranteed.</p>
   *
   * @param prefix The Namespace prefix being declared.
   * @param uri The Namespace URI the prefix is mapped to.
   * @throws org.xml.sax.SAXException The client may throw
   *            an exception during processing.
   * @see #endPrefixMapping
   * @see #startElement
   */
  public void startPrefixMapping(String prefix, String uri)
          throws org.xml.sax.SAXException
  {
    startPrefixMapping(prefix, uri, true);
  }

  /**
   * Begin the scope of a prefix-URI Namespace mapping.
   *
   *
   * @param prefix The Namespace prefix being declared.
   * @param uri The Namespace URI the prefix is mapped to.
   * @param shouldFlush Indicate whether pending events needs
   * to be flushed first
   *
   * @throws org.xml.sax.SAXException The client may throw
   *            an exception during processing.
   */
  public void startPrefixMapping(
          String prefix, String uri, boolean shouldFlush)
            throws org.xml.sax.SAXException
  {

    if (shouldFlush)
      flushPending(false);

    if (!m_nsContextPushed)
    {
      if (DEBUG)
        System.out.println(
          "ResultTreeHandler#startPrefixMapping push(startPrefixMapping: "
          + prefix + ")");

      m_nsSupport.pushContext();

      m_nsContextPushed = true;
    }

    if (null == prefix)
      prefix = "";  // bit-o-hack, that that's OK

    String existingURI = m_nsSupport.getURI(prefix);

    if (null == existingURI)
      existingURI = "";

    if (null == uri)
      uri = "";

    if (!existingURI.equals(uri))
    {
      if (DEBUG)
      {
        System.out.println("ResultTreeHandler#startPrefixMapping Prefix: "
                           + prefix);
        System.out.println("ResultTreeHandler#startPrefixMapping uri: "
                           + uri);
      }

      m_nsSupport.declarePrefix(prefix, uri);
    }
  }

  /**
   * End the scope of a prefix-URI mapping.
   *
   * <p>See startPrefixMapping for details.  This event will
   * always occur after the corresponding endElement event,
   * but the order of endPrefixMapping events is not otherwise
   * guaranteed.</p>
   *
   * @param prefix The prefix that was being mapping.
   * @throws org.xml.sax.SAXException The client may throw
   *            an exception during processing.
   * @see #startPrefixMapping
   * @see #endElement
   */
  public void endPrefixMapping(String prefix)
          throws org.xml.sax.SAXException{}

  /**
   * Bottleneck the characters event.
   *
   * @param ch Array of characters to process
   * @param start start of characters in the array
   * @param length Number of characters in the array
   *
   * @throws org.xml.sax.SAXException
   */
  public void characters(char ch[], int start, int length)
          throws org.xml.sax.SAXException
  {

    // It would be nice to suppress all whitespace before the
    // first element, but this is going to cause potential problems with 
    // text serialization and with text entities (right term?).
    // So this really needs to be done at the serializer level.

    /*if (m_startDoc.isPending
    && XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
    return;*/
    if (DEBUG)
    {
      System.out.print("ResultTreeHandler#characters: ");

      int n = start + length;

      for (int i = start; i < n; i++)
      {
        if (Character.isWhitespace(ch[i]))
          System.out.print("\\" + ((int) ch[i]));
        else
          System.out.print(ch[i]);
      }

      System.out.println("");
    }

    flushPending(true);
    m_contentHandler.characters(ch, start, length);

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_CHARACTERS,
                                           ch, start, length);

      m_tracer.fireGenerateEvent(ge);
    }
  }
  
  public void characters(org.w3c.dom.Node node)
          throws org.xml.sax.SAXException
  {

    flushPending(true);
    
    if(m_isTransformClient)
      m_snapshot.m_currentNode = node;

    String data = node.getNodeValue();
    char [] ch = null;
    int length = 0;
    if (data != null)
    {
    ch = data.toCharArray();
    length = data.length();
    m_contentHandler.characters(ch, 0, length);
    }
    
    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_CHARACTERS,
                                           ch, 0, length);

      m_tracer.fireGenerateEvent(ge);
    }  
    if(m_isTransformClient)
      m_snapshot.m_currentNode = null;
  }

  /**
   * Bottleneck the ignorableWhitespace event.
   *
   * @param ch Array of characters to process
   * @param start start of characters in the array
   * @param length Number of characters in the array
   *
   * @throws org.xml.sax.SAXException
   */
  public void ignorableWhitespace(char ch[], int start, int length)
          throws org.xml.sax.SAXException
  {

    if (m_docPending
            && XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
      return;

    flushPending(true);
    m_contentHandler.ignorableWhitespace(ch, start, length);

    if (null != m_tracer)
    {
      GenerateEvent ge =
        new GenerateEvent(m_transformer,
                          GenerateEvent.EVENTTYPE_IGNORABLEWHITESPACE, ch,
                          start, length);

      m_tracer.fireGenerateEvent(ge);
    }
  }

  /**
   * Bottleneck the processingInstruction event.
   *
   * @param target Processing instruction target name
   * @param data Processing instruction data
   *
   * @throws org.xml.sax.SAXException
   */
  public void processingInstruction(String target, String data)
          throws org.xml.sax.SAXException
  {

    flushPending(true);
    m_contentHandler.processingInstruction(target, data);

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_PI,
                                           target, data);

      m_tracer.fireGenerateEvent(ge);
    }
  }

  /**
   * Bottleneck the comment event.
   *
   * @param data Comment data
   *
   * @throws org.xml.sax.SAXException
   */
  public void comment(String data) throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.comment(data.toCharArray(), 0, data.length());
    }

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_COMMENT,
                                           data);

      m_tracer.fireGenerateEvent(ge);
    }
  }

  /**
   * Bottleneck the comment event.
   *
   * @param ch Character array with comment data
   * @param start start of characters in the array
   * @param length number of characters in the array
   *
   * @throws org.xml.sax.SAXException
   */
  public void comment(char ch[], int start, int length)
          throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.comment(ch, start, length);
    }

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_COMMENT,
                                           new String(ch, start, length));

      m_tracer.fireGenerateEvent(ge);
    }
  }

  /**
   * Entity reference event.
   *
   * @param name Name of entity
   *
   * @throws org.xml.sax.SAXException
   */
  public void entityReference(String name) throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.startEntity(name);
      m_lexicalHandler.endEntity(name);
    }

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_ENTITYREF,
                                           name);

      m_tracer.fireGenerateEvent(ge);
    }
  }

  /**
   * Start an entity.
   *
   * @param name Name of the entity
   *
   * @throws org.xml.sax.SAXException
   */
  public void startEntity(String name) throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.startEntity(name);
    }
  }

  /**
   * End an entity.
   *
   * @param name Name of the entity
   *
   * @throws org.xml.sax.SAXException
   */
  public void endEntity(String name) throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.endEntity(name);
    }

    if (null != m_tracer)
    {
      GenerateEvent ge = new GenerateEvent(m_transformer,
                                           GenerateEvent.EVENTTYPE_ENTITYREF,
                                           name);

      m_tracer.fireGenerateEvent(ge);
    }
  }

  /**
   * Start the DTD.
   *
   * @param s1 The document type name.
   * @param s2 The declared public identifier for the
   *        external DTD subset, or null if none was declared.
   * @param s3 The declared system identifier for the
   *        external DTD subset, or null if none was declared.
   *
   * @throws org.xml.sax.SAXException
   */
  public void startDTD(String s1, String s2, String s3)
          throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.startDTD(s1, s2, s3);
    }
  }

  /**
   * End the DTD.
   *
   * @throws org.xml.sax.SAXException
   */
  public void endDTD() throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.endDTD();
    }
  }

  /**
   * Start the CDATACharacters.
   *
   * @throws org.xml.sax.SAXException
   */
  public void startCDATA() throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.startCDATA();
    }
  }

  /**
   * End the CDATA characters.
   *
   * @throws org.xml.sax.SAXException
   */
  public void endCDATA() throws org.xml.sax.SAXException
  {

    flushPending(true);

    if (null != m_lexicalHandler)
    {
      m_lexicalHandler.endCDATA();
    }
  }

  /**
   * Receive notification of a skipped entity.
   *
   * <p>The Parser will invoke this method once for each entity
   * skipped.  Non-validating processors may skip entities if they
   * have not seen the declarations (because, for example, the
   * entity was declared in an external DTD subset).  All processors
   * may skip external entities, depending on the values of the
   * http://xml.org/sax/features/external-general-entities and the
   * http://xml.org/sax/features/external-parameter-entities
   * properties.</p>
   *
   * @param name The name of the skipped entity.  If it is a
   *        parameter entity, the name will begin with '%'.
   * @throws org.xml.sax.SAXException Any SAX exception, possibly
   *            wrapping another exception.
   */
  public void skippedEntity(String name) throws org.xml.sax.SAXException{}

  /**
   * Set whether Namespace declarations have been added to
   * this element
   *
   *
   * @param b Flag indicating whether Namespace declarations
   * have been added to this element
   */
  public void setNSDeclsHaveBeenAdded(boolean b)
  {

    m_nsDeclsHaveBeenAdded = b;
  }

  /**
   * Flush the event.
   *
   * @throws TransformerException
   *
   * @throws org.xml.sax.SAXException
   */
  void flushDocEvent() throws org.xml.sax.SAXException
  {

    if (m_docPending)
    {
      m_contentHandler.startDocument();

      if (null != m_tracer)
      {
        GenerateEvent ge =
          new GenerateEvent(m_transformer,
                            GenerateEvent.EVENTTYPE_STARTDOCUMENT);

        m_tracer.fireGenerateEvent(ge);
      }

      if (m_contentHandler instanceof TransformerClient)
      {
        ((TransformerClient) m_contentHandler).setTransformState(this);
      }

      m_docPending = false;
    }
  }
  
  /**
   * Flush the event.
   *
   * @throws SAXException
   */
  void flushElem() throws org.xml.sax.SAXException
  {

    if (m_elemIsPending)
    {
      if (null != m_name)
      {
        m_contentHandler.startElement(m_url, m_localName, m_name,
                                      m_attributes);
        
        if(null != m_tracer)
        {
          GenerateEvent ge =
            new GenerateEvent(m_transformer,
                              GenerateEvent.EVENTTYPE_STARTELEMENT, m_name,
                                      m_attributes);
  
          m_tracer.fireGenerateEvent(ge);
        }
        if(m_isTransformClient)
          m_snapshot.m_currentNode = null;
      }

      m_elemIsPending = false;
      m_attributes.clear();
  
      m_nsDeclsHaveBeenAdded = false;
      m_name = null;
      m_url = null;
      m_localName = null;
      m_namespaces = null;

      // super.flush();
    }
  }


  /**
   * Flush the pending element.
   *
   * @throws org.xml.sax.SAXException
   */
  public final void flushPending() throws org.xml.sax.SAXException
  {
    flushPending(true);
  }

  /**
   * Flush the pending element.
   *
   * @param type Event type
   *
   * NEEDSDOC @param flushPrefixes
   *
   * @throws org.xml.sax.SAXException
   */
  public final void flushPending(boolean flushPrefixes)
          throws org.xml.sax.SAXException
  {

    if (flushPrefixes && m_docPending)
    {
      flushDocEvent();
    }

    if (m_elemIsPending)
    {
      // Combined loop shoud be much more efficient.
      // %REVIEW% %OPT% Will the "else" case ever arise?
      if (!m_nsDeclsHaveBeenAdded)
//        addNSDeclsToAttrs();
          startAndAddPrefixMappings();  // new
      else                              // new
          sendStartPrefixMappings();


      if (DEBUG)
      {
        System.out.println("ResultTreeHandler#flushPending - start flush: "
                           + m_name);
      }

      flushElem();

      if (DEBUG)
      {
        System.out.println(
          "ResultTreeHandler#flushPending - after flush, isPending: "
          + m_elemIsPending);
      }

      m_nsContextPushed = false;
    }
  }

  /**
   * Given a result tree fragment, walk the tree and
   * output it to the result stream.
   *
   * @param obj Result tree fragment object
   * @param support XPath context for the result tree fragment
   *
   * @throws org.xml.sax.SAXException
   */
  public void outputResultTreeFragment(XObject obj, XPathContext support)
          throws org.xml.sax.SAXException
  {

    int doc = obj.rtf();
    DTM dtm = support.getDTM(doc);

    for (int n = dtm.getFirstChild(doc); DTM.NULL != n;
            n = dtm.getNextSibling(n))
    {
      flushPending(true);  // I think.
      dtm.dispatchToEvents(n, this);
    }
  }

  /**
   * To fullfill the FormatterListener interface... no action
   * for the moment.
   *
   * @param locator Document locator
   */
  public void setDocumentLocator(Locator locator){}

  /**
   * This function checks to make sure a given prefix is really
   * declared.  It might not be, because it may be an excluded prefix.
   * If it's not, it still needs to be declared at this point.
   * TODO: This needs to be done at an earlier stage in the game... -sb
   *
   * @param ns Namespace URI of the element
   * @param rawName Raw name of element (with prefix)
   *
   * @throws org.xml.sax.SAXException
   */
  public void ensurePrefixIsDeclared(String ns, String rawName)
          throws org.xml.sax.SAXException
  {

    if (ns != null && ns.length() > 0)
    {
      int index;
      String prefix = (index = rawName.indexOf(":")) < 0
                      ? "" : rawName.substring(0, index);

      if (null != prefix)
      {
        String foundURI = m_nsSupport.getURI(prefix);

        if ((null == foundURI) ||!foundURI.equals(ns))
        {
          startPrefixMapping(prefix, ns, false);
        }
      }
    }
  }

  /**
   * This function checks to make sure a given prefix is really
   * declared.  It might not be, because it may be an excluded prefix.
   * If it's not, it still needs to be declared at this point.
   * TODO: This needs to be done at an earlier stage in the game... -sb
   *
   * @param ns Namespace URI of the element
   * @param rawName Raw name of element (with prefix)
   *
   * NEEDSDOC @param dtm
   * NEEDSDOC @param namespace
   *
   * @throws org.xml.sax.SAXException
   */
  public void ensureNamespaceDeclDeclared(DTM dtm, int namespace)
          throws org.xml.sax.SAXException
  {

    String uri = dtm.getNodeValue(namespace);
    String prefix = dtm.getNodeNameX(namespace);

    if ((uri != null && uri.length() > 0) && (null != prefix))
    {
      String foundURI = m_nsSupport.getURI(prefix);

      if ((null == foundURI) ||!foundURI.equals(uri))
      {
        startPrefixMapping(prefix, uri, false);
      }
    }
  }

  /**
   * Add the attributes that have been declared to the attribute list.
   * (Seems like I shouldn't have to do this...)
   * Internally deprecated in favor of combined startAndAddPrefixMappings();
   * 
   *
   * @throws org.xml.sax.SAXException
   */
  protected void sendStartPrefixMappings() throws org.xml.sax.SAXException
  {
    Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
    ContentHandler handler = m_contentHandler;
    while (prefixes.hasMoreElements())
    {
      String prefix = (String) prefixes.nextElement();
      handler.startPrefixMapping(prefix, m_nsSupport.getURI(prefix));
    }
  }

  /**
   * JJK: Combination of sendStartPrefixMappings and
   * addNSDeclsToAttrs() (which it mostly replaces).  Merging the two
   * loops is significantly more efficient.
   *
   * @throws org.xml.sax.SAXException */
  protected void startAndAddPrefixMappings() throws org.xml.sax.SAXException
  {

    Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
    ContentHandler handler = m_contentHandler;

    while (prefixes.hasMoreElements())
    {
      String prefix = (String) prefixes.nextElement();
      String uri=m_nsSupport.getURI(prefix);
      
      // Send event
      handler.startPrefixMapping(prefix, uri);

      // Set attribute
      boolean isDefault = (prefix.length() == 0);
      String name;

      if (isDefault)
      {
        //prefix = "xml";
        name = "xmlns";
      }
      else
        name = "xmlns:" + prefix;

      if (null == uri)
        uri = "";

      m_attributes.addAttribute("http://www.w3.org/2000/xmlns/", 
                                prefix, name, "CDATA", uri);
    }
    m_nsDeclsHaveBeenAdded=true;
  }

  /**
   * Add the attributes that have been declared to the attribute list.
   * (Seems like I shouldn't have to do this...)
   *
   * @throws org.xml.sax.SAXException
   */
  protected void sendEndPrefixMappings() throws org.xml.sax.SAXException
  {

    Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
    ContentHandler handler = m_contentHandler;

    while (prefixes.hasMoreElements())
    {
      String prefix = (String) prefixes.nextElement();

      handler.endPrefixMapping(prefix);
    }
  }

  /**
   * Check to see if we should switch serializers based on the
   * first output element being an HTML element.
   *
   * @param ns Namespace URI of the element
   * @param localName Local part of name of the element
   *
   * @throws org.xml.sax.SAXException
   */
  private void checkForSerializerSwitch(String ns, String localName)
          throws org.xml.sax.SAXException
  {

    try
    {
      if (m_docPending)
      {
        SerializerSwitcher.switchSerializerIfHTML(m_transformer, ns,
                                                  localName);
      }
    }
    catch (TransformerException te)
    {
      throw new org.xml.sax.SAXException(te);
    }
  }

  /**
   * Add the attributes that have been declared to the attribute list.
   *
   * %REVIEW% This should have been done automatically during
   * flushPending(boolean); is it ever explicitly reinvoked?
   */
  public void addNSDeclsToAttrs()
  {

    Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();

    while (prefixes.hasMoreElements())
    {
      String prefix = (String) prefixes.nextElement();
      boolean isDefault = (prefix.length() == 0);
      String name;

      if (isDefault)
      {

        //prefix = "xml";
        name = "xmlns";
      }
      else
        name = "xmlns:" + prefix;

      String uri = m_nsSupport.getURI(prefix);

      if (null == uri)
        uri = "";

      m_attributes.addAttribute("http://www.w3.org/2000/xmlns/", 
                                prefix, name, "CDATA", uri);
    }

    m_nsDeclsHaveBeenAdded = true;
  }

  /**
   * Copy <KBD>xmlns:</KBD> attributes in if not already in scope.
   *
   * @param src Source Node
   * NEEDSDOC @param type
   * NEEDSDOC @param dtm
   *
   * @throws TransformerException
   */
  public void processNSDecls(int src, int type, DTM dtm)
          throws TransformerException
  {

    try
    {
      if (type == DTM.ELEMENT_NODE)
      {
        for (int namespace = dtm.getFirstNamespaceNode(src, true);
                DTM.NULL != namespace;
                namespace = dtm.getNextNamespaceNode(src, namespace, true))
        {

          // String prefix = dtm.getPrefix(namespace);
          String prefix = dtm.getNodeNameX(namespace);
          String desturi = getURI(prefix);
          String srcURI = dtm.getNodeValue(namespace);

          if (!srcURI.equalsIgnoreCase(desturi))
          {
            this.startPrefixMapping(prefix, srcURI, false);
          }
        }
      }
    }
    catch (org.xml.sax.SAXException se)
    {
      throw new TransformerException(se);
    }
  }

  /**
   * Given a prefix, return the namespace,
   *
   * @param prefix Given prefix name
   *
   * @return Namespace associated with the given prefix, or null
   */
  public String getURI(String prefix)
  {
    return m_nsSupport.getURI(prefix);
  }

  /**
   * Given a namespace, try and find a prefix.
   *
   * @param namespace Given namespace URI
   *
   * @return Prefix name associated with namespace URI
   */
  public String getPrefix(String namespace)
  {

    // This Enumeration business may be too slow for our purposes...
    Enumeration enum = m_nsSupport.getPrefixes();

    while (enum.hasMoreElements())
    {
      String prefix = (String) enum.nextElement();

      if (m_nsSupport.getURI(prefix).equals(namespace))
        return prefix;
    }

    return null;
  }

  /**
   * Get the NamespaceSupport object.
   *
   * @return NamespaceSupport object.
   */
  public NamespaceSupport getNamespaceSupport()
  {
    return m_nsSupport;
  }

  //  /**
  //   * Override QueuedEvents#initQSE.
  //   *
  //   * @param qse Give queued Sax event
  //   */
  //  protected void initQSE(QueuedSAXEvent qse)
  //  {
  //
  //    // qse.setContentHandler(m_contentHandler);
  //    // qse.setTransformer(m_transformer);
  //    // qse.setTraceManager(m_tracer);
  //  }

  /**
   * Return the current content handler.
   *
   * @return The current content handler, or null if none
   *         has been registered.
   * @see #setContentHandler
   */
  public final ContentHandler getContentHandler()
  {
    return m_contentHandler;
  }

  /**
   * Set the current content handler.
   *
   *
   * @param ch Content Handler to be set
   * @return The current content handler, or null if none
   *         has been registered.
   * @see #getContentHandler
   */
  public void setContentHandler(ContentHandler ch)
  {

    m_contentHandler = ch;

    m_isTransformClient = (m_contentHandler instanceof TransformerClient);

    if (m_contentHandler instanceof LexicalHandler)
      m_lexicalHandler = (LexicalHandler) m_contentHandler;
    else
      m_lexicalHandler = null;

    reInitEvents();
  }

  /**
   * Get a unique namespace value.
   *
   * @return a unique namespace value to be used with a
   * fabricated prefix
   */
  public int getUniqueNSValue()
  {
    return m_uniqueNSValue++;
  }

  /**
   * Get new unique namespace prefix.
   *
   * @return Unique fabricated prefix.
   */
  public String getNewUniqueNSPrefix()
  {
    return S_NAMESPACEPREFIX + String.valueOf(getUniqueNSValue());
  }

  /**
   * Get the pending attributes.  We have to delay the call to
   * m_flistener.startElement(name, atts) because of the
   * xsl:attribute and xsl:copy calls.  In other words,
   * the attributes have to be fully collected before you
   * can call startElement.
   *
   * @return the pending attributes.
   */
  public MutableAttrListImpl getPendingAttributes()
  {
    return m_attributes;
  }

  /**
   * Add an attribute to the end of the list.
   *
   * <p>Do not pass in xmlns decls to this function!
   *
   * <p>For the sake of speed, this method does no checking
   * to see if the attribute is already in the list: that is
   * the responsibility of the application.</p>
   *
   * @param uri The Namespace URI, or the empty string if
   *        none is available or Namespace processing is not
   *        being performed.
   * @param localName The local name, or the empty string if
   *        Namespace processing is not being performed.
   * @param rawName The raw XML 1.0 name, or the empty string
   *        if raw names are not available.
   * @param type The attribute type as a string.
   * @param value The attribute value.
   *
   * @throws TransformerException
   */
  public void addAttribute(
          String uri, String localName, String rawName, String type, String value)
            throws TransformerException
  {

    // %REVIEW% %OPT% Is this ever needed?????
    if (!m_nsDeclsHaveBeenAdded)
      addNSDeclsToAttrs();

    if (null == uri)  // defensive, should not really need this.
      uri = "";

    try
    {
      if (!rawName.equals("xmlns"))  // don't handle xmlns default namespace.
        ensurePrefixIsDeclared(uri, rawName);
    }
    catch (org.xml.sax.SAXException se)
    {
      throw new TransformerException(se);
    }

    if (DEBUG)
      System.out.println("ResultTreeHandler#addAttribute Adding attr: "
                         + localName + ", " + uri);

    if (!isDefinedNSDecl(rawName, value))
      m_attributes.addAttribute(uri, localName, rawName, type, value);
  }

  /**
   * Return whether or not a namespace declaration is defined
   *
   *
   * @param rawName Raw name of namespace element
   * @param value URI of given namespace
   *
   * @return True if the namespace is already defined in list of
   * namespaces
   */
  public boolean isDefinedNSDecl(String rawName, String value)
  {

    if (rawName.equals("xmlns") || rawName.startsWith("xmlns:"))
    {
      int index;
      String prefix = (index = rawName.indexOf(":")) < 0
                      ? "" : rawName.substring(0, index);
      String definedURI = m_nsSupport.getURI(prefix);

      if (null != definedURI)
      {
        if (definedURI.equals(value))
        {
          return true;
        }
        else
          return false;
      }
      else
        return false;
    }
    else
      return false;
  }

  /**
   * Returns whether a namespace is defined
   *
   *
   * @param attr Namespace attribute node
   *
   * @return True if the namespace is already defined in
   * list of namespaces
   */
  public boolean isDefinedNSDecl(int attr)
  {

    DTM dtm = m_transformer.getXPathContext().getDTM(attr);

    if (DTM.NAMESPACE_NODE == dtm.getNodeType(attr))
    {

      // String prefix = dtm.getPrefix(attr);
      String prefix = dtm.getNodeNameX(attr);
      String uri = getURI(prefix);

      if ((null != uri) && uri.equals(dtm.getStringValue(attr)))
        return true;
    }

    return false;
  }

  /**
   * Returns whether a namespace is defined
   *
   *
   * @param attr Namespace attribute node
   * @param dtm The DTM that owns attr.
   *
   * @return True if the namespace is already defined in
   * list of namespaces
   */
  public boolean isDefinedNSDecl(int attr, DTM dtm)
  {

    if (DTM.NAMESPACE_NODE == dtm.getNodeType(attr))
    {

      // String prefix = dtm.getPrefix(attr);
      String prefix = dtm.getNodeNameX(attr);
      String uri = getURI(prefix);

      if ((null != uri) && uri.equals(dtm.getStringValue(attr)))
        return true;
    }

    return false;
  }

  /**
   * Copy an DOM attribute to the created output element, executing
   * attribute templates as need be, and processing the xsl:use
   * attribute.
   *
   * @param attr Attribute node to add to result tree
   *
   * @throws TransformerException
   */
  public void addAttribute(int attr) throws TransformerException
  {

    DTM dtm = m_transformer.getXPathContext().getDTM(attr);

    if (isDefinedNSDecl(attr, dtm))
      return;

    String ns = dtm.getNamespaceURI(attr);

    if (ns == null)
      ns = "";

    // %OPT% ...can I just store the node handle?    
    addAttribute(ns, dtm.getLocalName(attr), dtm.getNodeName(attr), "CDATA",
                 dtm.getNodeValue(attr));
  }  // end copyAttributeToTarget method

  /**
   * Copy DOM attributes to the result element.
   *
   * @param src Source node with the attributes
   *
   * @throws TransformerException
   */
  public void addAttributes(int src) throws TransformerException
  {

    DTM dtm = m_transformer.getXPathContext().getDTM(src);

    for (int node = dtm.getFirstAttribute(src); DTM.NULL != node;
            node = dtm.getNextAttribute(node))
    {
      addAttribute(node);
    }
  }

  /**
   * Tell if an element is pending, to be output to the result tree.
   *
   * @return True if an element is pending
   */
  public final boolean isElementPending()
  {
    
    return m_elemIsPending;
  }

  /**
   * Retrieves the stylesheet element that produced
   * the SAX event.
   *
   * <p>Please note that the ElemTemplateElement returned may
   * be in a default template, and thus may not be
   * defined in the stylesheet.</p>
   *
   * @return the stylesheet element that produced the SAX event.
   */
  public ElemTemplateElement getCurrentElement()
  {

    if (m_elemIsPending)
      return m_snapshot.m_currentElement;
    else
      return m_transformer.getCurrentElement();
  }

  /**
   * This method retrieves the current context node
   * in the source tree.
   *
   * @return the current context node in the source tree.
   */
  public org.w3c.dom.Node getCurrentNode()
  {
    
    if (m_snapshot.m_currentNode != null)
    {
      return m_snapshot.m_currentNode;
    }
    else
    {
      DTM dtm = m_transformer.getXPathContext().getDTM(m_transformer.getCurrentNode());
      return dtm.getNode(m_transformer.getCurrentNode());
    }
  }

  /**
   * This method retrieves the xsl:template
   * that is in effect, which may be a matched template
   * or a named template.
   *
   * <p>Please note that the ElemTemplate returned may
   * be a default template, and thus may not have a template
   * defined in the stylesheet.</p>
   *
   * @return the xsl:template that is in effect
   */
  public ElemTemplate getCurrentTemplate()
  {

    if (m_elemIsPending)
      return m_snapshot.m_currentTemplate;
    else
      return m_transformer.getCurrentTemplate();
  }

  /**
   * This method retrieves the xsl:template
   * that was matched.  Note that this may not be
   * the same thing as the current template (which
   * may be from getCurrentElement()), since a named
   * template may be in effect.
   *
   * <p>Please note that the ElemTemplate returned may
   * be a default template, and thus may not have a template
   * defined in the stylesheet.</p>
   *
   * @return the xsl:template that was matched.
   */
  public ElemTemplate getMatchedTemplate()
  {

    if (m_elemIsPending)
      return m_snapshot.m_matchedTemplate;
    else
      return m_transformer.getMatchedTemplate();
  }

  /**
   * Retrieves the node in the source tree that matched
   * the template obtained via getMatchedTemplate().
   *
   * @return the node in the source tree that matched
   * the template obtained via getMatchedTemplate().
   */
  public org.w3c.dom.Node getMatchedNode()
  {

    if (m_elemIsPending)
    {
      DTM dtm = m_transformer.getXPathContext().getDTM(m_snapshot.m_matchedNode);
      return dtm.getNode(m_snapshot.m_matchedNode);
    }
    else
    {
      DTM dtm = m_transformer.getXPathContext().getDTM(m_transformer.getMatchedNode());
      return dtm.getNode(m_transformer.getMatchedNode());
    }
  }

  /**
   * Get the current context node list.
   *
   * @return the current context node list.
   */
  public org.w3c.dom.traversal.NodeIterator getContextNodeList()
  {

    if (m_elemIsPending)
    {
      return new org.apache.xml.dtm.ref.DTMNodeIterator(m_snapshot.m_contextNodeList);
    }
    else
      return new org.apache.xml.dtm.ref.DTMNodeIterator(m_transformer.getContextNodeList());
  }

  /**
   * Get the TrAX Transformer object in effect.
   *
   * @return the TrAX Transformer object in effect.
   */
  public Transformer getTransformer()
  {
    return m_transformer;
  }
  
  
  // Implement ErrorHandler
  
  /**
    * Receive notification of a warning.
    *
    * <p>SAX parsers will use this method to report conditions that
    * are not errors or fatal errors as defined by the XML 1.0
    * recommendation.  The default behaviour is to take no action.</p>
    *
    * <p>The SAX parser must continue to provide normal parsing events
    * after invoking this method: it should still be possible for the
    * application to process the document through to the end.</p>
    *
    * <p>Filters may use this method to report other, non-XML warnings
    * as well.</p>
    *
    * @param exception The warning information encapsulated in a
    *                  SAX parse exception.
    * @exception org.xml.sax.SAXException Any SAX exception, possibly
    *            wrapping another exception.
    * @see org.xml.sax.SAXParseException 
    */
  public void warning (SAXParseException exception)
    throws SAXException
  {
    if (m_contentHandler instanceof ErrorHandler)
      ((ErrorHandler)m_contentHandler).warning(exception);
  }
    
    
   /**
    * Receive notification of a recoverable error.
    *
    * <p>This corresponds to the definition of "error" in section 1.2
    * of the W3C XML 1.0 Recommendation.  For example, a validating
    * parser would use this callback to report the violation of a
    * validity constraint.  The default behaviour is to take no
    * action.</p>
    *
    * <p>The SAX parser must continue to provide normal parsing events
    * after invoking this method: it should still be possible for the
    * application to process the document through to the end.  If the
    * application cannot do so, then the parser should report a fatal
    * error even if the XML 1.0 recommendation does not require it to
    * do so.</p>
    *
    * <p>Filters may use this method to report other, non-XML errors
    * as well.</p>
    *
    * @param exception The error information encapsulated in a
    *                  SAX parse exception.
    * @exception org.xml.sax.SAXException Any SAX exception, possibly
    *            wrapping another exception.
    * @see org.xml.sax.SAXParseException 
    */
  public void error (SAXParseException exception)
    throws SAXException
  {
    if (m_contentHandler instanceof ErrorHandler)
      ((ErrorHandler)m_contentHandler).error(exception);
  }
    
    
   /**
    * Receive notification of a non-recoverable error.
    *
    * <p>This corresponds to the definition of "fatal error" in
    * section 1.2 of the W3C XML 1.0 Recommendation.  For example, a
    * parser would use this callback to report the violation of a
    * well-formedness constraint.</p>
    *
    * <p>The application must assume that the document is unusable
    * after the parser has invoked this method, and should continue
    * (if at all) only for the sake of collecting addition error
    * messages: in fact, SAX parsers are free to stop reporting any
    * other events once this method has been invoked.</p>
    *
    * @param exception The error information encapsulated in a
    *                  SAX parse exception.  
    * @exception org.xml.sax.SAXException Any SAX exception, possibly
    *            wrapping another exception.
    * @see org.xml.sax.SAXParseException
    */
  public void fatalError (SAXParseException exception)
    throws SAXException
  {      
    m_elemIsPending = false;
    m_docEnded = true;
    m_docPending = false;
    
    if (m_contentHandler instanceof ErrorHandler)
      ((ErrorHandler)m_contentHandler).fatalError(exception);
  }
  
  boolean m_isTransformClient = false;

  /**
   * Use the SAX2 helper class to track result namespaces.
   */
  NamespaceSupport m_nsSupport = new NamespaceSupport2();

  /**
   * The transformer object.
   */
  private TransformerImpl m_transformer;

  /**
   * The content handler.  May be null, in which
   * case, we'll defer to the content handler in the
   * transformer.
   */
  private ContentHandler m_contentHandler;

  /** The LexicalHandler */
  private LexicalHandler m_lexicalHandler;

  /**
   * The root of a linked set of stylesheets.
   */
  private StylesheetRoot m_stylesheetRoot = null;

  /**
   * This is used whenever a unique namespace is needed.
   */
  private int m_uniqueNSValue = 0;

  /** Prefix used to create unique prefix names */
  private static final String S_NAMESPACEPREFIX = "ns";

  /**
   * This class clones nodes to the result tree.
   */
  public ClonerToResultTree m_cloner;

  /**
   * Trace manager for debug support.
   */
  private TraceManager m_tracer;
  
  private QueuedStateSnapshot m_snapshot = new QueuedStateSnapshot();

  // These are passed to flushPending, to help it decide if it 
  // should really flush.
  
  class QueuedStateSnapshot
  {
    /**
     * The stylesheet element that produced the SAX event.
     */
    ElemTemplateElement m_currentElement;
    
    /**
     * The current context node in the source tree.
     */
    org.w3c.dom.Node m_currentNode;
    
    /**
     * The xsl:template that is in effect, which may be a matched template
     * or a named template.
     */
    ElemTemplate m_currentTemplate;
    
    /**
     * The xsl:template that was matched.
     */
    ElemTemplate m_matchedTemplate;
    
    /**
     * The node in the source tree that matched
     * the template obtained via getMatchedTemplate().
     */
    int m_matchedNode;
    
    /**
     * The current context node list.
     */
    DTMIterator m_contextNodeList;
  }
}
