001: package com.rimfaxe.xml.xmlreader;
002:
003: import java.io.IOException;
004: import java.io.Writer;
005: import java.util.*;
006:
007: import com.rimfaxe.xml.xmlreader.xpath.*;
008:
009: /** An XML Document.
010:
011: <blockquote><small> Copyright (C) 2002 Hewlett-Packard Company.
012: This file is part of Sparta, an XML Parser, DOM, and XPath library.
013: This library is free software; you can redistribute it and/or
014: modify it under the terms of the GNU Lesser General Public License
015: as published by the Free Software Foundation; either version 2.1 of
016: the License, or (at your option) any later version. This library
017: is distributed in the hope that it will be useful, but WITHOUT ANY
018: WARRANTY; without even the implied warranty of MERCHANTABILITY or
019: FITNESS FOR A PARTICULAR PURPOSE.</small></blockquote>
020: @see <a "href="doc-files/LGPL.txt">GNU Lesser General Public License</a>
021: @version $Date: 2003/01/27 23:30:58 $ $Revision: 1.8 $
022: @author Eamonn O'Brien-Strain
023:
024:
025: @see org.w3c.dom.Document
026: */
027: public class Document extends Node {
028:
029: Document(String systemId) {
030: systemId_ = systemId;
031: }
032:
033: /**
034: * Create new empty in-memory Document with a null documentElement.
035: */
036: public Document() {
037: systemId_ = "MEMORY";
038: }
039:
040: /** Deep copy of this document. Any annotation is not copied. */
041: public Object clone() {
042: Document copy = new Document(systemId_);
043: copy.rootElement_ = (Element) rootElement_.clone();
044: return copy;
045: }
046:
047: /**
048: * @return the filename, URL, or other ID by which this document is known.
049: * Initialized to "MEMORY" for Document created with default constructor.
050: */
051: public String getSystemId() {
052: return systemId_;
053: }
054:
055: /**
056: * @param systemId the filename, URL, or other ID by which this document is known.
057: */
058: public void setSystemId(String systemId) {
059: systemId_ = systemId;
060: notifyObservers();
061: }
062:
063: /** Same as {@link #getSystemId getSystemId} */
064: public String toString() {
065: return systemId_;
066: }
067:
068: /**
069: * @return root element of this DOM tree.
070: */
071: public Element getDocumentElement() {
072: return rootElement_;
073: }
074:
075: /**
076: * Set the root element of this DOM tree.
077: */
078: public void setDocumentElement(Element rootElement) {
079: rootElement_ = rootElement;
080: rootElement_.setOwnerDocument(this );
081: notifyObservers();
082: }
083:
084: private XPathVisitor visitor(String xpath, boolean expectStringValue)
085: throws XPathException //, IOException
086: {
087: if (xpath.charAt(0) != '/')
088: xpath = "/" + xpath;
089: return visitor(XPath.get(xpath), expectStringValue);
090: }
091:
092: private XPathVisitor visitor(XPath parseTree,
093: boolean expectStringValue) throws XPathException //, IOException
094: {
095: if (parseTree.isStringValue() != expectStringValue) {
096: String msg = expectStringValue ? "evaluates to element not string"
097: : "evaluates to string not element";
098: throw new XPathException(parseTree, "\"" + parseTree
099: + "\" evaluates to " + msg);
100: }
101: return new XPathVisitor(this , parseTree);
102: }
103:
104: /** Select all the elements that match the absolute XPath
105: expression in this document. */
106: public Enumeration xpathSelectElements(String xpath)
107: throws ParseException //, IOException
108: {
109: try {
110:
111: return visitor(xpath, false).getResultEnumeration();
112:
113: } catch (XPathException e) {
114: throw new ParseException("XPath problem", e);
115: }
116: }
117:
118: /** Select all the strings that match the absolute XPath
119: expression in this document. */
120: public Enumeration xpathSelectStrings(String xpath)
121: throws ParseException {
122: try {
123:
124: return visitor(xpath, true).getResultEnumeration();
125:
126: } catch (XPathException e) {
127: throw new ParseException("XPath problem", e);
128: }
129: }
130:
131: /** Select the first element that matches the absolute XPath
132: expression in this document, or null if
133: there is no match. */
134: public Element xpathSelectElement(String xpath)
135: throws ParseException {
136: try {
137:
138: return visitor(xpath, false).getFirstResultElement();
139:
140: } catch (XPathException e) {
141: throw new ParseException("XPath problem", e);
142: }
143: }
144:
145: /** Select the first element that matches the absolute XPath
146: expression in this document, or null if
147: there is no match. */
148: public String xpathSelectString(String xpath) throws ParseException {
149: try {
150:
151: return visitor(xpath, true).getFirstResultString();
152:
153: } catch (XPathException e) {
154: throw new ParseException("XPath problem", e);
155: }
156: }
157:
158: /** Just like Element.xpathEnsure, but also handles case of no documentElement.
159: */
160: public boolean xpathEnsure(String xpath) throws ParseException {
161: try {
162:
163: //Quick exit for common case
164: if (xpathSelectElement(xpath) != null)
165: return false;
166:
167: //Split XPath into dirst step and bit relative to rootElement
168: final XPath parseTree = XPath.get(xpath);
169: int stepCount = 0;
170: for (Enumeration i = parseTree.getSteps(); i
171: .hasMoreElements();) {
172: i.nextElement();
173: ++stepCount;
174: }
175: Enumeration i = parseTree.getSteps();
176: Step firstStep = (Step) i.nextElement();
177: Step[] rootElemSteps = new Step[stepCount - 1];
178: for (int j = 0; j < rootElemSteps.length; ++j)
179: rootElemSteps[j] = (Step) i.nextElement();
180:
181: //Create root element if necessary
182: if (rootElement_ == null) {
183: Element newRoot = makeMatching(null, firstStep, xpath);
184: setDocumentElement(newRoot);
185: } else {
186: Element expectedRoot = xpathSelectElement("/"
187: + firstStep);
188: if (expectedRoot == null)
189: throw new ParseException("Existing root element <"
190: + rootElement_.getTagName()
191: + "...> does not match first step \""
192: + firstStep + "\" of \"" + xpath);
193: }
194:
195: if (rootElemSteps.length == 0)
196: return true;
197: else
198: return rootElement_.xpathEnsure(XPath.get(false,
199: rootElemSteps).toString());
200:
201: } catch (XPathException e) {
202: throw new ParseException(xpath, e);
203: }
204: }
205:
206: /** @see Document#xpathGetIndex(String) */
207: public class Index implements Observer {
208:
209: Index(XPath xpath) throws XPathException {
210: attrName_ = xpath.getIndexingAttrName();
211: xpath_ = xpath;
212: addObserver(this );
213: }
214:
215: /**
216: * @param a value of the indexing attribute
217: * @return enumeration of Elements
218: * @throws ParseException when XPath that created this Index is malformed.
219: */
220: public Enumeration get(String attrValue) throws ParseException {
221: if (dict_ == null)
222: regenerate();
223: Vector elemList = (Vector) dict_.get(attrValue);
224: return elemList == null ? EMPTY : elemList.elements();
225: }
226:
227: /**
228: * @return number of elements returned by {@link #get(String) get}
229: * @throws ParseException
230: */
231: public int size() throws ParseException {
232: if (dict_ == null)
233: regenerate();
234: return dict_.size();
235: }
236:
237: /**
238: * @see com.hp.hpl.sparta.Document.Observer#update(Document)
239: */
240: public void update(Document doc) {
241: dict_ = null; //force index to be regenerated on next get()
242: }
243:
244: private void regenerate() throws ParseException {
245: try {
246:
247: dict_ = new HashMap();
248: for (Enumeration i = visitor(xpath_, false)
249: .getResultEnumeration(); i.hasMoreElements();) {
250: Element elem = (Element) i.nextElement();
251: String attrValue = elem.getAttribute(attrName_);
252: Vector elemList = (Vector) dict_.get(attrValue);
253: if (elemList == null) {
254: elemList = new Vector(1);
255: dict_.put(attrValue, elemList);
256: }
257: elemList.addElement(elem);
258: }
259:
260: } catch (XPathException e) {
261: throw new ParseException("XPath problem", e);
262: }
263: }
264:
265: private Map dict_ = null;
266: private final XPath xpath_;
267: private final String attrName_;
268: }
269:
270: static private final Enumeration EMPTY = new EmptyEnumeration();
271:
272: /**
273: * @see #xpathGetIndex
274: * @return whether an index existst for this xpath
275: */
276: public boolean xpathHasIndex(String xpath) {
277: return indices_.get(xpath) != null;
278: }
279:
280: /**
281: * For faster lookup by XPath return (creating if necessary) an
282: * index. The xpath should be of the form "xp[@attrName]" where
283: * xp is an xpath, not ending in a "[...]" predicate, that returns
284: * a list of elements. Doing a get("foo") on the index is
285: * equivalent to doing an
286: * xpathSelectElement("xp[@attrName='foo']") on the document
287: * except that it is faster ( O(1) as apposed to O(n) ).
288: * EXAMPLE:<PRE>
289: * Enumeration leaders;
290: * if( doc.xpathHasIndex( "/Team/Members[@firstName]" ){
291: * //fast version
292: * Document.Index index = doc.xpathGetIndex( "/Team/Members[@role]" );
293: * leaders = index.get("leader");
294: * }else
295: * //slow version
296: * leaders = doc.xpathSelectElement( "/Team/Members[@role='leader']" );
297: *</PRE>
298: *
299: * */
300: public Index xpathGetIndex(String xpath) throws ParseException {
301: try {
302:
303: Index index = (Index) indices_.get(xpath);
304: //TODO: cacnonicalize key (use XPath object as key)
305: if (index == null) {
306: XPath xp = XPath.get(xpath);
307: index = new Index(xp);
308: indices_.put(xpath, index);
309: }
310: return index;
311:
312: } catch (XPathException e) {
313: throw new ParseException("XPath problem", e);
314: }
315: }
316:
317: public void removeIndices() {
318: indices_.clear();
319: }
320:
321: /** Something that is informed whenever the document changes. */
322: public interface Observer {
323: /** Called when the document changes. */
324: void update(Document doc);
325: }
326:
327: public void addObserver(Observer observer) {
328: observers_.addElement(observer);
329: }
330:
331: public void deleteObserver(Observer observer) {
332: observers_.remove(observer);
333: }
334:
335: /** Accumulate text nodes hierarchically. */
336: public void toString(Writer writer) throws IOException {
337: rootElement_.toString(writer);
338: }
339:
340: void notifyObservers() {
341: for (Enumeration i = observers_.elements(); i.hasMoreElements();)
342: ((Observer) i.nextElement()).update(this );
343: }
344:
345: /**
346: * Write DOM to XML.
347: */
348: public void toXml(Writer writer) throws IOException {
349: writer.write("<?xml version=\"1.0\" ?>\n");
350: rootElement_.toXml(writer);
351: }
352:
353: /** Two documents are equal IFF their document elements are equal. */
354: public boolean equals(Object thatO) {
355:
356: //Do cheap tests first
357: if (this == thatO)
358: return true;
359: if (!(thatO instanceof Document))
360: return false;
361: Document that = (Document) thatO;
362: return this .rootElement_.equals(that.rootElement_);
363: }
364:
365: /**
366: * @link aggregation
367: * @label documentElement
368: */
369: private Element rootElement_ = null;
370: private String systemId_;
371: private Map indices_ = new HashMap();
372: private Vector observers_ = new Vector();
373: }
374:
375: class EmptyEnumeration implements Enumeration {
376: public boolean hasMoreElements() {
377: return false;
378: }
379:
380: public Object nextElement() {
381: throw new NoSuchElementException();
382: }
383: }
384:
385: // $Log: Document.java,v $
386: // Revision 1.8 2003/01/27 23:30:58 yuhongx
387: // Replaced Hashtable with HashMap.
388: //
389: // Revision 1.7 2003/01/09 00:55:26 yuhongx
390: // Use jdk1.1 API (replaced add() with addElement()).
391: //
392: // Revision 1.6 2002/12/13 22:44:36 eobrain
393: // Remove redundant get/set annotation that is already in superclass.
394: //
395: // Revision 1.5 2002/12/13 18:12:16 eobrain
396: // Fix xpathEnsure to handle case when the XPath given specifies a root node tagname that conflicts with the existing root node. Extend xpathEnsure to work with any type of predicate. Replace hacky string manipulation code with code that works on the XPath parse tree.
397: //
398: // Revision 1.4 2002/11/06 02:57:59 eobrain
399: // Organize imputs to removed unused imports. Remove some unused local variables.
400: //
401: // Revision 1.3 2002/10/30 16:39:02 eobrain
402: // Fixed bug [ 627024 ] doc.xpathEnsure("/top") throws exception
403: // http://sourceforge.net/projects/sparta-xml/
404: //
405: // Revision 1.2 2002/09/12 23:00:57 eobrain
406: // Allow Document.xpathEnsure to work when there is no root element set.
407: //
408: // Revision 1.1.1.1 2002/08/19 05:03:55 eobrain
409: // import from HP Labs internal CVS
410: //
411: // Revision 1.24 2002/08/18 04:19:18 eob
412: // Sparta no longer throws XPathException -- it throws ParseException
413: // instead.
414: //
415: // Revision 1.23 2002/08/17 22:41:41 eob
416: // Add copyright and other formatting and commenting in preparation for
417: // release to SourceForge.
418: //
419: // Revision 1.22 2002/08/15 22:39:53 eob
420: // Fix bug in which index was not getting put into index hash.
421: //
422: // Revision 1.21 2002/08/15 21:25:18 eob
423: // Constructor no longer needs documenent.
424: //
425: // Revision 1.20 2002/08/15 05:07:48 eob
426: // Add indexing for fast XPath lookup.
427: //
428: // Revision 1.19 2002/08/13 22:54:39 eob
429: // Added xpath indexing for faster lookup.
430: //
431: // Revision 1.18 2002/07/25 21:10:15 sermarti
432: // Adding files that mysteriously weren't added from Sparta before.
433: //
434: // Revision 1.17 2002/06/14 19:36:42 eob
435: // Make toString of Node do the same as in XSLT -- recursive
436: // concatenation of all text in text nodes.
437: //
438: // Revision 1.16 2002/05/23 21:04:35 eob
439: // Add better error reporting.
440: //
441: // Revision 1.15 2002/05/10 21:37:42 eob
442: // equals added
443: //
444: // Revision 1.14 2002/05/09 16:47:50 eob
445: // Add cloneDocument
446: //
447: // Revision 1.13 2002/03/26 01:41:11 eob
448: // Deprecate XPathAPI
449: //
450: // Revision 1.12 2002/02/23 01:43:26 eob
451: // Add clone method. Tweak toXml API.
452: //
453: // Revision 1.11 2002/02/15 21:20:35 eob
454: // Rename xpath* methods to xpathSelect* to make more obvious.
455: //
456: // Revision 1.10 2002/02/15 21:05:28 eob
457: // Add convenient xpath* methods, allowing a more object-oriented use than
458: // XPathAPI.
459: //
460: // Revision 1.9 2002/02/04 22:09:04 eob
461: // Add defualt constructer.
462: //
463: // Revision 1.8 2002/02/01 21:49:45 eob
464: // Make Document inherit from Node. Needed for XPath.
465: //
466: // Revision 1.7 2002/01/04 00:36:52 eob
467: // add annotation
468: //
469: // Revision 1.6 2002/01/04 16:48:44 eob
470: // Comment changes only.
471: //
472: // Revision 1.5 2002/01/04 14:48:56 eob
473: // Remove Log
474: //
475: // Revision 1.4 2002/01/04 14:46:21 eob
476: // comment change only
477: //
478: // Revision 1.3 2002/01/04 14:39:19 eob
479: // Move parse functionality functionality to ParseSource.
480: //
481: // Revision 1.2 2001/12/20 20:06:28 eob
482: // Fix some entity bugs. Use UTD-8 or UTF-16 encoding as appropriate.
483: //
484: // Revision 1.1 2001/12/19 05:52:38 eob
485: // initial
|