001: package net.sf.saxon.pull;
002:
003: import net.sf.saxon.event.PipelineConfiguration;
004: import net.sf.saxon.om.*;
005: import net.sf.saxon.tinytree.TinyNodeImpl;
006: import net.sf.saxon.tinytree.TinyTreeWalker;
007: import net.sf.saxon.trans.XPathException;
008: import net.sf.saxon.type.Type;
009: import net.sf.saxon.value.AtomicValue;
010:
011: import javax.xml.transform.SourceLocator;
012: import java.util.Stack;
013:
014: /**
015: * This implementation of the Saxon pull interface starts from any NodeInfo,
016: * and returns the events corresponding to that node and its descendants (including
017: * their attributes and namespaces). This works with any tree model: alternative
018: * implementations may be available that take advantage of particular implementations
019: * of the tree model.
020: */
021:
022: public class TreeWalker implements PullProvider, SourceLocator {
023:
024: private NodeInfo startNode;
025: private NodeInfo currentNode;
026: private int currentEvent;
027: private Stack iteratorStack = new Stack();
028: private PipelineConfiguration pipe;
029:
030: /**
031: * Factory method to get a tree walker starting an a given node
032: * @param startNode the start node
033: * @return a PullProvider that delivers events associated with the subtree starting at the given node
034: */
035:
036: public static PullProvider makeTreeWalker(NodeInfo startNode) {
037: if (startNode instanceof UnconstructedParent) {
038: return ((UnconstructedParent) startNode).getPuller();
039: }
040: if (startNode instanceof TinyNodeImpl) {
041: switch (startNode.getNodeKind()) {
042: case Type.DOCUMENT:
043: case Type.ELEMENT:
044: return new TinyTreeWalker((TinyNodeImpl) startNode);
045: default:
046: return new PullFromIterator(SingletonIterator
047: .makeIterator(startNode));
048: }
049:
050: } else {
051: return new TreeWalker(startNode);
052: }
053: }
054:
055: /**
056: * Private constructor: the class should be instantiated using the static factory method
057: * @param startNode
058: */
059:
060: private TreeWalker(NodeInfo startNode) {
061: this .startNode = startNode;
062: }
063:
064: /**
065: * Set configuration information. This must only be called before any events
066: * have been read.
067: */
068:
069: public void setPipelineConfiguration(PipelineConfiguration pipe) {
070: this .pipe = pipe;
071: }
072:
073: /**
074: * Get configuration information.
075: */
076:
077: public PipelineConfiguration getPipelineConfiguration() {
078: return pipe;
079: }
080:
081: /**
082: * Get the next event
083: *
084: * @return an integer code indicating the type of event. The code
085: * {@link #END_OF_INPUT} is returned if there are no more events to return.
086: */
087:
088: public int next() throws XPathException {
089: switch (currentEvent) {
090: case START_OF_INPUT:
091: currentNode = startNode;
092: switch (currentNode.getNodeKind()) {
093: case Type.DOCUMENT:
094: currentEvent = START_DOCUMENT;
095: break;
096: case Type.ELEMENT:
097: currentEvent = START_ELEMENT;
098: break;
099: case Type.TEXT:
100: currentEvent = TEXT;
101: break;
102: case Type.COMMENT:
103: currentEvent = COMMENT;
104: break;
105: case Type.PROCESSING_INSTRUCTION:
106: currentEvent = PROCESSING_INSTRUCTION;
107: break;
108: case Type.ATTRIBUTE:
109: currentEvent = ATTRIBUTE;
110: break;
111: case Type.NAMESPACE:
112: currentEvent = NAMESPACE;
113: break;
114: }
115: return currentEvent;
116:
117: case START_DOCUMENT:
118: case START_ELEMENT:
119: AxisIterator kids = currentNode.iterateAxis(Axis.CHILD);
120: iteratorStack.push(kids);
121:
122: currentNode = (NodeInfo) kids.next();
123: if (currentNode != null) {
124: switch (currentNode.getNodeKind()) {
125: case Type.ELEMENT:
126: currentEvent = START_ELEMENT;
127: break;
128: case Type.TEXT:
129: currentEvent = TEXT;
130: break;
131: case Type.COMMENT:
132: currentEvent = COMMENT;
133: break;
134: case Type.PROCESSING_INSTRUCTION:
135: currentEvent = PROCESSING_INSTRUCTION;
136: break;
137: }
138: return currentEvent;
139: } else {
140: iteratorStack.pop();
141: if (iteratorStack.isEmpty()) {
142: currentNode = startNode;
143: }
144: if (currentEvent == START_DOCUMENT) {
145: currentEvent = END_DOCUMENT;
146: } else {
147: currentEvent = END_ELEMENT;
148: }
149: return currentEvent;
150: }
151: case TEXT:
152: case COMMENT:
153: case PROCESSING_INSTRUCTION:
154: case END_ELEMENT:
155: if (iteratorStack.isEmpty()) {
156: if (currentNode == startNode) {
157: currentNode = null;
158: currentEvent = END_OF_INPUT;
159: } else {
160: currentNode = startNode;
161: if (currentNode.getNodeKind() == Type.ELEMENT) {
162: currentEvent = END_ELEMENT;
163: } else {
164: currentEvent = END_DOCUMENT;
165: }
166: }
167: return currentEvent;
168: }
169: do {
170: AxisIterator siblings = (AxisIterator) iteratorStack
171: .peek();
172: currentNode = (NodeInfo) siblings.next();
173: if (currentNode == null) {
174: iteratorStack.pop();
175: if (iteratorStack.isEmpty()) {
176: currentNode = startNode;
177: if (currentNode.getNodeKind() == Type.ELEMENT) {
178: currentEvent = END_ELEMENT;
179: } else {
180: currentEvent = END_DOCUMENT;
181: }
182: return currentEvent;
183: }
184: AxisIterator uncles = (AxisIterator) iteratorStack
185: .peek();
186: currentNode = (NodeInfo) uncles.current();
187: if (currentNode.getNodeKind() == Type.DOCUMENT) {
188: currentEvent = END_DOCUMENT;
189: } else {
190: currentEvent = END_ELEMENT;
191: }
192: return currentEvent;
193: } else {
194: switch (currentNode.getNodeKind()) {
195: case Type.ELEMENT:
196: currentEvent = START_ELEMENT;
197: break;
198: case Type.TEXT:
199: currentEvent = TEXT;
200: break;
201: case Type.COMMENT:
202: currentEvent = COMMENT;
203: break;
204: case Type.PROCESSING_INSTRUCTION:
205: currentEvent = PROCESSING_INSTRUCTION;
206: break;
207: }
208: return currentEvent;
209: }
210: } while (true);
211:
212: case ATTRIBUTE:
213: case NAMESPACE:
214: case END_DOCUMENT:
215: currentEvent = END_OF_INPUT;
216: return currentEvent;
217:
218: case END_OF_INPUT:
219: throw new IllegalStateException(
220: "Cannot call next() when input is exhausted");
221:
222: default:
223: throw new IllegalStateException("Unrecognized event "
224: + currentEvent);
225:
226: }
227: }
228:
229: /**
230: * Get the event most recently returned by next(), or by other calls that change
231: * the position, for example getStringValue() and skipToMatchingEnd(). This
232: * method does not change the position of the PullProvider.
233: *
234: * @return the current event
235: */
236:
237: public int current() {
238: return currentEvent;
239: }
240:
241: /**
242: * Get the attributes associated with the current element. This method must
243: * be called only after a START_ELEMENT event has been notified. The contents
244: * of the returned AttributeCollection are guaranteed to remain unchanged
245: * until the next START_ELEMENT event, but may be modified thereafter. The object
246: * should not be modified by the client.
247: * <p/>
248: * <p>Attributes may be read before or after reading the namespaces of an element,
249: * but must not be read after the first child node has been read, or after calling
250: * one of the methods skipToEnd(), getStringValue(), or getTypedValue().</p>
251: *
252: * @return an AttributeCollection representing the attributes of the element
253: * that has just been notified.
254: */
255:
256: public AttributeCollection getAttributes() throws XPathException {
257: if (currentNode.getNodeKind() == Type.ELEMENT) {
258: AttributeCollectionImpl atts = new AttributeCollectionImpl(
259: getNamePool());
260: SequenceIterator iter = currentNode
261: .iterateAxis(Axis.ATTRIBUTE);
262: while (true) {
263: NodeInfo node = (NodeInfo) iter.next();
264: if (node == null) {
265: break;
266: }
267: atts.addAttribute(node.getNameCode(), node
268: .getTypeAnnotation(), node.getStringValue(), 0,
269: 0);
270: }
271: return atts;
272: } else {
273: throw new IllegalStateException(
274: "getAttributes() called when current event is not ELEMENT_START");
275: }
276: }
277:
278: /**
279: * Get the namespace declarations associated with the current element. This method must
280: * be called only after a START_ELEMENT event has been notified. In the case of a top-level
281: * START_ELEMENT event (that is, an element that either has no parent node, or whose parent
282: * is not included in the sequence being read), the NamespaceDeclarations object returned
283: * will contain a namespace declaration for each namespace that is in-scope for this element
284: * node. In the case of a non-top-level element, the NamespaceDeclarations will contain
285: * a set of namespace declarations and undeclarations, representing the differences between
286: * this element and its parent.
287: * <p/>
288: * <p>It is permissible for this method to return namespace declarations that are redundant.</p>
289: * <p/>
290: * <p>The NamespaceDeclarations object is guaranteed to remain unchanged until the next START_ELEMENT
291: * event, but may then be overwritten. The object should not be modified by the client.</p>
292: * <p/>
293: * <p>Namespaces may be read before or after reading the attributes of an element,
294: * but must not be read after the first child node has been read, or after calling
295: * one of the methods skipToEnd(), getStringValue(), or getTypedValue().</p>*
296: */
297:
298: public NamespaceDeclarations getNamespaceDeclarations()
299: throws XPathException {
300: if (currentNode.getNodeKind() == Type.ELEMENT) {
301: if (iteratorStack.size() == 0) {
302: // get all inscope namespaces for a top-level element in the sequence.
303: int[] codes = new NamespaceIterator(currentNode, null)
304: .getInScopeNamespaceCodes();
305: return new NamespaceDeclarationsImpl(getNamePool(),
306: codes);
307: } else {
308: // only namespace declarations (and undeclarations) on this element are required
309: return new NamespaceDeclarationsImpl(getNamePool(),
310: currentNode.getDeclaredNamespaces(nsBuffer));
311: }
312: }
313: throw new IllegalStateException(
314: "getNamespaceDeclarations() called when current event is not ELEMENT_START");
315: }
316:
317: private int[] nsBuffer = new int[10];
318:
319: /**
320: * Skip the current subtree. This method may be called only immediately after
321: * a START_DOCUMENT or START_ELEMENT event. This call returns the matching
322: * END_DOCUMENT or END_ELEMENT event; the next call on next() will return
323: * the event following the END_DOCUMENT or END_ELEMENT.
324: */
325:
326: public int skipToMatchingEnd() throws XPathException {
327: // For this implementation, we simply leave the current node unchanged, and change
328: // the current event
329: switch (currentEvent) {
330: case START_DOCUMENT:
331: currentEvent = END_DOCUMENT;
332: return currentEvent;
333: case START_ELEMENT:
334: currentEvent = END_ELEMENT;
335: return currentEvent;
336: default:
337: throw new IllegalStateException(
338: "Cannot call skipToMatchingEnd() except when at start of element or document");
339: }
340: }
341:
342: /**
343: * Close the event reader. This indicates that no further events are required.
344: * It is not necessary to close an event reader after {@link #END_OF_INPUT} has
345: * been reported, but it is recommended to close it if reading terminates
346: * prematurely. Once an event reader has been closed, the effect of further
347: * calls on next() is undefined.
348: */
349:
350: public void close() {
351: // no action
352: }
353:
354: /**
355: * Get the namePool used to lookup all name codes and namespace codes
356: *
357: * @return the namePool
358: */
359:
360: public NamePool getNamePool() {
361: return pipe.getConfiguration().getNamePool();
362: }
363:
364: /**
365: * Get the nameCode identifying the name of the current node. This method
366: * can be used after the {@link #START_ELEMENT}, {@link #PROCESSING_INSTRUCTION},
367: * {@link #ATTRIBUTE}, or {@link #NAMESPACE} events. With some PullProvider implementations,
368: * including this one, it can also be used after {@link #END_ELEMENT}.
369: * If called at other times, the result is undefined and may result in an IllegalStateException.
370: * If called when the current node is an unnamed namespace node (a node representing the default namespace)
371: * the returned value is -1.
372: * @return the nameCode. The nameCode can be used to obtain the prefix, local name,
373: * and namespace URI from the name pool.
374: */
375:
376: public int getNameCode() {
377: return currentNode.getNameCode();
378: }
379:
380: /**
381: * Get the fingerprint of the name of the element. This is similar to the nameCode, except that
382: * it does not contain any information about the prefix: so two elements with the same fingerprint
383: * have the same name, excluding prefix. This method
384: * can be used after the {@link #START_ELEMENT}, {@link #END_ELEMENT}, {@link #PROCESSING_INSTRUCTION},
385: * {@link #ATTRIBUTE}, or {@link #NAMESPACE} events.
386: * If called at other times, the result is undefined and may result in an IllegalStateException.
387: * If called when the current node is an unnamed namespace node (a node representing the default namespace)
388: * the returned value is -1.
389: *
390: * @return the fingerprint. The fingerprint can be used to obtain the local name
391: * and namespace URI from the name pool.
392: */
393:
394: public int getFingerprint() {
395: return currentNode.getFingerprint();
396: }
397:
398: /**
399: * Get the string value of the current attribute, text node, processing-instruction,
400: * or atomic value.
401: * This method cannot be used to obtain the string value of an element, or of a namespace
402: * node. If the most recent event was anything other than {@link #START_ELEMENT}, {@link #TEXT},
403: * {@link #PROCESSING_INSTRUCTION}, or {@link #ATOMIC_VALUE}, the result is undefined.
404: */
405:
406: public CharSequence getStringValue() throws XPathException {
407: if (currentNode.getNodeKind() == Type.ELEMENT) {
408: skipToMatchingEnd();
409: }
410: return currentNode.getStringValueCS();
411: }
412:
413: /**
414: * Get the type annotation of the current attribute or element node, or atomic value.
415: * The result of this method is undefined unless the most recent event was START_ELEMENT,
416: * START_CONTENT, ATTRIBUTE, or ATOMIC_VALUE.
417: *
418: * @return the type code. This code is the fingerprint of a type name, which may be
419: * resolved to a {@link net.sf.saxon.type.SchemaType} by access to the Configuration.
420: */
421:
422: public int getTypeAnnotation() {
423: return currentNode.getTypeAnnotation();
424: }
425:
426: /**
427: * Get an atomic value. This call may be used only when the last event reported was
428: * ATOMIC_VALUE. This indicates that the PullProvider is reading a sequence that contains
429: * a free-standing atomic value; it is never used when reading the content of a node.
430: */
431:
432: public AtomicValue getAtomicValue() {
433: throw new IllegalStateException();
434: }
435:
436: /**
437: * Get the location of the current event.
438: * For an event stream representing a real document, the location information
439: * should identify the location in the lexical XML source. For a constructed document, it should
440: * identify the location in the query or stylesheet that caused the node to be created.
441: * A value of null can be returned if no location information is available.
442: */
443:
444: public SourceLocator getSourceLocator() {
445: return this ;
446: }
447:
448: /**
449: * Return the public identifier for the current document event.
450: * <p/>
451: * <p>The return value is the public identifier of the document
452: * entity or of the external parsed entity in which the markup that
453: * triggered the event appears.</p>
454: *
455: * @return A string containing the public identifier, or
456: * null if none is available.
457: * @see #getSystemId
458: */
459: public String getPublicId() {
460: return null;
461: }
462:
463: /**
464: * Return the system identifier for the current document event.
465: * <p/>
466: * <p>The return value is the system identifier of the document
467: * entity or of the external parsed entity in which the markup that
468: * triggered the event appears.</p>
469: * <p/>
470: * <p>If the system identifier is a URL, the parser must resolve it
471: * fully before passing it to the application.</p>
472: *
473: * @return A string containing the system identifier, or null
474: * if none is available.
475: * @see #getPublicId
476: */
477: public String getSystemId() {
478: return currentNode.getSystemId();
479: }
480:
481: /**
482: * Return the line number where the current document event ends.
483: * <p/>
484: * <p><strong>Warning:</strong> The return value from the method
485: * is intended only as an approximation for the sake of error
486: * reporting; it is not intended to provide sufficient information
487: * to edit the character content of the original XML document.</p>
488: * <p/>
489: * <p>The return value is an approximation of the line number
490: * in the document entity or external parsed entity where the
491: * markup that triggered the event appears.</p>
492: *
493: * @return The line number, or -1 if none is available.
494: * @see #getColumnNumber
495: */
496: public int getLineNumber() {
497: return currentNode.getLineNumber();
498: }
499:
500: /**
501: * Return the character position where the current document event ends.
502: * <p/>
503: * <p><strong>Warning:</strong> The return value from the method
504: * is intended only as an approximation for the sake of error
505: * reporting; it is not intended to provide sufficient information
506: * to edit the character content of the original XML document.</p>
507: * <p/>
508: * <p>The return value is an approximation of the column number
509: * in the document entity or external parsed entity where the
510: * markup that triggered the event appears.</p>
511: *
512: * @return The column number, or -1 if none is available.
513: * @see #getLineNumber
514: */
515: public int getColumnNumber() {
516: return -1;
517: }
518: }
519:
520: //
521: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
522: // you may not use this file except in compliance with the License. You may obtain a copy of the
523: // License at http://www.mozilla.org/MPL/
524: //
525: // Software distributed under the License is distributed on an "AS IS" basis,
526: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
527: // See the License for the specific language governing rights and limitations under the License.
528: //
529: // The Original Code is: all this file.
530: //
531: // The Initial Developer of the Original Code is Michael H. Kay.
532: //
533: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
534: //
535: // Contributor(s): none.
536: //
|