/** * 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.externalcontext.ExternalContext; import org.orbeon.oxf.pipeline.api.PipelineContext; 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.util.StringUtils; import org.orbeon.oxf.xml.SAXUtils; import org.orbeon.oxf.xml.XMLConstants; import org.orbeon.oxf.xml.XMLReceiver; 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 = StringUtils.trimAllToEmpty(st.nextToken()); 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 = StringUtils.trimAllToEmpty(st.nextToken()); 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(StringUtils.trimAllToEmpty(nameAttribute))) { final StringTokenizer st = new StringTokenizer(valueAttribute, ","); final StringBuilder sb = new StringBuilder(valueAttribute.length() * 2); boolean first = true; while (st.hasMoreTokens()) { final String currentArchive = StringUtils.trimAllToEmpty(st.nextToken()); 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); } }