001: // You can redistribute this software and/or modify it under the terms of
002: // the Ozone Library License version 1 published by ozone-db.org.
003: //
004: // The original code and portions created by SMB are
005: // Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
006: //
007: // $Id: SAXChunkConsumer.java,v 1.1 2001/12/18 10:31:31 per_nyfelt Exp $
008:
009: package org.ozoneDB.xml.util;
010:
011: import java.io.IOException;
012: import java.io.Serializable;
013:
014: import org.w3c.dom.Node;
015: import org.w3c.dom.NodeList;
016: import org.w3c.dom.Document;
017: import org.w3c.dom.Element;
018: import org.w3c.dom.Text;
019: import org.w3c.dom.ProcessingInstruction;
020: import org.w3c.dom.EntityReference;
021: import org.w3c.dom.Comment;
022: import org.w3c.dom.CDATASection;
023:
024: import org.xml.sax.SAXException;
025: import org.xml.sax.ContentHandler;
026: import org.xml.sax.ext.LexicalHandler;
027: import org.xml.sax.Locator;
028: import org.xml.sax.Attributes;
029: import org.xml.sax.helpers.AttributesImpl;
030:
031: import org.ozoneDB.DxLib.DxHashMap;
032: import org.ozoneDB.DxLib.DxDeque;
033: import org.ozoneDB.OzoneObject;
034:
035: /**
036: * Objects of this class can be used to convert a sequence of
037: * {@link SAXEventChunk}s back into a DOM tree or SAX events.
038: * WARNING: Don't reuse instances of this class. Create a new instance for each
039: * chunk set (chunks containing information about the same XML tree).
040: * @version $Revision: 1.1 $ $Date: 2001/12/18 10:31:31 $
041: * @author <a href="http://www.smb-tec.com">SMB</a>
042: */
043: public final class SAXChunkConsumer implements ContentHandler,
044: LexicalHandler, Serializable {
045:
046: private final static boolean debug = false;
047:
048: private final Node appendTo;
049:
050: protected Node startNode = null;
051:
052: protected Node currentNode = null;
053:
054: protected final Document domFactory;
055:
056: protected final boolean domLevel2;
057:
058: protected final ContentHandler contentHandler;
059:
060: protected final LexicalHandler lexicalHandler;
061:
062: protected final ChunkInputStream chunkInput;
063:
064: protected final CompiledXMLInputStream cxmlInput;
065:
066: protected int processLevel;
067:
068: protected ModifiableNodeList resultNodeList;
069:
070: /**
071: * Use this chunk consumer to produce a DOM tree out of the consumed
072: * chunks.
073: * @param _node The node where the newly created DOM tree will be appended to
074: * @throws IllegalArgumentException if the given node was null
075: */
076: public SAXChunkConsumer(Document domFactory, Node appendTo)
077: throws IOException {
078: if (domFactory == null) {
079: throw new IllegalArgumentException(
080: "provided DOM factory node was null!");
081: }
082:
083: this .contentHandler = this ;
084: this .lexicalHandler = this ;
085:
086: this .appendTo = appendTo;
087: this .processLevel = 0;
088:
089: this .resultNodeList = new ModifiableNodeList();
090:
091: this .domFactory = domFactory;
092: // this.domLevel2 = domFactory.getImplementation().hasFeature("XML", "2.0");
093: this .domLevel2 = false;
094:
095: this .chunkInput = new ChunkInputStream(null);
096: this .cxmlInput = new CompiledXMLInputStream(this .chunkInput);
097: }
098:
099: /**
100: * Use this chunk consumer to produce SAX events out of the consumed chunks.
101: * @param _contentHandler the handler the SAX events will be send to
102: * @throws IllegalArgumentException if the given content handler was null
103: */
104: public SAXChunkConsumer(ContentHandler contentHandler)
105: throws IOException {
106: if (contentHandler == null) {
107: throw new IllegalArgumentException(
108: "provided SAX content handler was null");
109: }
110: this .contentHandler = contentHandler;
111: this .lexicalHandler = (contentHandler instanceof LexicalHandler) ? (LexicalHandler) contentHandler
112: : null;
113: this .appendTo = null;
114:
115: this .chunkInput = new ChunkInputStream(null);
116: this .cxmlInput = new CompiledXMLInputStream(this .chunkInput);
117:
118: this .domLevel2 = false;
119: this .domFactory = null;
120: }
121:
122: /**
123: * Takes a chunk and produces corresponding SAX events
124: * @param _chunk the chunk containing definitions of events to be thrown
125: * @throws IllegalArgumentException if the given chunk was null
126: */
127: public final void processChunk(byte[] chunkData)
128: throws SAXException, IOException {
129: if (chunkData == null) {
130: throw new IllegalArgumentException(
131: "provided event chunk was null");
132: }
133:
134: this .chunkInput.setBuffer(chunkData);
135:
136: while (this .chunkInput.available() > 0) {
137: switch (cxmlInput.readEvent()) {
138: case CXMLContentHandler.START_DOCUMENT:
139: if (debug) {
140: System.out.println(this .getClass().getName()
141: + ": startDocument()");
142: }
143: this .contentHandler.startDocument();
144: break;
145: case CXMLContentHandler.END_DOCUMENT:
146: this .contentHandler.endDocument();
147: return;
148: case CXMLContentHandler.START_PREFIX_MAPPING:
149: this .contentHandler.startPrefixMapping(cxmlInput
150: .readString(), cxmlInput.readString());
151: break;
152: case CXMLContentHandler.END_PREFIX_MAPPING:
153: this .contentHandler.endPrefixMapping(cxmlInput
154: .readString());
155: break;
156: case CXMLContentHandler.START_ELEMENT:
157: if (debug) {
158: System.out.println(this .getClass().getName()
159: + ": startElement()");
160: }
161: int attributes = cxmlInput.readAttributes();
162: AttributesImpl atts = new AttributesImpl();
163: for (int i = 0; i < attributes; i++) {
164: // atts.addAttribute(cxmlInput.readString(), cxmlInput.readString(), cxmlInput.readString(),
165: // cxmlInput.readString(), cxmlInput.readString());
166: atts.addAttribute(cxmlInput.readString(), cxmlInput
167: .readString(), cxmlInput.readString(),
168: cxmlInput.readString(), new String(
169: cxmlInput.readChars()));
170: }
171: String namespace = cxmlInput.readString();
172: String localname = cxmlInput.readString();
173: String qname = cxmlInput.readString();
174:
175: if (debug) {
176: System.out.println(this .getClass().getName()
177: + "namespace=" + namespace + ", localname="
178: + localname + ", qname=" + qname);
179: }
180:
181: this .contentHandler.startElement(namespace, localname,
182: qname, atts);
183: break;
184: case CXMLContentHandler.END_ELEMENT:
185: this .contentHandler.endElement(cxmlInput.readString(),
186: cxmlInput.readString(), cxmlInput.readString());
187: break;
188: case CXMLContentHandler.CHARACTERS:
189: char[] chars = cxmlInput.readChars();
190: this .contentHandler.characters(chars, 0, chars.length);
191: break;
192: case CXMLContentHandler.IGNORABLE_WHITESPACE:
193: char[] spaces = cxmlInput.readChars();
194: this .contentHandler
195: .characters(spaces, 0, spaces.length);
196: break;
197: case CXMLContentHandler.PROCESSING_INSTRUCTION:
198: this .contentHandler.processingInstruction(cxmlInput
199: .readString(), cxmlInput.readString());
200: break;
201: case CXMLContentHandler.COMMENT:
202: if (this .lexicalHandler != null) {
203: char[] comment = cxmlInput.readChars();
204: this .lexicalHandler.comment(comment, 0,
205: comment.length);
206: }
207: break;
208: case CXMLContentHandler.START_CDATA:
209: if (this .lexicalHandler != null) {
210: this .lexicalHandler.startCDATA();
211: }
212: break;
213: case CXMLContentHandler.END_CDATA:
214: if (this .lexicalHandler != null) {
215: this .lexicalHandler.endCDATA();
216: }
217: break;
218: default:
219: throw new IOException(
220: "parsing error: event not supported: ");
221: }
222: }
223: }
224:
225: /**
226: * @return the last created result node, if this consumer was initialized
227: * for DOM creation.
228: */
229: public final Node getResultNode() {
230: return startNode;
231: }
232:
233: /**
234: * @return the result node list, if this consumer was initialized for
235: * DOM creation.
236: */
237: public final NodeList getResultNodeList() {
238: return resultNodeList;
239: }
240:
241: //
242: // SAX content handler implemenation
243: //
244:
245: /**
246: * Received notification of the beginning of the document.
247: */
248: public final void startDocument() {
249: if (this .startNode == null) {
250: this .startNode = (this .appendTo != null) ? this .appendTo
251: : this .domFactory;
252: }
253:
254: if (debug) {
255: System.out.println(this .getClass().getName()
256: + ": startDocument()");
257: }
258:
259: if (this .startNode.getNodeType() != Node.DOCUMENT_NODE) {
260: throw new IllegalStateException(
261: "A document can't be appended to another node!");
262: }
263:
264: if (this .startNode.hasChildNodes()) {
265: throw new RuntimeException(
266: "The given DOM document must not have children if a whole document shall be converted!");
267: }
268:
269: if (this .processLevel != 0) {
270: throw new RuntimeException(
271: "startDocument event must not occur within other start-end-event pairs!");
272: }
273:
274: this .resultNodeList.addNode(this .startNode);
275: this .currentNode = this .startNode;
276: this .processLevel++;
277: }
278:
279: /**
280: * Received notification of the end of the document.
281: */
282: public final void endDocument() {
283: if (debug) {
284: System.out.println(this .getClass().getName()
285: + ": endDocument()");
286: }
287:
288: this .currentNode = this .currentNode.getParentNode();
289: this .processLevel--;
290: }
291:
292: /**
293: * Receive notification of the start of an element.
294: * Note: Namespace handling has to be revised.
295: */
296: public final void startElement(String _namespaceURI,
297: String _localName, String _rawName, Attributes _atts) {
298: if (debug) {
299: System.out.println(this .getClass().getName()
300: + ": startElement(...)");
301: }
302:
303: Element newElem;
304: if (domLevel2) {
305: newElem = domFactory.createElementNS(_namespaceURI,
306: _rawName);
307:
308: //add the attributes
309: for (int i = 0; i < _atts.getLength(); i++) {
310: newElem.setAttributeNS(_atts.getURI(i), _atts
311: .getQName(i), _atts.getValue(i));
312: }
313: } else {
314: newElem = domFactory.createElement(_rawName);
315:
316: //add the attributes
317: for (int i = 0; i < _atts.getLength(); i++) {
318: newElem.setAttribute(_atts.getQName(i), _atts
319: .getValue(i));
320: }
321:
322: }
323:
324: if (this .processLevel == 0) {
325: this .resultNodeList.addNode(newElem);
326: this .startNode = newElem;
327: this .currentNode = newElem;
328: if (this .appendTo != null) {
329: this .appendTo.appendChild(newElem);
330: }
331: } else {
332: this .currentNode.appendChild(newElem);
333: this .currentNode = newElem;
334: }
335: this .processLevel++;
336: }
337:
338: /**
339: * Receive notification of the end of an element.
340: */
341: public final void endElement(String _namespaceURI,
342: String _localName, String _rawName) {
343: if (debug) {
344: System.out.println(this .getClass().getName()
345: + ": endElement(...)");
346: }
347:
348: this .currentNode = this .currentNode.getParentNode();
349: this .processLevel--;
350: }
351:
352: /**
353: * Begin the scope of a prefix-URI Namespace mapping.
354: */
355: public final void startPrefixMapping(String _prefix, String _uri) {
356: return;
357: }
358:
359: /**
360: * End the scope of a prefix-URI mapping.
361: */
362: public final void endPrefixMapping(String prefix) {
363: // Ozone is not yet namespace aware, so we ignore the prefix mappings
364: }
365:
366: /**
367: * Receive notification of character data inside an element.
368: */
369: public final void characters(char[] ch, int start, int length) {
370:
371: if ((this .currentNode != null)
372: && (this .currentNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
373: //we receive a CDATA section
374:
375: StringBuffer chars = new StringBuffer();
376:
377: chars.append(ch, start, length);
378: chars.append(this .currentNode.getNodeValue());
379: this .currentNode.setNodeValue(chars.toString());
380: } else {
381: //just normal characters
382: Text textNode = this .domFactory.createTextNode(new String(
383: ch, start, length));
384: if (debug) {
385: System.out.println(this .getClass().getName()
386: + ": characters(...)");
387: }
388:
389: if (this .processLevel == 0) {
390: this .resultNodeList.addNode(textNode);
391: this .startNode = textNode;
392: this .currentNode = textNode;
393: if (this .appendTo != null) {
394: this .appendTo.appendChild(textNode);
395: }
396: } else {
397: this .currentNode.appendChild(textNode);
398: }
399: }
400: }
401:
402: /**
403: * Receive notification of a processing instruction.
404: */
405: public final void processingInstruction(String _target, String _data) {
406: ProcessingInstruction piNode = domFactory
407: .createProcessingInstruction(_target, _data);
408: if (debug) {
409: System.out.println(this .getClass().getName()
410: + ": processingInstruction(...)");
411: }
412:
413: if (this .processLevel == 0) {
414: this .resultNodeList.addNode(piNode);
415: this .startNode = piNode;
416: this .currentNode = piNode;
417: if (this .appendTo != null) {
418: this .appendTo.appendChild(piNode);
419: }
420: } else {
421: this .currentNode.appendChild(piNode);
422: }
423: }
424:
425: /**
426: * Receive notification of a skipped entity.
427: */
428: public final void skippedEntity(java.lang.String _name) {
429: EntityReference erNode = domFactory
430: .createEntityReference(_name);
431: if (debug) {
432: System.out.println(this .getClass().getName()
433: + ": skippedEntity(...)");
434: }
435:
436: if (this .processLevel == 0) {
437: this .resultNodeList.addNode(erNode);
438: this .startNode = erNode;
439: this .currentNode = erNode;
440: if (this .appendTo != null) {
441: this .appendTo.appendChild(erNode);
442: }
443: } else {
444: this .currentNode.appendChild(erNode);
445: }
446: }
447:
448: /**
449: * Receive notification of ignorable whitespace in element content.
450: */
451: public final void ignorableWhitespace(char[] _ch, int _start,
452: int _length) {
453: Text whitespace = domFactory.createTextNode(new String(_ch,
454: _start, _length));
455: if (debug) {
456: System.out.println(this .getClass().getName()
457: + ": ignorableWhitespace(...)");
458: }
459:
460: if (this .processLevel == 0) {
461: this .resultNodeList.addNode(whitespace);
462: this .startNode = whitespace;
463: this .currentNode = whitespace;
464: if (this .appendTo != null) {
465: this .appendTo.appendChild(whitespace);
466: }
467: } else {
468: this .currentNode.appendChild(whitespace);
469: }
470: }
471:
472: /**
473: * Receive an object for locating the origin of SAX document events.
474: */
475: public final void setDocumentLocator(Locator locator) {
476: // we don't care about the origin of the document
477: }
478:
479: //
480: // SAX LexicalHandler implemenation
481: //
482:
483: public void comment(char[] ch, int start, int length)
484: throws SAXException {
485:
486: Comment commentNode = this .domFactory.createComment(new String(
487: ch, start, length));
488:
489: if (debug) {
490: System.out.println(this .getClass().getName()
491: + ": comment(...)");
492: }
493:
494: if (this .processLevel == 0) {
495: this .resultNodeList.addNode(commentNode);
496: this .startNode = commentNode;
497: this .currentNode = commentNode;
498: if (this .appendTo != null) {
499: this .appendTo.appendChild(commentNode);
500: }
501: } else {
502: this .currentNode.appendChild(commentNode);
503: }
504: }
505:
506: public void startCDATA() throws SAXException {
507: CDATASection cdata = this .domFactory.createCDATASection("");
508: if (debug) {
509: System.out.println(this .getClass().getName()
510: + ": startCDATA(...)");
511: }
512:
513: if (this .processLevel == 0) {
514: this .resultNodeList.addNode(cdata);
515: this .startNode = cdata;
516: this .currentNode = cdata;
517: if (this .appendTo != null) {
518: this .appendTo.appendChild(cdata);
519: }
520: } else {
521: this .currentNode.appendChild(cdata);
522: this .currentNode = cdata;
523: }
524: this .processLevel++;
525: }
526:
527: public void endCDATA() throws SAXException {
528: if (debug) {
529: System.out.println(this .getClass().getName()
530: + ": endCDATA(...)");
531: }
532:
533: this .currentNode = this .currentNode.getParentNode();
534: this .processLevel--;
535: }
536:
537: public void startDTD(String name, String publicId, String systemId)
538: throws SAXException {
539: //FIXME(?)
540: //not handled
541: }
542:
543: public void endDTD() throws SAXException {
544: //FIXME(?)
545: //not handled
546: }
547:
548: public void startEntity(String name) throws SAXException {
549: //FIXME(?)
550: //not handled
551: }
552:
553: public void endEntity(String name) throws SAXException {
554: //FIXME(?)
555: //not handled
556: }
557: }
|