package client.net.sf.saxon.ce.event; import client.net.sf.saxon.ce.expr.instruct.Template; import client.net.sf.saxon.ce.om.NamePool; import client.net.sf.saxon.ce.om.StandardNames; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.value.Whitespace; /** * The Stripper class performs whitespace stripping according to the rules of * the xsl:strip-space and xsl:preserve-space instructions. * It maintains details of which elements need to be stripped. * The code is written to act as a SAX-like filter to do the stripping. * @author Michael H. Kay */ public abstract class Stripper extends ProxyReceiver { // stripStack is used to hold information used while stripping nodes. We avoid allocating // space on the tree itself to keep the size of nodes down. Each entry on the stack is two // booleans, one indicates the current value of xml-space is "preserve", the other indicates // that we are in a space-preserving element. // We implement our own stack to avoid the overhead of allocating objects. The two booleans // are held as the ls bits of a byte. private byte[] stripStack = new byte[100]; private int top = 0; public final static Template STRIP = new Template(){}; public final static Template PRESERVE = new Template(); /** * Get a clean copy of this stripper. The new copy shares the same PipelineConfiguration * as the original, but the underlying receiver (that is, the destination for post-stripping * events) is left uninitialized. */ public abstract Stripper getAnother(); /** * Decide whether an element is in the set of white-space preserving element types * @param fingerprint Identifies the name of the element whose whitespace is to * be preserved * @return ALWAYS_PRESERVE if the element is in the set of white-space preserving * element types, ALWAYS_STRIP if the element is to be stripped regardless of the * xml:space setting, and STRIP_DEFAULT otherwise * @throws XPathException if the rules are ambiguous and ambiguities are to be * reported as errors */ public abstract byte isSpacePreserving(int fingerprint) throws XPathException; public static final byte ALWAYS_PRESERVE = 0x01; // whitespace always preserved (e.g. xsl:text) public static final byte ALWAYS_STRIP = 0x02; // whitespace always stripped (e.g. xsl:choose) public static final byte STRIP_DEFAULT = 0x00; // no special action public static final byte PRESERVE_PARENT = 0x04; // parent element specifies xml:space="preserve" public static final byte CANNOT_STRIP = 0x08; // type annotation indicates simple typed content /** * Callback interface for SAX: not for application use */ public void open () throws XPathException { // System.err.println("Stripper#startDocument()"); top = 0; stripStack[top] = ALWAYS_PRESERVE; // {xml:preserve = false, preserve this element = true} super.open(); } public void startElement(int nameCode, int properties) throws XPathException { // System.err.println("startElement " + nameCode); nextReceiver.startElement(nameCode, properties); byte preserveParent = stripStack[top]; byte preserve = (byte)(preserveParent & PRESERVE_PARENT); byte elementStrip = isSpacePreserving(nameCode & NamePool.FP_MASK); if (elementStrip == ALWAYS_PRESERVE) { preserve |= ALWAYS_PRESERVE; } else if (elementStrip == ALWAYS_STRIP) { preserve |= ALWAYS_STRIP; } // put "preserve" value on top of stack top++; if (top >= stripStack.length) { byte[] newStack = new byte[top*2]; System.arraycopy(stripStack, 0, newStack, 0, top); stripStack = newStack; } stripStack[top] = preserve; } public void attribute(int nameCode, CharSequence value) throws XPathException { // test for xml:space="preserve" | "default" if ((nameCode & 0xfffff) == StandardNames.XML_SPACE) { if (value.toString().equals("preserve")) { stripStack[top] |= PRESERVE_PARENT; } else { stripStack[top] &= ~PRESERVE_PARENT; } } nextReceiver.attribute(nameCode, value); } /** * Handle an end-of-element event */ public void endElement () throws XPathException { nextReceiver.endElement(); top--; } /** * Handle a text node */ public void characters(CharSequence chars) throws XPathException { // assume adjacent chunks of text are already concatenated if (((((stripStack[top] & (ALWAYS_PRESERVE | PRESERVE_PARENT | CANNOT_STRIP)) != 0) && (stripStack[top] & ALWAYS_STRIP) == 0) || !Whitespace.isWhite(chars)) && chars.length() > 0) { nextReceiver.characters(chars); } } /** * Ask whether this Receiver (or the downstream pipeline) makes any use of the type annotations * supplied on element and attribute events * @return true if the Receiver makes any use of this information. If false, the caller * may supply untyped nodes instead of supplying the type annotation */ public boolean usesTypeAnnotations() { return true; } } // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.