001: package net.sf.saxon.tree;
002:
003: import net.sf.saxon.event.Builder;
004: import net.sf.saxon.event.LocationProvider;
005: import net.sf.saxon.event.ReceiverOptions;
006: import net.sf.saxon.om.AttributeCollectionImpl;
007: import net.sf.saxon.om.DocumentInfo;
008: import net.sf.saxon.om.NodeInfo;
009: import net.sf.saxon.trans.DynamicError;
010: import net.sf.saxon.trans.XPathException;
011:
012: import java.util.ArrayList;
013:
014: /**
015: * The Builder class is responsible for taking a stream of SAX events and constructing
016: * a Document tree.
017: * @author Michael H. Kay
018: */
019:
020: public class TreeBuilder extends Builder
021:
022: {
023: private static AttributeCollectionImpl emptyAttributeCollection = new AttributeCollectionImpl(
024: null);
025:
026: private ParentNodeImpl currentNode;
027:
028: private NodeFactory nodeFactory;
029: private int[] size = new int[100]; // stack of number of children for each open node
030: private int depth = 0;
031: private ArrayList arrays = new ArrayList(20); // reusable arrays for creating nodes
032: private int pendingElement;
033: private int pendingLocationId;
034: private AttributeCollectionImpl attributes;
035: private int[] namespaces;
036: private int namespacesUsed;
037:
038: private int nextNodeNumber = 1;
039: private static final int[] EMPTY_ARRAY_OF_INT = new int[0];
040:
041: /**
042: * create a Builder and initialise variables
043: */
044:
045: public TreeBuilder() {
046: nodeFactory = new DefaultNodeFactory();
047: // System.err.println("new TreeBuilder " + this);
048: }
049:
050: /**
051: * Set the Node Factory to use. If none is specified, the Builder uses its own.
052: */
053:
054: public void setNodeFactory(NodeFactory factory) {
055: nodeFactory = factory;
056: }
057:
058: ////////////////////////////////////////////////////////////////////////////////////////
059: // Implement the org.xml.sax.ContentHandler interface.
060: ////////////////////////////////////////////////////////////////////////////////////////
061:
062: /**
063: * Callback interface for SAX: not for application use
064: */
065:
066: public void open() throws XPathException {
067: // System.err.println("TreeBuilder: " + this + " Start document depth=" + depth);
068: //failed = false;
069: started = true;
070:
071: DocumentImpl doc;
072: if (currentRoot == null) {
073: // normal case
074: doc = new DocumentImpl();
075: currentRoot = doc;
076: } else {
077: // document node supplied by user
078: if (!(currentRoot instanceof DocumentImpl)) {
079: throw new DynamicError(
080: "Document node supplied is of wrong kind ("
081: + currentRoot.getClass().getName()
082: + ')');
083: }
084: doc = (DocumentImpl) currentRoot;
085: if (doc.getFirstChild() != null) {
086: throw new DynamicError("Supplied document is not empty");
087: }
088:
089: }
090:
091: doc.setSystemId(getSystemId());
092: doc.setConfiguration(config);
093: currentNode = doc;
094: depth = 0;
095: size[depth] = 0;
096: doc.sequence = 0;
097: //charBuffer = new StringBuffer(4096);
098: //doc.setCharacterBuffer(charBuffer);
099: if (lineNumbering) {
100: doc.setLineNumbering();
101: }
102:
103: super .open();
104: }
105:
106: /**
107: * Callback interface for SAX: not for application use
108: */
109:
110: public void close() throws XPathException {
111: // System.err.println("TreeBuilder: " + this + " End document");
112: if (currentNode == null)
113: return; // can be called twice on an error path
114: currentNode.compact(size[depth]);
115: currentNode = null;
116:
117: // we're not going to use this Builder again so give the garbage collector
118: // something to play with
119: arrays = null;
120:
121: super .close();
122: nodeFactory = null;
123:
124: }
125:
126: /**
127: * Notify the start of an element
128: */
129:
130: public void startElement(int nameCode, int typeCode,
131: int locationId, int properties) throws XPathException {
132: // System.err.println("TreeBuilder: " + this + " Start element depth=" + depth);
133:
134: pendingElement = nameCode;
135: pendingLocationId = locationId;
136: namespaces = null;
137: namespacesUsed = 0;
138: attributes = null;
139: }
140:
141: public void namespace(int namespaceCode, int properties) {
142: if (namespaces == null) {
143: namespaces = new int[5]; //TODO: reuse this array
144: }
145: if (namespacesUsed == namespaces.length) {
146: int[] ns2 = new int[namespaces.length * 2];
147: System.arraycopy(namespaces, 0, ns2, 0, namespacesUsed);
148: namespaces = ns2;
149: }
150: namespaces[namespacesUsed++] = namespaceCode;
151: }
152:
153: public void attribute(int nameCode, int typeCode,
154: CharSequence value, int locationId, int properties)
155: throws XPathException {
156:
157: // if ((properties & ReceiverOptions.DISABLE_ESCAPING) != 0) {
158: // throw new DynamicError("Cannot disable output escaping when writing a tree");
159: // }
160: properties &= ~ReceiverOptions.DISABLE_ESCAPING;
161:
162: if (attributes == null) {
163: attributes = new AttributeCollectionImpl(namePool);
164: }
165: // String attType = "CDATA";
166: // if (typeCode == Type.ID || (properties & ReceiverOptions.DTD_ID_ATTRIBUTE) != 0) {
167: // attType = "ID";
168: // }
169: attributes.addAttribute(nameCode, typeCode, value.toString(),
170: locationId, properties);
171: }
172:
173: public void startContent() throws XPathException {
174: // System.err.println("TreeBuilder: " + this + " startContent()");
175: if (attributes == null) {
176: attributes = emptyAttributeCollection;
177: } else {
178: attributes.compact();
179: }
180:
181: if (namespaces == null) {
182: namespaces = EMPTY_ARRAY_OF_INT;
183: }
184:
185: ElementImpl elem = nodeFactory.makeElementNode(currentNode,
186: pendingElement, attributes, namespaces, namespacesUsed,
187: pipe.getLocationProvider(), pendingLocationId,
188: nextNodeNumber++);
189:
190: namespaces = null;
191: namespacesUsed = 0;
192: attributes = null;
193:
194: // the initial array used for pointing to children will be discarded when the exact number
195: // of children in known. Therefore, it can be reused. So we allocate an initial array from
196: // a pool of reusable arrays. A nesting depth of >20 is so rare that we don't bother.
197:
198: while (depth >= arrays.size()) {
199: arrays.add(new NodeImpl[20]);
200: }
201: elem.useChildrenArray((NodeImpl[]) arrays.get(depth));
202:
203: currentNode.addChild(elem, size[depth]++);
204: if (depth >= size.length - 1) {
205: int[] newsize = new int[size.length * 2];
206: System.arraycopy(size, 0, newsize, 0, size.length);
207: size = newsize;
208: }
209: size[++depth] = 0;
210: namespacesUsed = 0;
211:
212: if (currentNode instanceof DocumentInfo) {
213: ((DocumentImpl) currentNode).setDocumentElement(elem);
214: }
215:
216: currentNode = elem;
217: }
218:
219: /**
220: * Notify the end of an element
221: */
222:
223: public void endElement() throws XPathException {
224: // System.err.println("End element depth=" + depth);
225: currentNode.compact(size[depth]);
226: depth--;
227: currentNode = (ParentNodeImpl) currentNode.getParent();
228: }
229:
230: /**
231: * Notify a text node. Adjacent text nodes must have already been merged
232: */
233:
234: public void characters(CharSequence chars, int locationId,
235: int properties) throws XPathException {
236: // System.err.println("Characters: " + chars.toString() + " depth=" + depth);
237: if (chars.length() > 0) {
238:
239: // if ((properties & ReceiverOptions.DISABLE_ESCAPING) != 0) {
240: // throw new DynamicError("Cannot disable output escaping when writing a tree");
241: // }
242:
243: // we rely on adjacent chunks of text having already been merged
244: //TextImpl n = new TextImpl(currentNode, bufferStart, length);
245: TextImpl n = new TextImpl(currentNode, chars.toString());
246: currentNode.addChild(n, size[depth]++);
247: }
248: }
249:
250: /**
251: * Notify a processing instruction
252: */
253:
254: public void processingInstruction(String name,
255: CharSequence remainder, int locationId, int properties) {
256: int nameCode = namePool.allocate("", "", name);
257: ProcInstImpl pi = new ProcInstImpl(nameCode, remainder
258: .toString());
259: currentNode.addChild(pi, size[depth]++);
260: LocationProvider locator = pipe.getLocationProvider();
261: if (locator != null) {
262: pi.setLocation(locator.getSystemId(locationId), locator
263: .getLineNumber(locationId));
264: }
265: }
266:
267: /**
268: * Notify a comment
269: */
270:
271: public void comment(CharSequence chars, int locationId,
272: int properties) throws XPathException {
273: CommentImpl comment = new CommentImpl(chars.toString());
274: currentNode.addChild(comment, size[depth]++);
275: }
276:
277: /**
278: * graftElement() allows an element node to be transferred from one tree to another.
279: * This is a dangerous internal interface which is used only to contruct a stylesheet
280: * tree from a stylesheet using the "literal result element as stylesheet" syntax.
281: * The supplied element is grafted onto the current element as its only child.
282: */
283:
284: public void graftElement(ElementImpl element) throws XPathException {
285: currentNode.addChild(element, size[depth]++);
286: }
287:
288: /**
289: * Set an unparsed entity URI for the document
290: */
291:
292: public void setUnparsedEntity(String name, String uri,
293: String publicId) {
294: ((DocumentImpl) currentRoot).setUnparsedEntity(name, uri,
295: publicId);
296: }
297:
298: //////////////////////////////////////////////////////////////////////////////
299: // Inner class DefaultNodeFactory. This creates the nodes in the tree.
300: // It can be overridden, e.g. when building the stylesheet tree
301: //////////////////////////////////////////////////////////////////////////////
302:
303: private static class DefaultNodeFactory implements NodeFactory {
304:
305: public ElementImpl makeElementNode(NodeInfo parent,
306: int nameCode, AttributeCollectionImpl attlist,
307: int[] namespaces, int namespacesUsed,
308: LocationProvider locator, int locationId,
309: int sequenceNumber)
310:
311: {
312: ElementImpl e;
313: if (attlist.getLength() == 0 && namespacesUsed == 0) {
314: // for economy, use a simple ElementImpl node
315: e = new ElementImpl();
316: } else {
317: e = new ElementWithAttributes();
318: if (namespacesUsed > 0) {
319: ((ElementWithAttributes) e)
320: .setNamespaceDeclarations(namespaces,
321: namespacesUsed);
322: }
323: }
324: String baseURI = null;
325: int lineNumber = -1;
326:
327: if (locator != null) {
328: baseURI = locator.getSystemId(locationId);
329: lineNumber = locator.getLineNumber(locationId);
330: }
331:
332: e.initialise(nameCode, attlist, parent, baseURI,
333: lineNumber, sequenceNumber);
334: return e;
335: }
336: }
337:
338: }
339:
340: //
341: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
342: // you may not use this file except in compliance with the License. You may obtain a copy of the
343: // License at http://www.mozilla.org/MPL/
344: //
345: // Software distributed under the License is distributed on an "AS IS" basis,
346: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
347: // See the License for the specific language governing rights and limitations under the License.
348: //
349: // The Original Code is: all this file.
350: //
351: // The Initial Developer of the Original Code is Michael H. Kay.
352: //
353: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
354: //
355: // Contributor(s): none.
356: //
|