001: /*
002: * Copyright 1999-2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: TreeWalker.java,v 1.3 2005/03/14 20:38:36 minchau Exp $
018: */
019: package org.apache.xml.serializer;
020:
021: import java.io.File;
022:
023: import org.apache.xml.serializer.utils.AttList;
024: import org.apache.xml.serializer.utils.DOM2Helper;
025: import org.w3c.dom.Comment;
026: import org.w3c.dom.Element;
027: import org.w3c.dom.EntityReference;
028: import org.w3c.dom.NamedNodeMap;
029: import org.w3c.dom.Node;
030: import org.w3c.dom.ProcessingInstruction;
031: import org.w3c.dom.Text;
032:
033: import org.xml.sax.ContentHandler;
034: import org.xml.sax.Locator;
035: import org.xml.sax.ext.LexicalHandler;
036: import org.xml.sax.helpers.LocatorImpl;
037:
038: /**
039: * This class does a pre-order walk of the DOM tree, calling a ContentHandler
040: * interface as it goes.
041: *
042: * This class is a copy of the one in org.apache.xml.utils.
043: * It exists to cut the serializers dependancy on that package.
044: *
045: * @xsl.usage internal
046: */
047:
048: public final class TreeWalker {
049:
050: /** Local reference to a ContentHandler */
051: final private ContentHandler m_contentHandler;
052: /**
053: * If m_contentHandler is a SerializationHandler, then this is
054: * a reference to the same object.
055: */
056: final private SerializationHandler m_Serializer;
057:
058: // ARGHH!! JAXP Uses Xerces without setting the namespace processing to ON!
059: // DOM2Helper m_dh = new DOM2Helper();
060:
061: /** DomHelper for this TreeWalker */
062: final protected DOM2Helper m_dh;
063:
064: /** Locator object for this TreeWalker */
065: final private LocatorImpl m_locator = new LocatorImpl();
066:
067: /**
068: * Get the ContentHandler used for the tree walk.
069: *
070: * @return the ContentHandler used for the tree walk
071: */
072: public ContentHandler getContentHandler() {
073: return m_contentHandler;
074: }
075:
076: public TreeWalker(ContentHandler ch) {
077: this (ch, null);
078: }
079:
080: /**
081: * Constructor.
082: * @param contentHandler The implemention of the
083: * contentHandler operation (toXMLString, digest, ...)
084: */
085: public TreeWalker(ContentHandler contentHandler, String systemId) {
086: // Set the content handler
087: m_contentHandler = contentHandler;
088: if (m_contentHandler instanceof SerializationHandler) {
089: m_Serializer = (SerializationHandler) m_contentHandler;
090: } else
091: m_Serializer = null;
092:
093: // Set the system ID, if it is given
094: m_contentHandler.setDocumentLocator(m_locator);
095: if (systemId != null)
096: m_locator.setSystemId(systemId);
097: else {
098: try {
099: // Bug see Bugzilla 26741
100: m_locator.setSystemId(System.getProperty("user.dir")
101: + File.separator + "dummy.xsl");
102: } catch (SecurityException se) {// user.dir not accessible from applet
103: }
104: }
105:
106: // Set the document locator
107: if (m_contentHandler != null)
108: m_contentHandler.setDocumentLocator(m_locator);
109: try {
110: // Bug see Bugzilla 26741
111: m_locator.setSystemId(System.getProperty("user.dir")
112: + File.separator + "dummy.xsl");
113: } catch (SecurityException se) {// user.dir not accessible from applet
114:
115: }
116: m_dh = new DOM2Helper();
117: }
118:
119: /**
120: * Perform a pre-order traversal non-recursive style.
121: *
122: * Note that TreeWalker assumes that the subtree is intended to represent
123: * a complete (though not necessarily well-formed) document and, during a
124: * traversal, startDocument and endDocument will always be issued to the
125: * SAX listener.
126: *
127: * @param pos Node in the tree where to start traversal
128: *
129: * @throws TransformerException
130: */
131: public void traverse(Node pos) throws org.xml.sax.SAXException {
132:
133: this .m_contentHandler.startDocument();
134:
135: Node top = pos;
136:
137: while (null != pos) {
138: startNode(pos);
139:
140: Node nextNode = pos.getFirstChild();
141:
142: while (null == nextNode) {
143: endNode(pos);
144:
145: if (top.equals(pos))
146: break;
147:
148: nextNode = pos.getNextSibling();
149:
150: if (null == nextNode) {
151: pos = pos.getParentNode();
152:
153: if ((null == pos) || (top.equals(pos))) {
154: if (null != pos)
155: endNode(pos);
156:
157: nextNode = null;
158:
159: break;
160: }
161: }
162: }
163:
164: pos = nextNode;
165: }
166: this .m_contentHandler.endDocument();
167: }
168:
169: /**
170: * Perform a pre-order traversal non-recursive style.
171:
172: * Note that TreeWalker assumes that the subtree is intended to represent
173: * a complete (though not necessarily well-formed) document and, during a
174: * traversal, startDocument and endDocument will always be issued to the
175: * SAX listener.
176: *
177: * @param pos Node in the tree where to start traversal
178: * @param top Node in the tree where to end traversal
179: *
180: * @throws TransformerException
181: */
182: public void traverse(Node pos, Node top)
183: throws org.xml.sax.SAXException {
184:
185: this .m_contentHandler.startDocument();
186:
187: while (null != pos) {
188: startNode(pos);
189:
190: Node nextNode = pos.getFirstChild();
191:
192: while (null == nextNode) {
193: endNode(pos);
194:
195: if ((null != top) && top.equals(pos))
196: break;
197:
198: nextNode = pos.getNextSibling();
199:
200: if (null == nextNode) {
201: pos = pos.getParentNode();
202:
203: if ((null == pos)
204: || ((null != top) && top.equals(pos))) {
205: nextNode = null;
206:
207: break;
208: }
209: }
210: }
211:
212: pos = nextNode;
213: }
214: this .m_contentHandler.endDocument();
215: }
216:
217: /** Flag indicating whether following text to be processed is raw text */
218: boolean nextIsRaw = false;
219:
220: /**
221: * Optimized dispatch of characters.
222: */
223: private final void dispatachChars(Node node)
224: throws org.xml.sax.SAXException {
225: if (m_Serializer != null) {
226: this .m_Serializer.characters(node);
227: } else {
228: String data = ((Text) node).getData();
229: this .m_contentHandler.characters(data.toCharArray(), 0,
230: data.length());
231: }
232: }
233:
234: /**
235: * Start processing given node
236: *
237: *
238: * @param node Node to process
239: *
240: * @throws org.xml.sax.SAXException
241: */
242: protected void startNode(Node node) throws org.xml.sax.SAXException {
243:
244: // TODO: <REVIEW>
245: // A Serializer implements ContentHandler, but not NodeConsumer
246: // so drop this reference to NodeConsumer which would otherwise
247: // pull in all sorts of things
248: // if (m_contentHandler instanceof NodeConsumer)
249: // {
250: // ((NodeConsumer) m_contentHandler).setOriginatingNode(node);
251: // }
252: // TODO: </REVIEW>
253:
254: if (node instanceof Locator) {
255: Locator loc = (Locator) node;
256: m_locator.setColumnNumber(loc.getColumnNumber());
257: m_locator.setLineNumber(loc.getLineNumber());
258: m_locator.setPublicId(loc.getPublicId());
259: m_locator.setSystemId(loc.getSystemId());
260: } else {
261: m_locator.setColumnNumber(0);
262: m_locator.setLineNumber(0);
263: }
264:
265: switch (node.getNodeType()) {
266: case Node.COMMENT_NODE: {
267: String data = ((Comment) node).getData();
268:
269: if (m_contentHandler instanceof LexicalHandler) {
270: LexicalHandler lh = ((LexicalHandler) this .m_contentHandler);
271:
272: lh.comment(data.toCharArray(), 0, data.length());
273: }
274: }
275: break;
276: case Node.DOCUMENT_FRAGMENT_NODE:
277:
278: // ??;
279: break;
280: case Node.DOCUMENT_NODE:
281:
282: break;
283: case Node.ELEMENT_NODE:
284: Element elem_node = (Element) node;
285: {
286: // Make sure the namespace node
287: // for the element itself is declared
288: // to the ContentHandler
289: String uri = elem_node.getNamespaceURI();
290: if (uri != null) {
291: String prefix = elem_node.getPrefix();
292: if (prefix == null)
293: prefix = "";
294: this .m_contentHandler.startPrefixMapping(prefix,
295: uri);
296: }
297: }
298: NamedNodeMap atts = elem_node.getAttributes();
299: int nAttrs = atts.getLength();
300: // System.out.println("TreeWalker#startNode: "+node.getNodeName());
301:
302: // Make sure the namespace node of
303: // each attribute is declared to the ContentHandler
304: for (int i = 0; i < nAttrs; i++) {
305: final Node attr = atts.item(i);
306: final String attrName = attr.getNodeName();
307: final int colon = attrName.indexOf(':');
308: final String prefix;
309:
310: // System.out.println("TreeWalker#startNode: attr["+i+"] = "+attrName+", "+attr.getNodeValue());
311: if (attrName.equals("xmlns")
312: || attrName.startsWith("xmlns:")) {
313: // Use "" instead of null, as Xerces likes "" for the
314: // name of the default namespace. Fix attributed
315: // to "Steven Murray" <smurray@ebt.com>.
316: if (colon < 0)
317: prefix = "";
318: else
319: prefix = attrName.substring(colon + 1);
320:
321: this .m_contentHandler.startPrefixMapping(prefix,
322: attr.getNodeValue());
323: } else if (colon > 0) {
324: prefix = attrName.substring(0, colon);
325: String uri = attr.getNamespaceURI();
326: if (uri != null)
327: this .m_contentHandler.startPrefixMapping(
328: prefix, uri);
329: }
330: }
331:
332: String ns = m_dh.getNamespaceOfNode(node);
333: if (null == ns)
334: ns = "";
335: this .m_contentHandler.startElement(ns, m_dh
336: .getLocalNameOfNode(node), node.getNodeName(),
337: new AttList(atts, m_dh));
338: break;
339: case Node.PROCESSING_INSTRUCTION_NODE: {
340: ProcessingInstruction pi = (ProcessingInstruction) node;
341: String name = pi.getNodeName();
342:
343: // String data = pi.getData();
344: if (name.equals("xslt-next-is-raw")) {
345: nextIsRaw = true;
346: } else {
347: this .m_contentHandler.processingInstruction(pi
348: .getNodeName(), pi.getData());
349: }
350: }
351: break;
352: case Node.CDATA_SECTION_NODE: {
353: boolean isLexH = (m_contentHandler instanceof LexicalHandler);
354: LexicalHandler lh = isLexH ? ((LexicalHandler) this .m_contentHandler)
355: : null;
356:
357: if (isLexH) {
358: lh.startCDATA();
359: }
360:
361: dispatachChars(node);
362:
363: {
364: if (isLexH) {
365: lh.endCDATA();
366: }
367: }
368: }
369: break;
370: case Node.TEXT_NODE: {
371: //String data = ((Text) node).getData();
372:
373: if (nextIsRaw) {
374: nextIsRaw = false;
375:
376: m_contentHandler
377: .processingInstruction(
378: javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING,
379: "");
380: dispatachChars(node);
381: m_contentHandler
382: .processingInstruction(
383: javax.xml.transform.Result.PI_ENABLE_OUTPUT_ESCAPING,
384: "");
385: } else {
386: dispatachChars(node);
387: }
388: }
389: break;
390: case Node.ENTITY_REFERENCE_NODE: {
391: EntityReference eref = (EntityReference) node;
392:
393: if (m_contentHandler instanceof LexicalHandler) {
394: ((LexicalHandler) this .m_contentHandler)
395: .startEntity(eref.getNodeName());
396: } else {
397:
398: // warning("Can not output entity to a pure SAX ContentHandler");
399: }
400: }
401: break;
402: default:
403: }
404: }
405:
406: /**
407: * End processing of given node
408: *
409: *
410: * @param node Node we just finished processing
411: *
412: * @throws org.xml.sax.SAXException
413: */
414: protected void endNode(Node node) throws org.xml.sax.SAXException {
415:
416: switch (node.getNodeType()) {
417: case Node.DOCUMENT_NODE:
418: break;
419:
420: case Node.ELEMENT_NODE:
421: String ns = m_dh.getNamespaceOfNode(node);
422: if (null == ns)
423: ns = "";
424: this .m_contentHandler.endElement(ns, m_dh
425: .getLocalNameOfNode(node), node.getNodeName());
426:
427: if (m_Serializer == null) {
428: // Don't bother with endPrefixMapping calls if the ContentHandler is a
429: // SerializationHandler because SerializationHandler's ignore the
430: // endPrefixMapping() calls anyways. . . . This is an optimization.
431: Element elem_node = (Element) node;
432: NamedNodeMap atts = elem_node.getAttributes();
433: int nAttrs = atts.getLength();
434:
435: // do the endPrefixMapping calls in reverse order
436: // of the startPrefixMapping calls
437: for (int i = (nAttrs - 1); 0 <= i; i--) {
438: final Node attr = atts.item(i);
439: final String attrName = attr.getNodeName();
440: final int colon = attrName.indexOf(':');
441: final String prefix;
442:
443: if (attrName.equals("xmlns")
444: || attrName.startsWith("xmlns:")) {
445: // Use "" instead of null, as Xerces likes "" for the
446: // name of the default namespace. Fix attributed
447: // to "Steven Murray" <smurray@ebt.com>.
448: if (colon < 0)
449: prefix = "";
450: else
451: prefix = attrName.substring(colon + 1);
452:
453: this .m_contentHandler.endPrefixMapping(prefix);
454: } else if (colon > 0) {
455: prefix = attrName.substring(0, colon);
456: this .m_contentHandler.endPrefixMapping(prefix);
457: }
458: }
459: {
460: String uri = elem_node.getNamespaceURI();
461: if (uri != null) {
462: String prefix = elem_node.getPrefix();
463: if (prefix == null)
464: prefix = "";
465: this .m_contentHandler.endPrefixMapping(prefix);
466: }
467: }
468: }
469: break;
470: case Node.CDATA_SECTION_NODE:
471: break;
472: case Node.ENTITY_REFERENCE_NODE: {
473: EntityReference eref = (EntityReference) node;
474:
475: if (m_contentHandler instanceof LexicalHandler) {
476: LexicalHandler lh = ((LexicalHandler) this .m_contentHandler);
477:
478: lh.endEntity(eref.getNodeName());
479: }
480: }
481: break;
482: default:
483: }
484: }
485: } //TreeWalker
|