001: package net.sf.saxon.om;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.value.Whitespace;
005: import net.sf.saxon.value.Value;
006: import net.sf.saxon.style.StandardNames;
007: import net.sf.saxon.event.Receiver;
008: import net.sf.saxon.event.Stripper;
009: import net.sf.saxon.pattern.NodeKindTest;
010: import net.sf.saxon.pattern.NodeTest;
011: import net.sf.saxon.trans.XPathException;
012: import net.sf.saxon.type.Type;
013:
014: /**
015: * A StrippedNode is a view of a node, in a virtual tree that has whitespace
016: * text nodes stripped from it. All operations on the node produce the same result
017: * as operations on the real underlying node, except that iterations over the axes
018: * take care to skip whitespace-only text nodes that are supposed to be stripped.
019: * Note that this class is only used in cases where a pre-built tree is supplied as
020: * the input to a transformation, and where the stylesheet does whitespace stripping;
021: * if a SAXSource or StreamSource is supplied, whitespace is stripped as the tree
022: * is built.
023: */
024:
025: public class StrippedNode implements NodeInfo, VirtualNode {
026:
027: protected NodeInfo node;
028: protected StrippedNode parent; // null means unknown
029: protected StrippedDocument docWrapper;
030:
031: protected StrippedNode() {
032: }
033:
034: /**
035: * This constructor is protected: nodes should be created using the makeWrapper
036: * factory method
037: * @param node The node to be wrapped
038: * @param parent The StrippedNode that wraps the parent of this node
039: */
040:
041: protected StrippedNode(NodeInfo node, StrippedNode parent) {
042: this .node = node;
043: this .parent = parent;
044: }
045:
046: /**
047: * Factory method to wrap a node with a wrapper that implements the Saxon
048: * NodeInfo interface.
049: * @param node The underlying node
050: * @param docWrapper The wrapper for the document node (must be supplied)
051: * @param parent The wrapper for the parent of the node (null if unknown)
052: * @return The new wrapper for the supplied node
053: */
054:
055: protected StrippedNode makeWrapper(NodeInfo node,
056: StrippedDocument docWrapper, StrippedNode parent) {
057: StrippedNode wrapper = new StrippedNode(node, parent);
058: wrapper.docWrapper = docWrapper;
059: return wrapper;
060: }
061:
062: /**
063: * Get the underlying DOM node, to implement the VirtualNode interface
064: */
065:
066: public Object getUnderlyingNode() {
067: return node;
068: }
069:
070: /**
071: * Get the configuration
072: */
073:
074: public Configuration getConfiguration() {
075: return node.getConfiguration();
076: }
077:
078: /**
079: * Get the name pool for this node
080: * @return the NamePool
081: */
082:
083: public NamePool getNamePool() {
084: return node.getNamePool();
085: }
086:
087: /**
088: * Return the type of node.
089: * @return one of the values Node.ELEMENT, Node.TEXT, Node.ATTRIBUTE, etc.
090: */
091:
092: public int getNodeKind() {
093: return node.getNodeKind();
094: }
095:
096: /**
097: * Get the typed value of the item
098: */
099:
100: public SequenceIterator getTypedValue() throws XPathException {
101: return node.getTypedValue();
102: }
103:
104: /**
105: * Get the typed value. The result of this method will always be consistent with the method
106: * {@link Item#getTypedValue()}. However, this method is often more convenient and may be
107: * more efficient, especially in the common case where the value is expected to be a singleton.
108: *
109: * @return the typed value. If requireSingleton is set to true, the result will always be an
110: * AtomicValue. In other cases it may be a Value representing a sequence whose items are atomic
111: * values.
112: * @since 8.5
113: */
114:
115: public Value atomize() throws XPathException {
116: return node.atomize();
117: }
118:
119: /**
120: * Get the type annotation
121: * @return 0 (there is no type annotation)
122: */
123:
124: public int getTypeAnnotation() {
125: return node.getTypeAnnotation();
126: }
127:
128: /**
129: * Determine whether this is the same node as another node. <br />
130: * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
131: * @return true if this Node object and the supplied Node object represent the
132: * same node in the tree.
133: */
134:
135: public boolean isSameNodeInfo(NodeInfo other) {
136: if (other instanceof StrippedNode) {
137: return node.isSameNodeInfo(((StrippedNode) other).node);
138: } else {
139: return node.isSameNodeInfo(other);
140: }
141: }
142:
143: /**
144: * Get the System ID for the node.
145: * @return the System Identifier of the entity in the source document containing the node,
146: * or null if not known. Note this is not the same as the base URI: the base URI can be
147: * modified by xml:base, but the system ID cannot.
148: */
149:
150: public String getSystemId() {
151: return node.getSystemId();
152: }
153:
154: public void setSystemId(String uri) {
155: node.setSystemId(uri);
156: }
157:
158: /**
159: * Get the Base URI for the node, that is, the URI used for resolving a relative URI contained
160: * in the node. In the JDOM model, base URIs are held only an the document level. We don't
161: * currently take any account of xml:base attributes.
162: */
163:
164: public String getBaseURI() {
165: return node.getBaseURI();
166: }
167:
168: /**
169: * Get line number
170: * @return the line number of the node in its original source document; or -1 if not available
171: */
172:
173: public int getLineNumber() {
174: return node.getLineNumber();
175: }
176:
177: /**
178: * Determine the relative position of this node and another node, in document order.
179: * The other node will always be in the same document.
180: * @param other The other node, whose position is to be compared with this node
181: * @return -1 if this node precedes the other node, +1 if it follows the other
182: * node, or 0 if they are the same node. (In this case, isSameNode() will always
183: * return true, and the two nodes will produce the same result for generateId())
184: */
185:
186: public int compareOrder(NodeInfo other) {
187: if (other instanceof StrippedNode) {
188: return node.compareOrder(((StrippedNode) other).node);
189: } else {
190: return node.compareOrder(other);
191: }
192: }
193:
194: /**
195: * Return the string value of the node. The interpretation of this depends on the type
196: * of node. For an element it is the accumulated character content of the element,
197: * including descendant elements.
198: * @return the string value of the node
199: */
200:
201: public String getStringValue() {
202: return getStringValueCS().toString();
203: }
204:
205: /**
206: * Get the value of the item as a CharSequence. This is in some cases more efficient than
207: * the version of the method that returns a String.
208: */
209:
210: public CharSequence getStringValueCS() {
211: // Might not be the same as the string value of the underlying node because of space stripping
212: switch (getNodeKind()) {
213: case Type.DOCUMENT:
214: case Type.ELEMENT:
215: AxisIterator iter = iterateAxis(Axis.DESCENDANT,
216: NodeKindTest.makeNodeKindTest(Type.TEXT));
217: FastStringBuffer sb = new FastStringBuffer(1024);
218: while (true) {
219: NodeInfo it = (NodeInfo) iter.next();
220: if (it == null) {
221: break;
222: }
223: sb.append(it.getStringValueCS());
224: }
225: return sb.condense();
226: default:
227: return node.getStringValueCS();
228: }
229: }
230:
231: /**
232: * Get name code. The name code is a coded form of the node name: two nodes
233: * with the same name code have the same namespace URI, the same local name,
234: * and the same prefix. By masking the name code with &0xfffff, you get a
235: * fingerprint: two nodes with the same fingerprint have the same local name
236: * and namespace URI.
237: * @see NamePool#allocate allocate
238: */
239:
240: public int getNameCode() {
241: return node.getNameCode();
242: }
243:
244: /**
245: * Get fingerprint. The fingerprint is a coded form of the expanded name
246: * of the node: two nodes
247: * with the same name code have the same namespace URI and the same local name.
248: * A fingerprint of -1 should be returned for a node with no name.
249: */
250:
251: public int getFingerprint() {
252: return node.getFingerprint();
253: }
254:
255: /**
256: * Get the local part of the name of this node. This is the name after the ":" if any.
257: * @return the local part of the name. For an unnamed node, returns null, except for
258: * un unnamed namespace node, which returns "".
259: */
260:
261: public String getLocalPart() {
262: return node.getLocalPart();
263: }
264:
265: /**
266: * Get the URI part of the name of this node. This is the URI corresponding to the
267: * prefix, or the URI of the default namespace if appropriate.
268: * @return The URI of the namespace of this node. For an unnamed node, return null.
269: * For a node with an empty prefix, return an empty string.
270: */
271:
272: public String getURI() {
273: return node.getURI();
274: }
275:
276: /**
277: * Get the prefix of the name of the node. This is defined only for elements and attributes.
278: * If the node has no prefix, or for other kinds of node, return a zero-length string.
279: *
280: * @return The prefix of the name of the node.
281: */
282:
283: public String getPrefix() {
284: return node.getPrefix();
285: }
286:
287: /**
288: * Get the display name of this node. For elements and attributes this is [prefix:]localname.
289: * For unnamed nodes, it is an empty string.
290: * @return The display name of this node.
291: * For a node with no name, return an empty string.
292: */
293:
294: public String getDisplayName() {
295: return node.getDisplayName();
296: }
297:
298: /**
299: * Get the NodeInfo object representing the parent of this node
300: */
301:
302: public NodeInfo getParent() {
303: if (parent == null) {
304: NodeInfo realParent = node.getParent();
305: if (realParent != null) {
306: parent = makeWrapper(realParent, docWrapper, null);
307: }
308: }
309: return parent;
310: }
311:
312: /**
313: * Return an iteration over the nodes reached by the given axis from this node
314: * @param axisNumber the axis to be used
315: * @return a SequenceIterator that scans the nodes reached by the axis in turn.
316: */
317:
318: public AxisIterator iterateAxis(byte axisNumber) {
319: switch (axisNumber) {
320: case Axis.ATTRIBUTE:
321: case Axis.NAMESPACE:
322: return new WrappingIterator(node.iterateAxis(axisNumber),
323: this );
324: case Axis.CHILD:
325: return new StrippingIterator(node.iterateAxis(axisNumber),
326: this );
327: case Axis.FOLLOWING_SIBLING:
328: case Axis.PRECEDING_SIBLING:
329: StrippedNode parent = (StrippedNode) getParent();
330: if (parent == null) {
331: return EmptyIterator.getInstance();
332: } else {
333: return new StrippingIterator(node
334: .iterateAxis(axisNumber), parent);
335: }
336: default:
337: return new StrippingIterator(node.iterateAxis(axisNumber),
338: null);
339: }
340: }
341:
342: /**
343: * Return an iteration over the nodes reached by the given axis from this node
344: * @param axisNumber the axis to be used
345: * @param nodeTest A pattern to be matched by the returned nodes
346: * @return a SequenceIterator that scans the nodes reached by the axis in turn.
347: */
348:
349: public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) {
350: return new Navigator.AxisFilter(iterateAxis(axisNumber),
351: nodeTest);
352: }
353:
354: /**
355: * Get the value of a given attribute of this node
356: * @param fingerprint The fingerprint of the attribute name
357: * @return the attribute value if it exists or null if not
358: */
359:
360: public String getAttributeValue(int fingerprint) {
361: return node.getAttributeValue(fingerprint);
362: }
363:
364: /**
365: * Get the root node - always a document node with this tree implementation
366: * @return the NodeInfo representing the containing document
367: */
368:
369: public NodeInfo getRoot() {
370: return docWrapper;
371: }
372:
373: /**
374: * Get the root (document) node
375: * @return the DocumentInfo representing the containing document
376: */
377:
378: public DocumentInfo getDocumentRoot() {
379: return docWrapper;
380: }
381:
382: /**
383: * Determine whether the node has any children. <br />
384: * Note: the result is equivalent to <br />
385: * getEnumeration(Axis.CHILD, AnyNodeTest.getInstance()).hasNext()
386: */
387:
388: public boolean hasChildNodes() {
389: return node.hasChildNodes();
390: }
391:
392: /**
393: * Get a character string that uniquely identifies this node.
394: * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
395: * @return a string that uniquely identifies this node, within this
396: * document. The calling code prepends information to make the result
397: * unique across all documents.
398: */
399:
400: public String generateId() {
401: return node.generateId();
402: }
403:
404: /**
405: * Get the document number of the document containing this node. For a free-standing
406: * orphan node, just return the hashcode.
407: */
408:
409: public int getDocumentNumber() {
410: return docWrapper.getDocumentNumber();
411: }
412:
413: /**
414: * Copy this node to a given outputter (deep copy)
415: */
416:
417: public void copy(Receiver out, int whichNamespaces,
418: boolean copyAnnotations, int locationId)
419: throws XPathException {
420: // The underlying code does not do whitespace stripping. So we need to interpose
421: // a stripper.
422: Stripper stripper = docWrapper.getStripper().getAnother();
423: stripper.setUnderlyingReceiver(out);
424: node.copy(stripper, whichNamespaces, copyAnnotations,
425: locationId);
426: }
427:
428: /**
429: * Output all namespace nodes associated with this element. Does nothing if
430: * the node is not an element.
431: * @param out The relevant outputter
432: * @param includeAncestors True if namespaces declared on ancestor elements must
433: */
434:
435: public void sendNamespaceDeclarations(Receiver out,
436: boolean includeAncestors) throws XPathException {
437: node.sendNamespaceDeclarations(out, includeAncestors);
438: }
439:
440: /**
441: * Get all namespace undeclarations and undeclarations defined on this element.
442: *
443: * @param buffer If this is non-null, and the result array fits in this buffer, then the result
444: * may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
445: * @return An array of integers representing the namespace declarations and undeclarations present on
446: * this element. For a node other than an element, return null. Otherwise, the returned array is a
447: * sequence of namespace codes, whose meaning may be interpreted by reference to the name pool. The
448: * top half word of each namespace code represents the prefix, the bottom half represents the URI.
449: * If the bottom half is zero, then this is a namespace undeclaration rather than a declaration.
450: * The XML namespace is never included in the list. If the supplied array is larger than required,
451: * then the first unused entry will be set to -1.
452: * <p/>
453: * <p>For a node other than an element, the method returns null.</p>
454: */
455:
456: public int[] getDeclaredNamespaces(int[] buffer) {
457: return node.getDeclaredNamespaces(buffer);
458: }
459:
460: /**
461: * A WrappingIterator delivers wrappers for the nodes delivered
462: * by its underlying iterator. It is used when no whitespace stripping
463: * is actually needed, e.g. for the attribute axis. But we still need to
464: * create wrappers, so that further iteration remains in the virtual layer
465: * rather than switching to the real nodes.
466: */
467:
468: private final class WrappingIterator implements AxisIterator,
469: AtomizableIterator {
470:
471: AxisIterator base;
472: StrippedNode parent;
473: Item current;
474: boolean atomizing = false;
475:
476: /**
477: * Create a WrappingIterator
478: * @param base The underlying iterator
479: * @param parent If all the nodes to be wrapped have the same parent,
480: * it can be specified here. Otherwise specify null.
481: */
482:
483: public WrappingIterator(AxisIterator base, StrippedNode parent) {
484: this .base = base;
485: this .parent = parent;
486: }
487:
488: public Item next() {
489: Item n = base.next();
490: if (n instanceof NodeInfo && !atomizing) {
491: current = makeWrapper((NodeInfo) n, docWrapper, parent);
492: } else {
493: current = n;
494: }
495: return current;
496: }
497:
498: public Item current() {
499: return current;
500: }
501:
502: public int position() {
503: return base.position();
504: }
505:
506: public SequenceIterator getAnother() {
507: return new WrappingIterator((AxisIterator) base
508: .getAnother(), parent);
509: }
510:
511: /**
512: * Get properties of this iterator, as a bit-significant integer.
513: *
514: * @return the properties of this iterator. This will be some combination of
515: * properties such as {@link GROUNDED}, {@link LAST_POSITION_FINDER},
516: * and {@link LOOKAHEAD}. It is always
517: * acceptable to return the value zero, indicating that there are no known special properties.
518: * It is acceptable for the properties of the iterator to change depending on its state.
519: */
520:
521: public int getProperties() {
522: return ATOMIZABLE;
523: }
524:
525: /**
526: * Indicate that any nodes returned in the sequence will be atomized. This
527: * means that if it wishes to do so, the implementation can return the typed
528: * values of the nodes rather than the nodes themselves. The implementation
529: * is free to ignore this hint.
530: * @param atomizing true if the caller of this iterator will atomize any
531: * nodes that are returned, and is therefore willing to accept the typed
532: * value of the nodes instead of the nodes themselves.
533: */
534:
535: public void setIsAtomizing(boolean atomizing) {
536: this .atomizing = true;
537: if (base instanceof AtomizableIterator) {
538: ((AtomizableIterator) base).setIsAtomizing(atomizing);
539: }
540: }
541:
542: } // end of class WrappingIterator
543:
544: /**
545: * A StrippingIterator delivers wrappers for the nodes delivered
546: * by its underlying iterator. It is used when whitespace stripping
547: * may be needed, e.g. for the child axis. It examines all text nodes
548: * encountered to see if they need to be stripped, and if so, it
549: * skips them.
550: */
551:
552: private final class StrippingIterator implements AxisIterator {
553:
554: AxisIterator base;
555: StrippedNode parent;
556: NodeInfo currentVirtualNode;
557: int position;
558:
559: /**
560: * Create a StrippingIterator
561: * @param base The underlying iterator
562: * @param parent If all the nodes to be wrapped have the same parent,
563: * it can be specified here. Otherwise specify null.
564: */
565:
566: public StrippingIterator(AxisIterator base, StrippedNode parent) {
567: this .base = base;
568: this .parent = parent;
569: position = 0;
570: }
571:
572: public Item next() {
573: NodeInfo nextRealNode;
574: while (true) {
575: nextRealNode = (NodeInfo) base.next();
576: if (nextRealNode == null) {
577: return null;
578: }
579: if (isPreserved(nextRealNode)) {
580: break;
581: }
582: // otherwise skip this whitespace text node
583: }
584:
585: currentVirtualNode = makeWrapper(nextRealNode, docWrapper,
586: parent);
587: position++;
588: return currentVirtualNode;
589: }
590:
591: private boolean isPreserved(NodeInfo nextRealNode) {
592: if (nextRealNode.getNodeKind() != Type.TEXT) {
593: return true;
594: }
595: if (!Whitespace.isWhite(nextRealNode.getStringValueCS())) {
596: return true;
597: }
598: NodeInfo actualParent = (parent == null ? nextRealNode
599: .getParent() : parent.node);
600:
601: if (docWrapper.containsPreserveSpace()) {
602: NodeInfo p = actualParent;
603: // the document contains one or more xml:space="preserve" attributes, so we need to see
604: // if one of them is on an ancestor of this node
605: while (p.getNodeKind() == Type.ELEMENT) {
606: String val = p
607: .getAttributeValue(StandardNames.XML_SPACE);
608: if (val != null) {
609: if ("preserve".equals(val)) {
610: return true;
611: } else if ("default".equals(val)) {
612: break;
613: }
614: }
615: p = p.getParent();
616: }
617: }
618:
619: if (docWrapper.getStripper()
620: .isSpacePreserving(actualParent) == Stripper.ALWAYS_PRESERVE) {
621: return true;
622: }
623: return false;
624: }
625:
626: public Item current() {
627: return currentVirtualNode;
628: }
629:
630: public int position() {
631: return position;
632: }
633:
634: public SequenceIterator getAnother() {
635: return new StrippingIterator((AxisIterator) base
636: .getAnother(), parent);
637: }
638:
639: /**
640: * Get properties of this iterator, as a bit-significant integer.
641: *
642: * @return the properties of this iterator. This will be some combination of
643: * properties such as {@link GROUNDED}, {@link LAST_POSITION_FINDER},
644: * and {@link LOOKAHEAD}. It is always
645: * acceptable to return the value zero, indicating that there are no known special properties.
646: * It is acceptable for the properties of the iterator to change depending on its state.
647: */
648:
649: public int getProperties() {
650: return 0;
651: }
652:
653: } // end of class StrippingIterator
654:
655: }
656:
657: //
658: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
659: // you may not use this file except in compliance with the License. You may obtain a copy of the
660: // License at http://www.mozilla.org/MPL/
661: //
662: // Software distributed under the License is distributed on an "AS IS" basis,
663: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
664: // See the License for the specific language governing rights and limitations under the License.
665: //
666: // The Original Code is: all this file.
667: //
668: // The Initial Developer of the Original Code is
669: // Michael H. Kay.
670: //
671: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
672: //
673: // Contributor(s): none.
674: //
|