001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.Controller;
004: import net.sf.saxon.expr.XPathContext;
005: import net.sf.saxon.om.NodeInfo;
006: import net.sf.saxon.om.Orphan;
007: import net.sf.saxon.style.StandardNames;
008: import net.sf.saxon.trans.Mode;
009: import net.sf.saxon.trans.XPathException;
010: import net.sf.saxon.type.ComplexType;
011: import net.sf.saxon.type.SchemaType;
012: import net.sf.saxon.type.Type;
013: import net.sf.saxon.value.Whitespace;
014:
015: /**
016: * The Stripper class maintains details of which elements need to be stripped.
017: * The code is written to act as a SAX-like filter to do the stripping.
018: * @author Michael H. Kay
019: */
020:
021: public class Stripper extends ProxyReceiver {
022:
023: private boolean preserveAll; // true if all elements have whitespace preserved
024: private boolean stripAll; // true if all whitespace nodes are stripped
025:
026: // stripStack is used to hold information used while stripping nodes. We avoid allocating
027: // space on the tree itself to keep the size of nodes down. Each entry on the stack is two
028: // booleans, one indicates the current value of xml-space is "preserve", the other indicates
029: // that we are in a space-preserving element.
030:
031: // We implement our own stack to avoid the overhead of allocating objects. The two booleans
032: // are held as the ls bits of a byte.
033:
034: private byte[] stripStack = new byte[100];
035: private int top = 0;
036:
037: // We use a collection of rules to determine whether to strip spaces; a collection
038: // of rules is known as a Mode. (We are reusing the code for template rule matching)
039:
040: private Mode stripperMode;
041:
042: // Mode expects to test an Element, so we create a dummy element for it to test
043: private Orphan element;
044:
045: // Stripper needs a context (a) for evaluating patterns
046: // and (b) to provide reporting of rule conflicts.
047: private XPathContext context;
048:
049: /**
050: * Default constructor for use in subclasses
051: */
052:
053: protected Stripper() {
054: }
055:
056: /**
057: * create a Stripper and initialise variables
058: * @param stripperRules defines which elements have whitespace stripped. If
059: * null, all whitespace is preserved.
060: */
061:
062: public Stripper(Mode stripperRules) {
063: stripperMode = stripperRules;
064: preserveAll = (stripperRules == null);
065: stripAll = false;
066: }
067:
068: /**
069: * Get a clean copy of this stripper
070: */
071:
072: public Stripper getAnother() {
073: Stripper clone = new Stripper(stripperMode);
074: clone.setPipelineConfiguration(getPipelineConfiguration());
075: clone.stripAll = stripAll;
076: clone.preserveAll = preserveAll;
077: return clone;
078: }
079:
080: /**
081: * Specify that all whitespace nodes are to be stripped
082: */
083:
084: public void setStripAll() {
085: preserveAll = false;
086: stripAll = true;
087: }
088:
089: /**
090: * Determine if all whitespace is to be stripped (in this case, no further testing
091: * is needed)
092: */
093:
094: public boolean getStripAll() {
095: return stripAll;
096: }
097:
098: public void setPipelineConfiguration(PipelineConfiguration pipe) {
099: super .setPipelineConfiguration(pipe);
100: Controller controller = pipe.getController();
101: if (controller != null) {
102: context = controller.newXPathContext();
103: }
104: element = new Orphan(pipe.getConfiguration());
105: element.setNodeKind(Type.ELEMENT);
106: }
107:
108: /**
109: * Decide whether an element is in the set of white-space preserving element types
110: * @param nameCode Identifies the name of the element whose whitespace is to
111: * be preserved
112: * @return ALWAYS_PRESERVE if the element is in the set of white-space preserving
113: * element types, ALWAYS_STRIP if the element is to be stripped regardless of the
114: * xml:space setting, and STRIP_DEFAULT otherwise
115: */
116:
117: public byte isSpacePreserving(int nameCode) {
118: try {
119: if (preserveAll)
120: return ALWAYS_PRESERVE;
121: if (stripAll)
122: return STRIP_DEFAULT;
123: element.setNameCode(nameCode);
124: Object rule = stripperMode.getRule(element, context);
125: if (rule == null)
126: return ALWAYS_PRESERVE;
127: return (((Boolean) rule).booleanValue() ? ALWAYS_PRESERVE
128: : STRIP_DEFAULT);
129: } catch (XPathException err) {
130: return ALWAYS_PRESERVE;
131: }
132: }
133:
134: public static final byte ALWAYS_PRESERVE = 0x01; // whitespace always preserved (e.g. xsl:text)
135: public static final byte ALWAYS_STRIP = 0x02; // whitespace always stripped (e.g. xsl:choose)
136: public static final byte STRIP_DEFAULT = 0x00; // no special action
137: public static final byte PRESERVE_PARENT = 0x04; // parent element specifies xml:space="preserve"
138: public static final byte CANNOT_STRIP = 0x08; // type annotation indicates simple typed content
139:
140: /**
141: * Decide whether an element is in the set of white-space preserving element types.
142: * This version of the method is useful in cases where getting the namecode of the
143: * element is potentially expensive, e.g. with DOM nodes.
144: * @param element Identifies the element whose whitespace is possibly to
145: * be preserved
146: * @return ALWAYS_PRESERVE if the element is in the set of white-space preserving
147: * element types, ALWAYS_STRIP if the element is to be stripped regardless of the
148: * xml:space setting, and STRIP_DEFAULT otherwise
149: */
150:
151: public byte isSpacePreserving(NodeInfo element) {
152: try {
153: if (preserveAll)
154: return ALWAYS_PRESERVE;
155: if (stripAll)
156: return STRIP_DEFAULT;
157: Object rule = stripperMode.getRule(element, context);
158: if (rule == null)
159: return ALWAYS_PRESERVE;
160: return (((Boolean) rule).booleanValue() ? ALWAYS_PRESERVE
161: : STRIP_DEFAULT);
162: } catch (XPathException err) {
163: return ALWAYS_PRESERVE;
164: }
165: }
166:
167: /**
168: * Callback interface for SAX: not for application use
169: */
170:
171: public void open() throws XPathException {
172: // System.err.println("Stripper#startDocument()");
173: top = 0;
174: stripStack[top] = ALWAYS_PRESERVE; // {xml:preserve = false, preserve this element = true}
175: super .open();
176: }
177:
178: public void startElement(int nameCode, int typeCode,
179: int locationId, int properties) throws XPathException {
180: // System.err.println("startElement " + nameCode);
181: super .startElement(nameCode, typeCode, locationId, properties);
182:
183: byte preserveParent = stripStack[top];
184: byte preserve = (byte) (preserveParent & PRESERVE_PARENT);
185:
186: byte elementStrip = isSpacePreserving(nameCode);
187: if (elementStrip == ALWAYS_PRESERVE) {
188: preserve |= ALWAYS_PRESERVE;
189: } else if (elementStrip == ALWAYS_STRIP) {
190: preserve |= ALWAYS_STRIP;
191: }
192: if (preserve == 0 && typeCode != -1
193: && typeCode != StandardNames.XDT_UNTYPED) {
194: // if the element has simple content, whitespace stripping is disabled
195: SchemaType type = getConfiguration()
196: .getSchemaType(typeCode);
197: if (type.isSimpleType()
198: || ((ComplexType) type).isSimpleContent()) {
199: preserve |= CANNOT_STRIP;
200: }
201: }
202:
203: // put "preserve" value on top of stack
204:
205: top++;
206: if (top >= stripStack.length) {
207: byte[] newStack = new byte[top * 2];
208: System.arraycopy(stripStack, 0, newStack, 0, top);
209: stripStack = newStack;
210: }
211: stripStack[top] = preserve;
212: }
213:
214: public void attribute(int nameCode, int typeCode,
215: CharSequence value, int locationId, int properties)
216: throws XPathException {
217:
218: // test for xml:space="preserve" | "default"
219:
220: if ((nameCode & 0xfffff) == StandardNames.XML_SPACE) {
221: if (value.toString().equals("preserve")) {
222: stripStack[top] |= PRESERVE_PARENT;
223: } else {
224: stripStack[top] &= ~PRESERVE_PARENT;
225: }
226: }
227: super .attribute(nameCode, typeCode, value, locationId,
228: properties);
229: }
230:
231: /**
232: * Handle an end-of-element event
233: */
234:
235: public void endElement() throws XPathException {
236: super .endElement();
237: top--;
238: }
239:
240: /**
241: * Handle a text node
242: */
243:
244: public void characters(CharSequence chars, int locationId,
245: int properties) throws XPathException {
246: // assume adjacent chunks of text are already concatenated
247:
248: if (chars.length() > 0) {
249: if ((((stripStack[top] & (ALWAYS_PRESERVE | PRESERVE_PARENT | CANNOT_STRIP)) != 0) && (stripStack[top] & ALWAYS_STRIP) == 0)
250: || !Whitespace.isWhite(chars)) {
251: super .characters(chars, locationId, properties);
252: }
253: }
254: }
255:
256: }
257:
258: //
259: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
260: // you may not use this file except in compliance with the License. You may obtain a copy of the
261: // License at http://www.mozilla.org/MPL/
262: //
263: // Software distributed under the License is distributed on an "AS IS" basis,
264: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
265: // See the License for the specific language governing rights and limitations under the License.
266: //
267: // The Original Code is: all this file.
268: //
269: // The Initial Developer of the Original Code is Michael H. Kay.
270: //
271: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
272: //
273: // Contributor(s): none.
274: //
|