/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * 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.1 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.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor.converter;

import org.orbeon.oxf.externalcontext.URLRewriter;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.xml.SAXUtils;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.processor.ProcessorImpl;
import org.orbeon.oxf.processor.ProcessorInputOutputInfo;
import org.orbeon.oxf.processor.ProcessorOutput;
import org.orbeon.oxf.processor.impl.CacheableTransformerOutputImpl;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.saxrewrite.DocumentRootState;
import org.orbeon.oxf.xml.saxrewrite.FragmentRootState;
import org.orbeon.oxf.xml.saxrewrite.State;
import org.orbeon.oxf.xml.saxrewrite.StatefulHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * Java impl of oxf-rewrite.xsl. Uses GOF state pattern + SAX to get the job done. The state machine ad hoc and relies a
 * bit on the simplicity of the transformation that we are performing.
 *
 * Wrt the transformation, here is the comment from oxf-rewrite.xsl :
 *
 * This stylesheet rewrites HTML or XHTML for servlets and portlets. URLs are parsed, so it must be made certain that
 * the URLs are well-formed. Absolute URLs are not modified. Relative or absolute paths are supported, as well as the
 * special case of a URL starting with a query string (e.g. "?name=value"). This last syntax is supported by most Web
 * browsers.
 *
 * A. For portlets, it does the following:
 *
 * 1. Rewrite form/@action to WSRP action URL encoding
 * 2. Rewrite a/@href and link/@href to WSRP render encoding
 * 3. Rewrite img/@src, input[@type='image']/@src and script/@src to WSRP resource URL encoding
 * 4. If no form/@method is supplied, force an HTTP POST
 * 5. Escape any wsrp_rewrite occurrence in text not within a script or
 *
 * SCRIPT element to wsrp_rewritewsrp_rewrite. WSRP 1.0 does not appear to specify a particular escape sequence, but we
 * use this one in Orbeon Forms Portal. The escaped sequence is recognized by the Orbeon Forms Portlet and restored to
 * the original sequence, so it is possible to include the string wsrp_rewrite within documents.
 *
 * 6. Occurrences of wsrp_rewrite found within script or SCRIPT elements, as well as occurrences within attributes,
 * are left untouched. This allows them to be recognized by the Orbeon Forms Portlet and rewritten.
 *
 * Known issues for portlets:
 *
 * o The input document should not contain;
 * o elements and attribute containing wsrp_rewrite
 * o namespace URIs containing wsrp_rewrite
 * o processing instructions containing wsrp_rewrite
 *
 * B. For servlets, it rewrites the URLs to be absolute paths, and prepends the context path.
 */
abstract class AbstractRewrite extends ProcessorImpl {

    static final String SCRIPT_ELT = "script";
    static final String OBJECT_ELT = "object";
    static final String ACTION_ATT = "action";
    static final String METHOD_ATT = "method";
    static final String HREF_ATT = "href";
    static final String SRC_ATT = "src";
    static final String BACKGROUND_ATT = "background";
    static final String NOREWRITE_ATT = "url-norewrite";

    /**
     * Base state. Simply forwards data to the destination content handler and returns itself. That is unless the
     * (element) depth becomes negative after an end element event. In this case the previous state is returned. This
     * means btw that we are really only considering state changes on start and end element events.
     */
    protected static abstract class State2 extends State {

        /**
         * Performs the URL rewrites.
         */
        protected final URLRewriter response;
        /**
         * Could have been another State. However since the value is determined in one state and then used by a
         * 'descendant' state doing so would have meant that descendant would have to walk it's ancestors to get the
         * value. So, since making this a field instead of a separate State sub-class was easier to implement and is
         * faster a field was used.
         */
        protected int scriptDepth;

        protected final String rewriteURI;

        /**
         * @param previousState     The previous state.
         * @param xmlReceiver       The destination for the rewrite transformation.
         * @param response          Used to perform URL rewrites.
         * @param scriptDepth       How below script elt we are.
         * @param rewriteURI        URI of elements (i.e. xhtml uri or "") of elements that need rewriting.
         */
        State2(final State previousState, final XMLReceiver xmlReceiver, final URLRewriter response,
               final int scriptDepth, final String rewriteURI) {
            super(previousState, xmlReceiver);
            this.response = response;
            this.scriptDepth = scriptDepth;
            this.rewriteURI = rewriteURI;
        }

        /**
         * Adjusts scriptDepth
         */
        final void scriptDepthOnStart(final String ns, final String lnam) {
            if (rewriteURI.equals(ns) && SCRIPT_ELT.equals(lnam)) {
                scriptDepth++;
            }
        }

        /**
         * Adjusts scriptDepth
         */
        final void scriptDepthOnEnd(final String ns, final String lnam) {
            if (rewriteURI.equals(ns) && SCRIPT_ELT.equals(lnam)) {
                scriptDepth--;
            }
        }

        @Override
        protected void endElementStart(final String ns, final String lnam, final String qnam)
                throws SAXException {
            scriptDepthOnEnd(ns, lnam);
            super.endElementStart(ns, lnam, qnam);
        }
    }

    /**
     * The rewrite state.  Essentially this corresponds to the default mode of oxf-rewrite.xsl.
     * Basically this:
     * <ul>
     * <li>Rewrites attributes in start element event when need be.
     * <li>Accumulates text from characters events so that proper char content rewriting can
     * happen.
     * <li>On an event that indicates the end of potentially rewritable text, e.g. start element,
     * rewrites and forwards the accumulated characters.
     * <li>When explicit no write is indicated, e.g. we see attributes no-rewrite=true, then
     * transition to the NoRewriteState.
     * </ul>
     */
    static class RewriteState extends State2 {

        /**
         * Used to accumulate characters from characters event. Lazily init'd in characters.
         *
         * NOTE: We use CharBuffer for historical reasons. Since we support Java 1.5 and up, we could use StringBuilder.
         */
        private java.nio.CharBuffer charactersBuf;

        private final ArrayList<Boolean> fObjectParent = new ArrayList<Boolean>();

        /**
         * Calls super( ... ) and initializes wsrprewriteMatcher with "wsrp_rewrite"
         *
         * @param rewriteURI
         */
        RewriteState(final State stt, final XMLReceiver xmlReceiver, final URLRewriter response
                , final int scriptDepth, final String rewriteURI) {
            super(stt, xmlReceiver, response, scriptDepth, rewriteURI);
        }

        /**
         * Handler for {http://www.w3.org/1999/xhtml}{elt name}.  Assumes namespace test has already
         * happened.  Implements :
         * <pre>
         *   <xsl:template match="xhtml:{elt name}[@{res attrib name}]" >
         *     <xsl:copy>
         *       <xsl:copy-of select="@*[namespace-uri() = '']"/>
         *         <xsl:attribute name="{res attrib name}">
         *           <xsl:value-of select="context:rewriteResourceURL(@{res attrib name})"/>
         *         </xsl:attribute>
         *         <xsl:apply-templates/>
         *       </xsl:copy>
         *   </xsl:template>
         * </pre>
         *
         * If match is satisfied then modified event is sent to destination contentHandler.
         *
         * @return null if match is not satisfied and this otherwise.
         * @throws SAXException if destination contentHandler throws SAXException
         */
        private State2 handleEltWithResource
                (final String elt, final String resAtt, final String ns, final String lnam
                        , final String qnam, final Attributes atts)
                throws SAXException {
            State2 ret = null;
            done :
            if (elt.equals(lnam)) {

                final String res = atts.getValue("", resAtt);
                if (res == null) break done;

                ret = this;
                final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
                final String newRes = response.rewriteResourceURL(res, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                final int idx = newAtts.getIndex("", resAtt);
                newAtts.setValue(idx, newRes);
                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
            }
            return ret;
        }

        /**
         * Handle xhtml:object
         */
        private State2 handleObject
                (final String ns, final String lnam
                        , final String qnam, final Attributes atts)
                throws SAXException {
            State2 ret = null;
            done :
            if (OBJECT_ELT.equals(lnam)) {

                final String codebaseAttribute = atts.getValue("", "codebase");
                final String classidAttribute = atts.getValue("", "classid");
                final String dataAttribute = atts.getValue("", "data");
                final String usemapAttribute = atts.getValue("", "usemap");
                final String archiveAttribute = atts.getValue("", "archive");// space-separated

                if (classidAttribute == null && codebaseAttribute == null && dataAttribute == null && usemapAttribute == null && archiveAttribute == null) break done;

                ret = this;
                final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
                if (codebaseAttribute != null) {
                    final String newAttribute = response.rewriteResourceURL(codebaseAttribute, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                    final int idx = newAtts.getIndex("", "codebase");
                    newAtts.setValue(idx, newAttribute);
                } else {
                    // We don't rewrite these attributes if there is a codebase
                    if (classidAttribute != null) {
                        final String newAttribute = response.rewriteResourceURL(classidAttribute, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                        final int idx = newAtts.getIndex("", "classid");
                        newAtts.setValue(idx, newAttribute);
                    }
                    if (dataAttribute != null) {
                        final String newAttribute = response.rewriteResourceURL(dataAttribute, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                        final int idx = newAtts.getIndex("", "data");
                        newAtts.setValue(idx, newAttribute);
                    }
                    if (usemapAttribute != null) {
                        final String newAttribute = response.rewriteResourceURL(usemapAttribute, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                        final int idx = newAtts.getIndex("", "usemap");
                        newAtts.setValue(idx, newAttribute);
                    }
                    if (archiveAttribute != null) {
                        final StringTokenizer st = new StringTokenizer(archiveAttribute, " ");
                        final StringBuilder sb = new StringBuilder(archiveAttribute.length() * 2);
                        boolean first = true;
                        while (st.hasMoreTokens()) {
                            final String currentArchive = st.nextToken().trim();
                            final String newArchive = response.rewriteResourceURL(currentArchive, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                            if (!first) {
                                sb.append(' ');
                            }
                            sb.append(newArchive);
                            first = false;
                        }
                        final int idx = newAtts.getIndex("", "archive");
                        newAtts.setValue(idx, sb.toString());
                    }
                }

                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
            }
            return ret;
        }

        /**
         * Handle xhtml:applet
         */
        private State2 handleApplet
                (final String ns, final String lnam
                        , final String qnam, final Attributes atts)
                throws SAXException {
            State2 ret = null;
            done :
            if ("applet".equals(lnam)) {

                final String codebaseAttribute = atts.getValue("", "codebase");
                final String archiveAttribute = atts.getValue("", "archive");// comma-separated
//                final String codeAttribute = atts.getValue("", "code");// not clear whether this needs to be rewritten

                if (archiveAttribute == null && codebaseAttribute == null) break done;

                ret = this;
                final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
                if (codebaseAttribute != null) {
                    final String newAttribute = response.rewriteResourceURL(codebaseAttribute, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                    final int idx = newAtts.getIndex("", "codebase");
                    newAtts.setValue(idx, newAttribute);
                } else {
                    // We don't rewrite the @archive attribute if there is a codebase
                    final StringTokenizer st = new StringTokenizer(archiveAttribute, ",");
                    final StringBuilder sb = new StringBuilder(archiveAttribute.length() * 2);
                    boolean first = true;
                    while (st.hasMoreTokens()) {
                        final String currentArchive = st.nextToken().trim();
                        final String newArchive = response.rewriteResourceURL(currentArchive, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                        if (!first) {
                            sb.append(' ');
                        }
                        sb.append(newArchive);
                        first = false;
                    }
                    final int idx = newAtts.getIndex("", "archive");
                    newAtts.setValue(idx, sb.toString());
                }

                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
            }
            return ret;
        }

        /**
         * Handle xhtml:param
         */
        private State2 handleParam
        (final String ns, final String lnam
        		, final String qnam, final Attributes atts)
        				throws SAXException {
        	State2 ret = null;
        	final boolean inObject = fObjectParent.size() >= 2 && fObjectParent.get(fObjectParent.size() - 2).booleanValue();
        	done :
    		if (inObject && "param".equals(lnam)) {
    			
    			final String nameAttribute = atts.getValue("", "name");
    			final String valueAttribute = atts.getValue("", "value");
    			
    			if (nameAttribute == null || valueAttribute == null) break done;
    			
    			ret = this;
    			final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
    			
    			if ("archive".equals(nameAttribute.trim()))
    			{
    				final StringTokenizer st = new StringTokenizer(valueAttribute, ",");
    				final StringBuilder sb = new StringBuilder(valueAttribute.length() * 2);
    				boolean first = true;
    				while (st.hasMoreTokens()) {
    					final String currentArchive = st.nextToken().trim();
    					final String newArchive = response.rewriteResourceURL(currentArchive, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
    					if (!first) {
    						sb.append(' ');
    					}
    					sb.append(newArchive);
    					first = false;
    				}
    				final int idx = newAtts.getIndex("", "value");
    				newAtts.setValue(idx, sb.toString());
    			}
    			
    			xmlReceiver.startElement(ns, lnam, qnam, newAtts);
    		}
        	return ret;
        }
        
        /**
         * Handler for {http://www.w3.org/1999/xhtml}a.  Assumes namespace test has already
         * happened.  Implements :
         * <pre>
         *   <xsl:template match="xhtml:a[@href]" >
         *     <xsl:copy>
         *       <xsl:copy-of select="@*[namespace-uri() = '']"/>
         *         <xsl:attribute name="href">
         *           <xsl:choose>
         *             <xsl:when test="not(@f:url-type) or @f:url-type = 'render'">
         *               <xsl:value-of select="context:rewriteRenderURL(@href)"/>
         *             </xsl:when>
         *             <xsl:when test="@f:url-type = 'action'">
         *               <xsl:value-of select="context:rewriteActionURL(@href)"/>
         *             </xsl:when>
         *             <xsl:when test="@f:url-type = 'resource'">
         *               <xsl:value-of select="context:rewriteResourceURL(@href)"/>
         *             </xsl:when>
         *           </xsl:choose>
         *         </xsl:attribute>
         *       <xsl:apply-templates/>
         *     </xsl:copy>
         *   </xsl:template>
         * </pre>
         *
         * If match is satisfied then modified event is sent to destination contentHandler.
         *
         * @return null if match is not satisfied and this otherwise.
         * @throws SAXException if destination contentHandler throws SAXException
         */
        private State2 handleA
                (final String ns, final String lnam, final String qnam, final Attributes atts)
                throws SAXException {
            State2 ret = null;
            done :
            if ("a".equals(lnam)) {

                final String href = atts.getValue("", HREF_ATT);
                if (href == null) break done;

                ret = this;
                final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
                final String urlType = atts.getValue(XMLConstants.OPS_FORMATTING_URI, "url-type");
                final String portletMode = atts.getValue(XMLConstants.OPS_FORMATTING_URI, "portlet-mode");
                final String windowState = atts.getValue(XMLConstants.OPS_FORMATTING_URI, "window-state");

                final String newHref;
                if (urlType == null || "render".equals(urlType)) {
                    newHref = response.rewriteRenderURL(href, portletMode, windowState);
                } else if ("action".equals(urlType)) {
                    newHref = response.rewriteActionURL(href, portletMode, windowState);
                } else if ("resource".equals(urlType)) {
                    newHref = response.rewriteResourceURL(href, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                } else {
                    newHref = null;
                }
                final int idx = newAtts.getIndex("", HREF_ATT);
                if (newHref == null && idx != -1) {
                    newAtts.removeAttribute(idx);
                } else {
                    newAtts.setValue(idx, newHref);
                }
                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
            }
            return ret;
        }

        /**
         * Handler for {http://www.w3.org/1999/xhtml}area.  Assumes namespace test has already
         * happened.  Implements :
         * <pre>
         *   <xsl:template match="xhtml:area[@href]" >
         *     <xsl:copy>
         *       <xsl:copy-of select="@*[namespace-uri() = '']"/>
         *         <xsl:attribute name="href">
         *           <xsl:value-of select="context:rewriteActionURL(@href)"/>
         *         </xsl:attribute>
         *         <xsl:apply-templates/>
         *       </xsl:copy>
         *   </xsl:template>
         * </pre>
         *
         * If match is satisfied then modified event is sent to destination contentHandler.
         *
         * @return null if match is not satisfied and this otherwise.
         * @throws SAXException if destination contentHandler throws SAXException
         */
        private State2 handleArea
                (final String ns, final String lnam, final String qnam, final Attributes atts)
                throws SAXException {
            State2 ret = null;
            done :
            if ("area".equals(lnam)) {

                final String href = atts.getValue("", HREF_ATT);
                if (href == null) break done;

                ret = this;
                final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
                final String newHref = response.rewriteActionURL(href);
                final int idx = newAtts.getIndex("", HREF_ATT);
                newAtts.setValue(idx, newHref);
                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
            }
            return ret;
        }

        /**
         * Handler for {http://www.w3.org/1999/xhtml}input.  Assumes namespace test has already
         * happened.  Implements :
         * <pre>
         *   <xsl:template match="xhtml:input[@type='image' and @src]" >
         *     <xsl:copy>
         *       <xsl:copy-of select="@*[namespace-uri() = '']"/>
         *         <xsl:attribute name="src">
         *           <xsl:value-of select="context:rewriteActionURL(@src)"/>
         *         </xsl:attribute>
         *         <xsl:apply-templates/>
         *       </xsl:copy>
         *   </xsl:template>
         * </pre>
         *
         * If match is satisfied then modified event is sent to destination contentHandler.
         *
         * @return null if @type='image' test is not satisfied and
         *         handleEltWithResource( "input", "src", ... ) otherwise.
         * @throws SAXException if destination contentHandler throws SAXException
         */
        private State2 handleInput
                (final String ns, final String lnam, final String qnam, final Attributes atts)
                throws SAXException {
            final State2 ret;
            final String typ = atts.getValue("", "type");
            if ("image".equals(typ)) {
                ret = handleEltWithResource("input", SRC_ATT, ns, lnam, qnam, atts);
            } else {
                ret = null;
            }
            return ret;
        }

        /**
         * Handler for {http://www.w3.org/1999/xhtml}form.  Assumes namespace test has already
         * happened.  Implements :
         * <pre>
         *   <xsl:template match="form | xhtml:form">
         *     <xsl:copy>
         *       <xsl:copy-of select="@*[namespace-uri() = '']"/>
         *       <xsl:choose>
         *         <xsl:when test="@action">
         *           <xsl:attribute name="action">
         *             <xsl:value-of select="context:rewriteActionURL(@action)"/>
         *           </xsl:attribute>
         *         </xsl:when>
         *         <xsl:otherwise>
         *           <xsl:attribute name="action">
         *             <xsl:value-of select="context:rewriteActionURL('')"/>
         *           </xsl:attribute>
         *         </xsl:otherwise>
         *       </xsl:choose>
         *       <!-- Default is POST instead of GET for portlets -->
         *       <xsl:if test="not(@method) and $container-type/* = 'portlet'">
         *         <xsl:attribute name="method">post</xsl:attribute>
         *       </xsl:if>
         *       <xsl:apply-templates/>
         *     </xsl:copy>
         *   </xsl:template>
         * </pre>
         *
         * If match is satisfied then modified event is sent to destination contentHandler.
         *
         * @return null match is not satisfied, this otherwise.
         * @throws SAXException if destination contentHandler throws SAXException
         */
        private State2 handleForm
                (final String ns, final String lnam, final String qnam, final Attributes atts)
                throws SAXException {
            final State2 ret;
            if ("form".equals(lnam)) {

                final AttributesImpl newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);

                final String actn = newAtts.getValue("", ACTION_ATT);
                final String newActn;
                if (actn == null) {
                    newActn = response.rewriteActionURL("");
                    newAtts.addAttribute("", ACTION_ATT, ACTION_ATT, "", newActn);
                } else {
                    final int idx = newAtts.getIndex("", ACTION_ATT);
                    newActn = response.rewriteActionURL(actn);
                    newAtts.setValue(idx, newActn);
                }

                if (atts.getValue("", METHOD_ATT) == null) {
                    newAtts.addAttribute("", METHOD_ATT, METHOD_ATT, "", "post");
                }

                ret = this;
                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
            } else {
                ret = null;
            }
            return ret;
        }

        /**
         * If we have accumulated character data rewrite it and forward it.  Implements :
         * <pre>
         *   <xsl:template match="text()">
         *     <xsl:value-of
         *       select="replace(current(), 'wsrp_rewrite', 'wsrp_rewritewsrp_rewrite')"/>
         *     <xsl:apply-templates/>
         *   </xsl:template>
         * </pre>
         * If there no character data has been accumulated do nothing.  Also clears buffer.
         */
        private void flushCharacters() throws SAXException {
            final int bfLen = charactersBuf == null ? 0 : charactersBuf.position();
            if (bfLen > 0) {
                charactersBuf.flip();
                final char[] chs = charactersBuf.array();
                final int chsStrt = charactersBuf.arrayOffset();
                int last = 0;
                if (last < bfLen) {
                    final int len = bfLen - last;
                    xmlReceiver.characters(chs, chsStrt + last, len);
                }
                charactersBuf.clear();
            }
        }

        /**
         * Just calls flushCharacters and super.endElement( ... )
         */
        @Override
        protected void endElementStart(final String ns, final String lnam, final String qnam)
                throws SAXException {
        	fObjectParent.remove(fObjectParent.size() - 1);
            flushCharacters();
            super.endElementStart(ns, lnam, qnam);
        }

        /**
         * Just calls flushCharacters then tests the event data.  If
         * <ul>
         * <li>
         *
         * @url-norewrite='true' then forward the event to the destination content handler and
         * return new NoRewriteState( ... ), otherwise
         * </li>
         * <li>
         * if ns.equals( XHTML_URI ) then
         * <ul>
         * <li>if one of the handleXXX methods returns non-null do nothing, otherwise</li>
         * <li>
         * forward the event to the destination content handler and return this, otherwise
         * </li>
         * </ul>
         * </li>
         * <li>
         * if the element is {http://orbeon.org/oxf/xml/formatting}rewrite then implement :
         * <pre>
         *       <xsl:choose>
         *         <xsl:when test="@type = 'action'">
         *           <xsl:value-of select="context:rewriteActionURL(@url)"/>
         *         </xsl:when>
         *         <xsl:when test="@type = 'render'">
         *           <xsl:value-of select="context:rewriteRenderURL(@url)"/>
         *         </xsl:when>
         *         <xsl:otherwise>
         *           <xsl:value-of select="context:rewriteResourceURL(@url)"/>
         *         </xsl:otherwise>
         *       </xsl:choose>
         *     </pre>
         * Note this means that we forward characters to the destination content handler instead
         * of a start element event, otherwise
         * </li>
         * <li>
         * simply forward the event as is to the destination content handler and return this.
         * </li>
         * </ul>
         */
        protected State startElementStart(final String ns, final String lnam, final String qnam, Attributes atts) throws SAXException {
        	fObjectParent.add(Boolean.valueOf(OBJECT_ELT.equals(lnam) && XMLConstants.XHTML_NAMESPACE_URI.equals(ns)));
        	
            final int noRewriteIndex = atts.getIndex(XMLConstants.OPS_FORMATTING_URI, NOREWRITE_ATT);
            final String noRewriteValue = atts.getValue(noRewriteIndex);
            State ret = null;
            flushCharacters();

            if (noRewriteValue != null) {
                // Remove f:url-norewrite attribute
                final AttributesImpl attributesImpl = new AttributesImpl(atts);
                attributesImpl.removeAttribute(noRewriteIndex);
                atts = attributesImpl;
            }

            done :
            if ("true".equals(noRewriteValue)) {
                final State stt = new NoRewriteState(this, xmlReceiver, response, scriptDepth, rewriteURI);
                ret = stt.startElement(ns, lnam, qnam, atts);
            } else if (XMLConstants.OPS_FORMATTING_URI.equals(ns) && "rewrite".equals(lnam)) {
                final String typ = atts.getValue("", "type");
                final String url = atts.getValue("", "url");
                if (url != null) {
                    final String newURL;
                    if ("action".equals(typ)) {
                        newURL = response.rewriteActionURL(url);
                    } else if ("render".equals(typ)) {
                        newURL = response.rewriteRenderURL(url);
                    } else {
                        newURL = response.rewriteResourceURL(url, ExternalContext.Response.REWRITE_MODE_ABSOLUTE_PATH_OR_RELATIVE);
                    }
                    final char[] chs = newURL.toCharArray();
                    xmlReceiver.characters(chs, 0, chs.length);
                }
            } else {
                scriptDepthOnStart(ns, lnam);
                if (rewriteURI.equals(ns)) {
                    ret = handleA(ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleForm(ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleArea(ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleEltWithResource("link", HREF_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleEltWithResource("img", SRC_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleEltWithResource("frame", SRC_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;
                    
                    ret = handleEltWithResource("iframe", SRC_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleEltWithResource(SCRIPT_ELT, SRC_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleInput(ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleEltWithResource("td", BACKGROUND_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleEltWithResource("body", BACKGROUND_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleObject(ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleApplet(ns, lnam, qnam, atts);
                    if (ret != null) break done;

                    ret = handleParam(ns, lnam, qnam, atts);
                    if (ret != null) break done;
                    
                    // Not valid in HTML, but useful for e.g. Dojo contentPane
                    ret = handleEltWithResource("div", HREF_ATT, ns, lnam, qnam, atts);
                    if (ret != null) break done;
                }
                ret = this;
                xmlReceiver.startElement(ns, lnam, qnam, atts);
            }
            return ret;
        }

        /**
         * If haveScriptAncestor then just forward data to destination contentHandler. Otherwise store that data in the
         * buffer and do not forward. Also manages init'ing and growing charactersBuf as need be.
         */
        @Override
        public State characters(final char[] ch, final int strt, final int len)
                throws SAXException {
            if (scriptDepth > 0) {
                xmlReceiver.characters(ch, strt, len);
            } else {
                final int bufLen = charactersBuf == null ? 0 : charactersBuf.position();
                final int cpcty = bufLen + (len * 2);
                if (charactersBuf == null || charactersBuf.remaining() < cpcty) {
                    final java.nio.CharBuffer newBuf = java.nio.CharBuffer.allocate(cpcty);
                    if (charactersBuf != null) {
                        charactersBuf.flip();
                        newBuf.put(charactersBuf);
                    }
                    charactersBuf = newBuf;
                }
                charactersBuf.put(ch, strt, len);
            }
            return this;
        }

        /**
         * Just calls flushCharacters and super.ignorableWhitespace( ... )
         */
        @Override
        public State ignorableWhitespace(final char[] ch, final int strt, final int len)
                throws SAXException {
            flushCharacters();
            return super.ignorableWhitespace(ch, strt, len);
        }

        /**
         * Just calls flushCharacters and super.processingInstruction( ... )
         */
        @Override
        public State processingInstruction(final String trgt, final String dat)
                throws SAXException {
            flushCharacters();
            return super.processingInstruction(trgt, dat);
        }

        /**
         * Just calls flushCharacters and super.skippedEntity( ... )
         */
        @Override
        public State skippedEntity(final String nam) throws SAXException {
            flushCharacters();
            return super.skippedEntity(nam);
        }
    }

    /**
     * Essentially this corresponds to the norewrite mode of oxf-rewrite.xsl. i.e. Just forwards events to the
     * destination content handler until we finish the initial element (depth < 0) or until it encounters
     * @url-norewrite='false'. In the first case transitions to the previous state and in the second case it transitions
     * to new RewriteState(this, contentHandler, response, haveScriptAncestor).
     */
    static class NoRewriteState extends State2 {

        NoRewriteState(final State2 previousState, final XMLReceiver xmlReceiver, final URLRewriter response,
                final int scriptDepth, final String rewriteURI) {
            super(previousState, xmlReceiver, response, scriptDepth, rewriteURI);
        }

        protected State startElementStart(final String ns, final String lnam, final String qnam, Attributes atts) throws SAXException {

            final int noRewriteIndex = atts.getIndex(XMLConstants.OPS_FORMATTING_URI, NOREWRITE_ATT);
            final String noRewriteValue = atts.getValue(noRewriteIndex);
            final State ret;

            if (noRewriteValue != null) {
                // Remove f:url-norewrite attribute
                final AttributesImpl attributesImpl = new AttributesImpl(atts);
                attributesImpl.removeAttribute(noRewriteIndex);
                atts = attributesImpl;
            }

            if ("false".equals(noRewriteValue)) {
                final State stt = new RewriteState(this, xmlReceiver, response, scriptDepth, rewriteURI);
                ret = stt.startElement(ns, lnam, qnam, atts);
            } else {
                scriptDepthOnStart(ns, lnam);
                final Attributes newAtts = SAXUtils.getAttributesFromDefaultNamespace(atts);
                xmlReceiver.startElement(ns, lnam, qnam, newAtts);
                ret = this;
            }
            return ret;
        }
    }

    /**
     * Namespace of the elements that are to be rewritten.
     */
    final String rewriteURI;

    /**
     * Just declares input 'data' and output 'data'.
     *
     * @param rewriteURI e.g. "http://www.w3.org/1999/xhtml" or ""
     */
    public AbstractRewrite(final String rewriteURI) {
        this.rewriteURI = rewriteURI;
        addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA));
        addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA));
    }

    @Override
    public ProcessorOutput createOutput(final String name) {
        final ProcessorOutput processorOutput = new CacheableTransformerOutputImpl(AbstractRewrite.this, name) {
            public void readImpl(final PipelineContext pipelineContext, final XMLReceiver xmlReceiver) {
                readInputAsSAX(pipelineContext, INPUT_DATA, getRewriteXMLReceiver(NetUtils.getExternalContext().getResponse(), xmlReceiver, false));
            }
        };
        addOutput(name, processorOutput);
        return processorOutput;
    }

    public XMLReceiver getRewriteXMLReceiver(final URLRewriter rewriter, final XMLReceiver xmlReceiver, final boolean fragment) {
        final State rootState;
        if (fragment) {
            // Start directly with rewrite state
            final FragmentRootState fragmentRootState = new FragmentRootState(null, xmlReceiver);
            final State afterRootState = new RewriteState(fragmentRootState, xmlReceiver, rewriter, 0, rewriteURI);
            fragmentRootState.setNextState(afterRootState);
            rootState = fragmentRootState;
        } else {
            // Start with root filter
            final DocumentRootState documentRootState = new DocumentRootState(null, xmlReceiver);
            final State afterRootState = new RewriteState(documentRootState, xmlReceiver, rewriter, 0, rewriteURI);
            documentRootState.setNextState(afterRootState);
            rootState = documentRootState;
        }
        return new StatefulHandler(rootState);
    }
}
