001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: HandlerStack.java,v 1.5 2007/03/27 21:59:43 mlipp Exp $
021: *
022: * $Log: HandlerStack.java,v $
023: * Revision 1.5 2007/03/27 21:59:43 mlipp
024: * Fixed lots of checkstyle warnings.
025: *
026: * Revision 1.4 2006/09/29 12:32:11 drmlipp
027: * Consistently using WfMOpen as projct name now.
028: *
029: * Revision 1.3 2004/10/15 14:13:08 drmlipp
030: * JDK 1.3 compatility patch.
031: *
032: * Revision 1.2 2004/09/28 13:18:03 drmlipp
033: * Provided backward compatibility to JDK 1.3.
034: *
035: * Revision 1.1.1.2 2004/08/18 15:17:35 drmlipp
036: * Update to 1.2
037: *
038: * Revision 1.12 2004/03/16 17:04:02 lipp
039: * Added some methods to access tracked information.
040: *
041: * Revision 1.11 2003/06/27 08:51:46 lipp
042: * Fixed copyright/license information.
043: *
044: * Revision 1.10 2003/06/23 15:52:10 lipp
045: * Fixed bug in namespace stack.
046: *
047: * Revision 1.9 2003/04/29 17:50:54 lipp
048: * Save namespaces.
049: *
050: * Revision 1.8 2003/04/25 14:50:59 lipp
051: * Fixed javadoc errors and warnings.
052: *
053: * Revision 1.7 2003/04/22 11:13:31 lipp
054: * Various fixes.
055: *
056: * Revision 1.6 2003/04/21 20:25:41 lipp
057: * Major revision of implementation.
058: *
059: * Revision 1.5 2003/04/17 21:04:13 lipp
060: * Minor updates for namespace support.
061: *
062: * Revision 1.4 2003/03/31 16:50:28 huaiyang
063: * Logging using common-logging.
064: *
065: * Revision 1.3 2002/01/23 09:42:34 lipp
066: * Added default handler and attributes.
067: *
068: * Revision 1.2 2002/01/22 22:28:26 lipp
069: * Getting started with sax support, the second.
070: *
071: * Revision 1.1 2002/01/22 15:51:48 lipp
072: * Getting started with sax support.
073: *
074: */
075:
076: package de.danet.an.util.sax;
077:
078: import java.util.ArrayList;
079: import java.util.HashMap;
080: import java.util.Iterator;
081: import java.util.List;
082: import java.util.Map;
083:
084: import org.xml.sax.Attributes;
085: import org.xml.sax.ContentHandler;
086: import org.xml.sax.Locator;
087: import org.xml.sax.SAXException;
088: import org.xml.sax.XMLReader;
089:
090: /**
091: * <code>HandlerStack</code> provides support for dispatching SAX
092: * events to the appropriate target during processing of an XML
093: * document.<P>
094: *
095: * Writing an event handler for a SAX parser and being object oriented
096: * isn't easy. Most people tend to write one large event handler that
097: * knows far too much about the objects that it has to create and
098: * provide with information. According to the object oriented approach,
099: * every object should implement its own handling for the parts of the
100: * XML text that provides information about the object's state.<P>
101: *
102: * This means changing the parser's content handler in a controlled
103: * way. Each time that a handler encounters an element that denotes
104: * the start of another object, it should create another object and
105: * delegate further processing to this object. Or even better, it should
106: * call a (static) factory method for the class associated with the
107: * encountered element and leave all further processing to the factory
108: * method.<P>
109: *
110: * Effectively, this results in a stack of handler with events being
111: * delivered to the top of stack handler. This class implements such
112: * a stack with as much automation as possible.
113: */
114: public class HandlerStack {
115:
116: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
117: .getLog(HandlerStack.class);
118:
119: /** The associated content handler. */
120: private ContentHandler myHandler = null;
121:
122: /** The stack. */
123: private List handlerStack = new ArrayList();
124: private List depthStack = new ArrayList();
125: private List textStack = new ArrayList();
126:
127: /** The top of stack. */
128: private ContentHandler top = null;
129: private int depth = 0;
130: private StringBuffer text = new StringBuffer();
131:
132: /**
133: * Indicates that stack is empty. The first handler is never
134: * removed because some events may follow the last endElement.
135: */
136: private boolean stackEmpty = false;
137:
138: /** Accumulated path. */
139: private StringBuffer accuPath = new StringBuffer();
140:
141: private String curUri = null;
142: private String curLocalName = null;
143: private String curQName = null;
144: private Attributes curAtts = null;
145:
146: /** The attribute map. */
147: private Map contextInfo = new HashMap();
148:
149: /* Namespace tracing. */
150: private Map nsMap = new HashMap();
151: private Map uriMap = new HashMap();
152:
153: /**
154: * Helper that delegates the received events to the top (and does
155: * some tracking).
156: */
157: private class MyHandler implements ContentHandler {
158: /**
159: * Receive an object for locating the origin of SAX document
160: * events.
161: *
162: * @param locator An object that can return the location of
163: * any SAX document event.
164: */
165: public void setDocumentLocator(Locator locator) {
166: // may occur after last element
167: top.setDocumentLocator(locator);
168: }
169:
170: /**
171: * Receive notification of the beginning of a document.
172: * @throws SAXException not thrown.
173: */
174: public void startDocument() throws SAXException {
175: if (!stackEmpty) {
176: accuPath.append("/");
177: top.startDocument();
178: } else {
179: logger
180: .error("Received event while no handler on stack.");
181: }
182: }
183:
184: /**
185: * Receive notification of the end of a document.
186: * @throws SAXException not thrown.
187: */
188: public void endDocument() throws SAXException {
189: // may occur after last element
190: top.endDocument();
191: accuPath.setLength(0);
192: }
193:
194: /**
195: * Begin the scope of a prefix-URI Namespace mapping.
196: *
197: * @param prefix The Namespace prefix being declared.
198: * @param uri The Namespace URI the prefix is mapped to.
199: * @throws SAXException not thrown.
200: */
201: public void startPrefixMapping(String prefix, String uri)
202: throws SAXException {
203: if (!stackEmpty) {
204: top.startPrefixMapping(prefix, uri);
205: } else {
206: logger
207: .error("Received event while no handler on stack.");
208: }
209: List ns = (List) nsMap.get(prefix);
210: if (ns == null) {
211: ns = new ArrayList(1);
212: nsMap.put(prefix, ns);
213: } else if (ns.size() > 0) {
214: uriMap.remove((String) ns.get(0));
215: }
216: ns.add(0, uri);
217: uriMap.put(uri, prefix);
218: }
219:
220: /**
221: * End the scope of a prefix-URI mapping.
222: *
223: * @param prefix The prefix that was being mapping.
224: * @throws SAXException not thrown.
225: */
226: public void endPrefixMapping(String prefix) throws SAXException {
227: // may occur after last element
228: List ns = (List) nsMap.get(prefix);
229: if (ns == null) {
230: throw new SAXException(
231: "Trying to remove unregistered namespace");
232: }
233: uriMap.remove((String) ns.remove(0));
234: if (ns.size() > 0) {
235: uriMap.put((String) ns.get(0), prefix);
236: } else {
237: nsMap.remove(prefix);
238: }
239: top.endPrefixMapping(prefix);
240: }
241:
242: /**
243: * Receive notification of the beginning of an element.
244: *
245: * @param uri the Namespace URI, or the empty string if the
246: * element has no Namespace URI or if Namespace processing is not
247: * being performed.
248: * @param loc the local name (without prefix), or the empty string
249: * if Namespace processing is not being performed.
250: * @param raw the raw XML 1.0 name (with prefix), or the empty
251: * string if raw names are not available.
252: * @param a the attributes attached to the element. If there are
253: * no attributes, it shall be an empty Attributes object.
254: * @throws SAXException not thrown.
255: */
256: public void startElement(String uri, String loc, String raw,
257: Attributes a) throws SAXException {
258: if (stackEmpty) {
259: logger
260: .error("Received event while no handler on stack.");
261: return;
262: }
263: textStack.add(0, text);
264: text = new StringBuffer();
265: curUri = uri;
266: curLocalName = loc;
267: curQName = raw;
268: curAtts = a;
269: if (accuPath.length() == 0
270: || accuPath.charAt(accuPath.length() - 1) != '/') {
271: accuPath.append('/');
272: }
273: if (uri != null && uri.length() > 0) {
274: accuPath.append('{');
275: accuPath.append(uri);
276: accuPath.append('}');
277: }
278: accuPath.append(loc);
279: top.startElement(uri, loc, raw, a);
280: curUri = null;
281: curLocalName = null;
282: curQName = null;
283: curAtts = null;
284: depth += 1;
285: }
286:
287: /**
288: * Receive notification of the end of an element.
289: *
290: * @param uri the Namespace URI, or the empty string if the
291: * element has no Namespace URI or if Namespace processing is not
292: * being performed.
293: * @param loc the local name (without prefix), or the empty string
294: * if Namespace processing is not being performed.
295: * @param raw the raw XML 1.0 name (with prefix), or the empty
296: * string if raw names are not available.
297: * @throws SAXException not thrown.
298: */
299: public void endElement(String uri, String loc, String raw)
300: throws SAXException {
301: if (stackEmpty) {
302: logger
303: .error("Received event while no handler on stack.");
304: return;
305: }
306: top.endElement(uri, loc, raw);
307: int lastSeg = -1;
308: try {
309: lastSeg = accuPath.lastIndexOf("/");
310: } catch (NoSuchMethodError e) {
311: // JDK 1.3 compatibility
312: lastSeg = accuPath.toString().lastIndexOf("/");
313: }
314: int uriEnd = -1;
315: try {
316: uriEnd = accuPath.lastIndexOf("}");
317: } catch (NoSuchMethodError e) {
318: // JDK 1.3 compatibility
319: uriEnd = accuPath.toString().lastIndexOf("}");
320: }
321: if (uriEnd > lastSeg) {
322: // may be ".../{.../...}loc", fix
323: try {
324: lastSeg = accuPath.lastIndexOf("/", accuPath
325: .lastIndexOf("{"));
326: } catch (NoSuchMethodError e) {
327: // JDK 1.3 compatibility
328: lastSeg = accuPath.toString().lastIndexOf("/",
329: accuPath.toString().lastIndexOf("{"));
330: }
331: }
332: if (lastSeg < 0) {
333: accuPath.setLength(0);
334: } else {
335: accuPath.setLength(lastSeg);
336: }
337: text = (StringBuffer) textStack.remove(0);
338: if (--depth == 0) {
339: pop(uri, loc, raw);
340: }
341: }
342:
343: /**
344: * Receive notification of character data.
345: *
346: * @param c The characters from the XML document.
347: * @param start The start position in the array.
348: * @param len The number of characters to read from the array.
349: * @throws SAXException not thrown.
350: */
351: public void characters(char[] c, int start, int len)
352: throws SAXException {
353: // may occur after last element
354: text.append(c, start, len);
355: top.characters(c, start, len);
356: }
357:
358: /**
359: * Receive notification of ignorable whitespace in element content.
360: *
361: * @param c The characters from the XML document.
362: * @param start The start position in the array.
363: * @param len The number of characters to read from the array.
364: * @throws SAXException not thrown.
365: */
366: public void ignorableWhitespace(char[] c, int start, int len)
367: throws SAXException {
368: // may occur after last element
369: top.ignorableWhitespace(c, start, len);
370: }
371:
372: /**
373: * Receive notification of a processing instruction.
374: *
375: * @param target The processing instruction target.
376: * @param data The processing instruction data, or null if none was
377: * supplied.
378: * @throws SAXException not thrown.
379: */
380: public void processingInstruction(String target, String data)
381: throws SAXException {
382: // may occur after last element (?)
383: top.processingInstruction(target, data);
384: }
385:
386: /**
387: * Receive notification of a skipped entity.
388: *
389: * @param name The name of the skipped entity. If it is a parameter
390: * entity, the name will begin with '%'.
391: * @throws SAXException not thrown.
392: */
393: public void skippedEntity(String name) throws SAXException {
394: // may occur after last element
395: top.skippedEntity(name);
396: }
397: }
398:
399: /**
400: * Create a new stack for a given reader. The stack's event
401: * handler will be set as the reader's content handler.
402: *
403: * @param theReader the reader associated with this stack.
404: * @param initialHandler the initial active handler (new top of stack).
405: */
406: public HandlerStack(XMLReader theReader,
407: ContentHandler initialHandler) {
408: this (initialHandler);
409: theReader.setContentHandler(myHandler);
410: }
411:
412: /**
413: * Create a new stack. To pass events to the stack, the stack's
414: * event handler can be retrieved with {@link #contentHandler
415: * <code>eventHandler()</code>}.
416: *
417: * @param initialHandler the initial active handler (new top of stack).
418: */
419: public HandlerStack(ContentHandler initialHandler) {
420: if (initialHandler instanceof StackedHandler) {
421: ((StackedHandler) initialHandler).setStack(this );
422: }
423: top = initialHandler;
424: myHandler = new MyHandler();
425: }
426:
427: /**
428: * Return the event handler associated with this stack.
429: * @return the event handler.
430: */
431: public ContentHandler contentHandler() {
432: return myHandler;
433: }
434:
435: /**
436: * Send prefix mapping start events for all namespaces currently
437: * in effect to the given handler.
438: * @param handler the handler.
439: * @throws SAXException if signalled by the receiving handler.
440: */
441: public void startAllPrefixMappings(ContentHandler handler)
442: throws SAXException {
443: for (Iterator i = nsMap.keySet().iterator(); i.hasNext();) {
444: String prefix = (String) i.next();
445: String uri = (String) ((List) nsMap.get(prefix)).get(0);
446: handler.startPrefixMapping(prefix, uri);
447: }
448: }
449:
450: /**
451: * Send prefix mapping end events for all namespaces currently in
452: * effect to the given handler.
453: * @param handler the handler.
454: * @throws SAXException if signalled by the receiving handler.
455: */
456: public void endAllPrefixMappings(ContentHandler handler)
457: throws SAXException {
458: for (Iterator i = nsMap.keySet().iterator(); i.hasNext();) {
459: String prefix = (String) i.next();
460: handler.endPrefixMapping(prefix);
461: }
462: }
463:
464: /**
465: * Return the prefix for a given URI based on the namespaces
466: * currently in effect.
467: *
468: * @param uri the namespace URI
469: * @return the prefix or <code>null</code>
470: */
471: public String getPrefixForURI(String uri) {
472: return (String) uriMap.get(uri);
473: }
474:
475: /**
476: * Return the URI for a given prefix based on the namespaces
477: * currently in effect.
478: *
479: * @param prefix the namespace URI
480: * @return the URI or <code>null</code>
481: */
482: public String getURIForPrefix(String prefix) {
483: List ns = (List) nsMap.get(prefix);
484: if (ns == null) {
485: return null;
486: }
487: return (String) ns.get(0);
488: }
489:
490: /**
491: * Sets a context data item. context data items may be used to
492: * communicate data between different handlers.
493: * @param name the name of the context item.
494: * @param value the associated value.
495: * @see #getContextData
496: */
497: public void setContextData(String name, Object value) {
498: contextInfo.put(name, value);
499: }
500:
501: /**
502: * Retrieves a context item.
503: * @param name the name of the context item.
504: * @return the associated value or <code>null</code> if the attribute
505: * has not been set.
506: * @see #setContextData
507: */
508: public Object getContextData(String name) {
509: return contextInfo.get(name);
510: }
511:
512: /**
513: * Retrieves and removes a context item.
514: * @param name the name of the context item.
515: * @return the associated value or <code>null</code> if the attribute
516: * has not been set.
517: * @see #setContextData
518: * @see #getContextData
519: */
520: public Object removeContextData(String name) {
521: return contextInfo.remove(name);
522: }
523:
524: /**
525: * Returns the relative depth of an element. The relative depth is
526: * tracked for every handler. When the <code>startElement</code>
527: * method of a newly pushed handler is called, the relative depth
528: * is 0. It is incremented immediately after
529: * <code>startElement</code> returns, so during the next call to
530: * <code>startElement</code> the value will be 1 (unless there has
531: * been a call to <code>endElement</code>, of course).
532: *
533: * @return the relative depth
534: */
535: public int getRelativeDepth() {
536: return depth;
537: }
538:
539: /**
540: * Return the accumulated text from <code>characters</code> events
541: * for the current element. Returns valid data during processing
542: * of <code>endElement</code> events only.
543: * @return the accumulated text.
544: */
545: public String text() {
546: return text.toString();
547: }
548:
549: /**
550: * Pushes the given handler on top of stack. This method may only
551: * be called from a {@link ContentHandler#startElement
552: * <code>ContentHandler.startElement</code>} that has been invoked
553: * by the stack. <P>
554: *
555: * When a handler is pushed, the pushed handler's {@link
556: * ContentHandler#startElement <code>startElement</code>} is
557: * called with the current context, i.e. the values for
558: * <code>uri</code>, <code>localName</code> and <code>qName</code>
559: * passed to the pushing <code>startElement</code> that initially
560: * caused the push.
561: *
562: * @param handler the new active handler.
563: * @throws IllegalStateException if the method is called outside a
564: * <code>ContentHandler.startElement</code> that has been called
565: * by the stack.
566: * @throws SAXException if thrown by the called
567: * <code>startElement</code>.
568: */
569: public void push(ContentHandler handler) throws SAXException,
570: IllegalStateException {
571: if (curUri == null) {
572: throw new IllegalStateException();
573: }
574: if (handler instanceof StackedHandler) {
575: ((StackedHandler) handler).setStack(this );
576: }
577: handlerStack.add(0, top);
578: depthStack.add(0, new Integer(depth));
579: top = handler;
580: depth = 0;
581: handler.startElement(curUri, curLocalName, curQName, curAtts);
582: }
583:
584: /**
585: * Remove the top handler from the stack. This method is
586: * automatically called if the current (end) element terminates
587: * processing of the subtree associated with current handler. <P>
588: *
589: * After the current handler is removed from the stack, the now
590: * current (TOS) handler's <code>endElement</code> method is
591: * called with the current (end) element context, i.e. the values for
592: * <code>uri</code>, <code>localName</code> and <code>qName</code>
593: * passed to the pushing <code>handleEndElement</code>.
594: *
595: * @param uri the Namespace URI of the current element.
596: * @param localName the local name of the current element.
597: * @param qName the qualified name of the current element.
598: * @throws SAXException if thrown by the called
599: * <code>handleStartElement</code>
600: */
601: private void pop(String uri, String localName, String qName)
602: throws SAXException {
603: if (handlerStack.size() == 0) {
604: stackEmpty = true;
605: } else {
606: top = (ContentHandler) handlerStack.remove(0);
607: depth = ((Integer) depthStack.remove(0)).intValue();
608: top.endElement(uri, localName, qName);
609: }
610: }
611:
612: /**
613: * Return the current path. The current path consists of all
614: * elements for which a <code>startElement</code> event has been
615: * received. The path is denoted as
616: * <code>/{uri1}loc1/loc2/{uri3}loc3</code>.
617: * @return the current path.
618: */
619: public String currentPath() {
620: return accuPath.toString();
621: }
622: }
|