001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.xml2;
030:
031: import com.caucho.log.Log;
032: import com.caucho.util.CharBuffer;
033: import com.caucho.util.L10N;
034:
035: import org.w3c.dom.*;
036: import org.xml.sax.Attributes;
037: import org.xml.sax.ContentHandler;
038: import org.xml.sax.ErrorHandler;
039: import org.xml.sax.Locator;
040: import org.xml.sax.SAXException;
041: import org.xml.sax.SAXParseException;
042:
043: import javax.xml.namespace.QName;
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.logging.Level;
047: import java.util.logging.Logger;
048:
049: /**
050: * XMLWriter to create a DOM document.
051: */
052: public class DOMBuilder implements XMLWriter, ContentHandler,
053: ErrorHandler {
054: static final Logger log = Log.open(DOMBuilder.class);
055: static final L10N L = new L10N(DOMBuilder.class);
056: static final String XMLNS = XmlParser.XMLNS;
057:
058: private QDocument _doc;
059: private Node _top;
060: private Node _node;
061:
062: private String _singleText;
063: private CharBuffer _text = new CharBuffer();
064:
065: private boolean _escapeText;
066: private boolean _strictXml;
067:
068: // If true, text and cdata sections should be combined.
069: private boolean _isCoalescing = true;
070: // If true, ignorable whitespace is skipped
071: private boolean _skipWhitespace = false;
072:
073: private ArrayList<QName> _prefixNames = new ArrayList<QName>();
074: private ArrayList<String> _prefixValues = new ArrayList<String>();
075:
076: private Locator _locator;
077: private ExtendedLocator _extLocator;
078: private String _systemId;
079:
080: public DOMBuilder() {
081: }
082:
083: public void init(Node top) {
084: if (top instanceof QDocument)
085: _doc = (QDocument) top;
086: else
087: _doc = (QDocument) top.getOwnerDocument();
088: _top = top;
089: _node = top;
090:
091: _singleText = null;
092:
093: _prefixNames.clear();
094: _prefixValues.clear();
095: }
096:
097: public void setSystemId(String systemId) {
098: _systemId = systemId;
099:
100: if (systemId != null && _top instanceof Document) {
101: Document tdoc = (Document) _top;
102: DocumentType dtd = tdoc.getDoctype();
103: if (tdoc instanceof QDocument && dtd == null) {
104: dtd = new QDocumentType(null);
105: ((QDocument) tdoc).setDoctype(dtd);
106: }
107:
108: if (dtd instanceof QDocumentType
109: && dtd.getSystemId() == null)
110: ((QDocumentType) dtd).setSystemId(systemId);
111: }
112:
113: if (_doc != null)
114: _doc.setSystemId(systemId);
115: }
116:
117: public String getSystemId() {
118: return _systemId;
119: }
120:
121: public void setFilename(String filename) {
122: if (filename != null && _top instanceof QDocument) {
123: _doc.setRootFilename(filename);
124: }
125: }
126:
127: /**
128: * Set true if we're only handling strict xml.
129: */
130: public void setStrictXML(boolean isStrictXml) {
131: _strictXml = isStrictXml;
132: }
133:
134: /**
135: * Set true if text and cdata nodes should be combined.
136: */
137: public void setCoalescing(boolean isCoalescing) {
138: _isCoalescing = isCoalescing;
139: }
140:
141: /**
142: * Set true if ignorable whitespace should be skipped.
143: */
144: public void setSkipWhitespace(boolean skipWhitespace) {
145: _skipWhitespace = skipWhitespace;
146: }
147:
148: public void setDocumentLocator(Locator loc) {
149: if (_doc == null) {
150: _doc = new QDocument();
151: _node = _doc;
152: _top = _doc;
153: }
154:
155: _locator = loc;
156:
157: if (loc instanceof ExtendedLocator)
158: _extLocator = (ExtendedLocator) loc;
159:
160: if (_extLocator != null && _doc.getSystemId() == null)
161: _doc.setLocation(_extLocator.getSystemId(), _extLocator
162: .getFilename(), _extLocator.getLineNumber(),
163: _extLocator.getColumnNumber());
164: }
165:
166: public void startPrefixMapping(String prefix, String url) {
167: if (_node == null || _node == _top)
168: _doc.addNamespace(prefix, url);
169:
170: if (prefix.equals("")) {
171: _prefixNames.add(new QName(null, "xmlns", XmlParser.XMLNS));
172: _prefixValues.add(url);
173: } else {
174: _prefixNames
175: .add(new QName("xmlns", prefix, XmlParser.XMLNS));
176: _prefixValues.add(url);
177: }
178: }
179:
180: public void endPrefixMapping(String prefix) {
181: }
182:
183: public Node getNode() {
184: return _top;
185: }
186:
187: public void startDocument() {
188: if (_doc == null) {
189: _doc = new QDocument();
190: _node = _doc;
191: _top = _doc;
192: }
193: }
194:
195: public void endDocument() throws SAXException {
196: popText();
197: }
198:
199: public void setLocation(String filename, int line, int column) {
200: }
201:
202: public void startElement(String uri, String localName, String qName)
203: throws IOException {
204: popText();
205:
206: Element elt;
207:
208: if (uri != null && !uri.equals(""))
209: elt = _doc.createElementNS(uri, qName);
210: else if (!qName.equals(""))
211: elt = _doc.createElement(qName);
212: else
213: elt = _doc.createElement(localName);
214:
215: if (_node == _doc) {
216: if (_doc.getDocumentElement() == null)
217: ((QDocument) _doc).setDocumentElement(elt);
218: }
219:
220: _node.appendChild(elt);
221: _node = elt;
222:
223: if (_extLocator != null && elt instanceof QElement) {
224: ((QElement) elt).setLocation(_extLocator.getSystemId(),
225: _extLocator.getFilename(), _extLocator
226: .getLineNumber(), _extLocator
227: .getColumnNumber());
228: }
229: }
230:
231: public void startElement(QName name, QAttributes attributes)
232: throws SAXException {
233: popText();
234:
235: QElement elt = (QElement) _doc.createElementByName(name);
236: _node.appendChild(elt);
237: _node = elt;
238:
239: if (_node == _doc) {
240: if (_doc.getDocumentElement() == null)
241: ((QDocument) _doc).setDocumentElement(elt);
242: }
243:
244: for (int i = 0; i < _prefixNames.size(); i++) {
245: QName attrName = _prefixNames.get(i);
246: String value = _prefixValues.get(i);
247:
248: elt.setAttribute(attrName, value);
249: }
250:
251: _prefixNames.clear();
252: _prefixValues.clear();
253:
254: int length = attributes.getLength();
255: for (int i = 0; i < length; i++) {
256: QName attrName = attributes.getName(i);
257: String value = attributes.getValue(i);
258:
259: elt.setAttribute(attrName, value);
260: }
261:
262: if (_extLocator != null) {
263: elt.setLocation(_extLocator.getSystemId(), _extLocator
264: .getFilename(), _extLocator.getLineNumber(),
265: _extLocator.getColumnNumber());
266: }
267:
268: QDocumentType dtd = (QDocumentType) _doc.getDoctype();
269: if (dtd != null)
270: dtd.fillDefaults(elt);
271: }
272:
273: public void startElement(String uri, String localName,
274: String qName, Attributes attributes) throws SAXException {
275: popText();
276:
277: Element elt;
278:
279: if (uri != null && !uri.equals(""))
280: elt = _doc.createElementNS(uri, qName);
281: else if (!qName.equals(""))
282: elt = _doc.createElement(qName);
283: else
284: elt = _doc.createElement(localName);
285:
286: if (_node == _doc) {
287: if (_doc.getDocumentElement() == null)
288: ((QDocument) _doc).setDocumentElement(elt);
289: else if (_strictXml)
290: throw error(L.l(
291: "expected a single top-level element at `{0}'",
292: qName));
293: }
294:
295: _node.appendChild(elt);
296: _node = elt;
297:
298: int length = attributes.getLength();
299: for (int i = 0; i < length; i++) {
300: String attrUri = attributes.getURI(i);
301: String attrQname = attributes.getQName(i);
302: String value = attributes.getValue(i);
303:
304: Attr attr;
305:
306: if (attrUri != null && !attrUri.equals(""))
307: attr = _doc.createAttributeNS(attrUri, attrQname);
308: else if (!attrQname.equals(""))
309: attr = _doc.createAttribute(attrQname);
310: else
311: attr = _doc.createAttribute(attributes.getLocalName(i));
312:
313: attr.setNodeValue(value);
314:
315: ((Element) _node).setAttributeNode(attr);
316: }
317:
318: if (_extLocator != null)
319: ((QElement) elt).setLocation(_extLocator.getSystemId(),
320: _extLocator.getFilename(), _extLocator
321: .getLineNumber(), _extLocator
322: .getColumnNumber());
323:
324: QDocumentType dtd = (QDocumentType) _doc.getDoctype();
325: if (dtd != null) {
326: dtd.fillDefaults((QElement) elt);
327: }
328: }
329:
330: public void dtd(QDocumentType dtd) {
331: ((QDocument) _doc).setDoctype(dtd);
332:
333: ((QDocument) _doc).appendChild(dtd);
334: }
335:
336: public void attribute(String uri, String localName, String qName,
337: String value) throws IOException {
338: if (_node instanceof Element) {
339: Attr attr = _doc.createAttributeNS(uri, qName);
340: attr.setNodeValue(value);
341:
342: ((Element) _node).setAttributeNode(attr);
343: } else
344: ((QDocument) _node).setAttribute(qName, value);
345: }
346:
347: public void endElement(String uri, String localName, String qName) {
348: popText();
349:
350: if (_node != null) // XXX:
351: _node = _node.getParentNode();
352: if (_node == null)
353: _node = _doc;
354: }
355:
356: public void processingInstruction(String name, String data) {
357: popText();
358:
359: ProcessingInstruction pi = _doc.createProcessingInstruction(
360: name, data);
361:
362: _node.appendChild(pi);
363: }
364:
365: /**
366: * Handles the callback for a comment.
367: *
368: * @param data the content of the comment.
369: */
370: public void comment(char[] buf, int offset, int length)
371: throws SAXException {
372: try {
373: comment(new String(buf, offset, length));
374: } catch (IOException e) {
375: throw new SAXException(e);
376: }
377: }
378:
379: /**
380: * Handles the callback for a comment.
381: *
382: * @param data the content of the comment.
383: */
384: public void comment(String data) throws IOException {
385: popText();
386:
387: Comment comment = _doc.createComment(data);
388:
389: _node.appendChild(comment);
390: }
391:
392: public boolean getEscapeText() {
393: return _escapeText;
394: }
395:
396: public void setEscapeText(boolean isEscaped) {
397: _escapeText = isEscaped;
398: }
399:
400: public void text(String text) throws IOException {
401: if (_singleText == null && _text.length() == 0) {
402: if (!text.equals(""))
403: _singleText = text;
404: } else if (_singleText != null) {
405: _text.append(_singleText);
406: _text.append(text);
407: } else
408: _text.append(text);
409:
410: if (!_isCoalescing)
411: popText();
412: }
413:
414: public void text(char[] buffer, int offset, int length)
415: throws IOException {
416: if (length == 0)
417: return;
418:
419: if (_singleText != null) {
420: _singleText = null;
421: _text.append(_singleText);
422: }
423: _text.append(buffer, offset, length);
424:
425: if (!_isCoalescing)
426: popText();
427: }
428:
429: /**
430: * Adds text characters to the current document.
431: */
432: public void characters(char[] buffer, int offset, int length)
433: throws SAXException {
434: if (length == 0)
435: return;
436:
437: if (_strictXml && _node == _doc) {
438: if (_doc.getDocumentElement() == null) {
439: while (length > 0
440: && XmlChar.isWhitespace(buffer[offset])) {
441: offset++;
442: length--;
443: }
444:
445: for (int i = 0; i < length; i++) {
446: if (buffer[offset + i] == '\n'
447: || buffer[offset + i] == '\r') {
448: length = i;
449: break;
450: }
451: }
452:
453: if (length > 16)
454: length = 16;
455:
456: if (length > 0)
457: throw error(L.l("expected top element at `{0}'",
458: new String(buffer, offset, length)));
459: }
460: }
461:
462: _text.append(buffer, offset, length);
463:
464: /*
465: if (! isCoalescing)
466: popText();
467: */
468: }
469:
470: /**
471: * Handles the callback for ignorable whitespace.
472: *
473: * @param buffer the character buffer containing the whitespace.
474: * @param offset starting offset into the character buffer.
475: * @param length number of characters in the buffer.
476: */
477: public void ignorableWhitespace(char[] buffer, int offset,
478: int length) throws SAXException {
479: if (!_skipWhitespace)
480: characters(buffer, offset, length);
481: }
482:
483: public void entityReference(String name) {
484: popText();
485:
486: QEntityReference er = new QEntityReference(name);
487: er._owner = (QDocument) _doc;
488:
489: _node.appendChild(er);
490: }
491:
492: public void skippedEntity(String s) {
493: _text.append(s);
494: }
495:
496: public void cdata(String text) throws IOException {
497: popText();
498:
499: _node.appendChild(_doc.createCDATASection(text));
500: }
501:
502: public void cdata(char[] buffer, int offset, int length)
503: throws IOException {
504: cdata(new String(buffer, offset, length));
505: }
506:
507: private void popText() {
508: if (_singleText != null) {
509: Node text = _doc.createTextNode(_singleText);
510: _node.appendChild(text);
511:
512: _singleText = null;
513: return;
514: }
515:
516: if (_text.length() == 0)
517: return;
518:
519: Node text = _doc.createTextNode(_text.toString());
520: _text.clear();
521:
522: _node.appendChild(text);
523: }
524:
525: public void fatalError(SAXParseException e) throws SAXException {
526: log.log(Level.FINE, e.toString(), e);
527:
528: throw error(e.getMessage());
529: }
530:
531: public void error(SAXParseException e) throws SAXException {
532: log.log(Level.FINER, e.toString(), e);
533:
534: throw error(e.getMessage());
535: }
536:
537: public void warning(SAXParseException e) throws SAXException {
538: log.log(Level.FINER, e.toString(), e);
539: }
540:
541: /**
542: * Throws an appropriate error.
543: */
544: public SAXException createError(Exception e) {
545: if (e instanceof SAXException)
546: return (SAXException) e;
547: else
548: return new SAXException(e);
549: }
550:
551: /**
552: * Returns a new parse exception with filename and line if available.
553: */
554: XmlParseException error(String text) {
555: if (_extLocator != null)
556: return new XmlParseException(_extLocator.getFilename()
557: + ":" + _extLocator.getLineNumber() + ": " + text);
558: else if (_locator != null)
559: return new XmlParseException(_locator.getSystemId() + ":"
560: + _locator.getLineNumber() + ": " + text);
561: else
562: return new XmlParseException(text);
563: }
564: }
|