Source code: com/aendvari/common/model/xalan/XalanModelTree.java
1 /*
2 * ModelTree.java
3 *
4 * Copyright (c) 2001, 2002 Aendvari, Ltd. All Rights Reserved.
5 *
6 * See the file LICENSE for terms of use.
7 *
8 */
9
10 package com.aendvari.common.model.xalan;
11
12 import java.util.*;
13 import java.io.*;
14
15 import org.w3c.dom.Element;
16 import org.w3c.dom.Node;
17 import org.w3c.dom.NodeList;
18 import org.w3c.dom.NodeList;
19 import org.w3c.dom.Document;
20 import org.w3c.dom.traversal.NodeIterator;
21
22 import org.w3c.dom.DOMException;
23
24 import org.xml.sax.SAXException;
25 import org.xml.sax.SAXParseException;
26
27 import org.apache.xpath.*;
28 import org.apache.xpath.compiler.*;
29 import org.apache.xpath.objects.*;
30
31 import org.apache.xpath.compiler.Compiler;
32
33 import org.apache.xml.utils.PrefixResolverDefault;
34 import org.apache.xml.utils.DefaultErrorHandler;
35
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.parsers.DocumentBuilder;
38 import javax.xml.parsers.ParserConfigurationException;
39
40 import javax.xml.transform.TransformerException;
41 import javax.xml.transform.ErrorListener;
42
43 import com.aendvari.common.model.ModelNode;
44 import com.aendvari.common.model.ModelTree;
45 import com.aendvari.common.model.ModelException;
46 import com.aendvari.common.model.ModelParserException;
47 import com.aendvari.common.model.ModelQueryPath;
48
49
50 /**
51 * <p>A Xalan XML implementation of the {@link ModelNode} interface.</p>
52 *
53 * @author Scott Milne
54 *
55 */
56
57 public class XalanModelTree implements ModelTree
58 {
59 /* Variables */
60
61 /** The document that represents the model tree */
62 protected Document modelTreeDocument;
63
64 /** Tracks whether the XML DOM has been modified. */
65 protected boolean modified;
66
67 /** Runtime context for XPath queries. */
68 protected XPathContext xpathContext;
69
70 /** XPath expression parser. */
71 protected XPathParser xpathParser;
72
73 /** XPath query. */
74 protected XPath xpath;
75
76
77 /* Constructors. */
78
79
80 /**
81 * Constructs a <code>XalanModelTree</code> instance.
82 *
83 */
84
85 public XalanModelTree()
86 throws ModelException
87 {
88 // create an empty Document
89 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
90
91 try
92 {
93 DocumentBuilder builder = factory.newDocumentBuilder();
94 modelTreeDocument = builder.newDocument();
95 }
96 catch (ParserConfigurationException pce)
97 {
98 // Parser with specified options can't be built
99 pce.printStackTrace();
100 }
101
102 // initially, not modified
103 modified = false;
104
105 // create xpath objects
106 createXPathObjects();
107 }
108
109 /**
110 * Constructs a <code>XalanModelNode</code> instance wrapping the supplied {@link Document}.
111 *
112 * @param setModelTree The XML {@link Document} to wrap.
113 *
114 */
115
116 public XalanModelTree( Document setModelTree )
117 {
118 modelTreeDocument = setModelTree;
119 }
120
121 /**
122 * States that the XML DOM has been modified.
123 *
124 */
125
126 public void setModified()
127 {
128 modified = true;
129 }
130
131 /**
132 * Creates objects for performing XPath queries.
133 *
134 */
135
136 protected void createXPathObjects()
137 throws ModelException
138 {
139 // create context
140 xpathContext = new XPathContext();
141
142 // create default listener
143 ErrorListener errorListener = new DefaultErrorHandler();
144
145 // create parser
146 xpathParser = new XPathParser(errorListener, null);
147
148 // create xpath object
149 PrefixResolverDefault prefixResolver = new PrefixResolverDefault(
150 modelTreeDocument.getDocumentElement());
151
152 try
153 {
154 xpath = new XPath("/", null, prefixResolver, XPath.SELECT, null);
155 }
156 catch (TransformerException exception)
157 {
158 throw new ModelException(exception);
159 }
160 }
161
162
163 /* Accessors. */
164
165
166 /**
167 * Sets the internal XML {@link Document} object wrapped by this {@link ModelTree}.
168 *
169 * @param setModelTree The XML {@link Document} to wrap.
170 *
171 */
172
173 public void setModelTree( Document setModelTree )
174 {
175 modelTreeDocument = setModelTree;
176 }
177
178 /**
179 * Returns the internal XML {@link Document} object wrapped by this {@link ModelTree}.
180 *
181 * @return The XML {@link Document} wrapped by this {@link ModelTree}.
182 *
183 */
184
185 public Document getModelTree()
186 {
187 return modelTreeDocument;
188 }
189
190 /**
191 * Returns the {@link ModelNode} of the DOM model space.
192 *
193 * @return A {@link ModelNode} representing the {@link Document}
194 * of the DOM.
195 *
196 */
197
198 public ModelNode getRootNode()
199 {
200 return new XalanModelNode(this, modelTreeDocument);
201 }
202
203 /**
204 * Creates a {@link ModelNode} object.
205 *
206 * @param name The node's name.
207 *
208 * @return A new {@link ModelNode} object.
209 *
210 */
211
212 public ModelNode createNode(String name)
213 {
214 Element node = (Element) modelTreeDocument.createElement(name);
215 return new XalanModelNode(this, node);
216 }
217
218 /**
219 * Creates a new {@link ModelTree} object.
220 *
221 * @return A new {@link ModelTree} object.
222 *
223 */
224
225 public ModelTree createTree()
226 {
227 return new XalanModelTree();
228 }
229
230 /**
231 * Creates a {@link ModelNode} object with the supplied value.
232 *
233 * @param name The node's name.
234 * @param value The node's value.
235 *
236 * @return A new {@link ModelNode} object.
237 *
238 */
239
240 public ModelNode createNode(String name, String value)
241 {
242 ModelNode node = createNode(name);
243 XalanModelNode textNode = new XalanModelNode( this, modelTreeDocument.createTextNode(value) );
244 node.appendChild( textNode );
245 return node;
246 }
247
248 /**
249 * Returns the {@link ModelNode} using the path provided.
250 *
251 * @param node The starting position for the search.
252 * @param path The query expression.
253 *
254 * @return A {@link ModelNode} using the path provided.
255 *
256 * @exception ModelException The search could not be performed.
257 *
258 */
259
260 public ModelNode getNode( ModelNode node, String path ) throws ModelException
261 {
262 try
263 {
264 Node modelNode = ((XalanModelNode)node).getXmlNode();
265
266 Node tmpNode = null;
267 XalanModelNode xmlNode = null;
268
269 // get namespace node
270 Node namespaceNode = modelNode;
271
272 // resolve from the document's root element
273 if (namespaceNode == modelTreeDocument)
274 {
275 namespaceNode = modelTreeDocument.getDocumentElement();
276 }
277
278 // create resolver
279 PrefixResolverDefault prefixResolver = new PrefixResolverDefault(namespaceNode);
280
281 // reset XPathContext if DOM has been modified
282 if (modified)
283 {
284 modified = false;
285 xpathContext.reset();
286 }
287
288 // compile expression
289 Compiler xpathCompiler = new Compiler();
290 xpathParser.initXPath(xpathCompiler, path, prefixResolver);
291
292 Expression expression = xpathCompiler.compile(0);
293 xpath.setExpression(expression);
294
295 // get XObject of XPath expression
296 XObject object = xpath.execute(xpathContext, modelNode, prefixResolver);
297
298 // XObject object = XPathAPI.eval(modelNode, path); // TODO: remove
299
300 // convert XObject to node
301 if (object.getType() == XObject.CLASS_NODESET)
302 {
303 // first node in set is selected node
304 tmpNode = object.nodeset().nextNode();
305
306 // check for empty node set
307 if (tmpNode == null)
308 {
309 return null;
310 }
311 else
312 {
313 xmlNode = new XalanModelNode(this, tmpNode);
314 }
315 }
316 else
317 if (object.getType() == XObject.CLASS_BOOLEAN)
318 {
319 // get boolean value
320 if (object.bool())
321 xmlNode = new XalanModelNode(this, "true");
322 else
323 xmlNode = new XalanModelNode(this, "false");
324 }
325 else
326 if (object.getType() == XObject.CLASS_NUMBER)
327 {
328 // get number value
329 xmlNode = new XalanModelNode(this, String.valueOf(object.num()));
330 }
331 else
332 if (object.getType() == XObject.CLASS_STRING)
333 {
334 // get string value
335 xmlNode = new XalanModelNode(this, String.valueOf(object.toString()));
336 }
337
338 return xmlNode;
339 }
340 catch ( TransformerException exception )
341 {
342 throw new ModelException(exception);
343 }
344 }
345
346 /**
347 * Returns a <code>List</code> of {@link ModelNode}'s using the path provided.
348 *
349 * @param node The starting position for the search.
350 * @param path The query expression.
351 *
352 * @return A <code>List</code> of {@link ModelNode}'s using the path provided.
353 *
354 * @exception ModelException The search could not be performed.
355 *
356 */
357
358 public List getNodes( ModelNode node, String path ) throws ModelException
359 {
360 ArrayList xmlNodeList = new ArrayList();
361
362 try
363 {
364 Node modelNode = ((XalanModelNode)node).getXmlNode();
365
366 // get namespace node
367 Node namespaceNode = modelNode;
368
369 // resolve from the document's root element
370 if (namespaceNode == modelTreeDocument)
371 {
372 namespaceNode = modelTreeDocument.getDocumentElement();
373 }
374
375 // create resolver
376 PrefixResolverDefault prefixResolver = new PrefixResolverDefault(namespaceNode);
377
378 // reset XPathContext if DOM has been modified
379 if (modified)
380 {
381 modified = false;
382 xpathContext.reset();
383 }
384
385 // compile expression
386 Compiler xpathCompiler = new Compiler();
387 xpathParser.initXPath(xpathCompiler, path, prefixResolver);
388
389 Expression expression = xpathCompiler.compile(0);
390 xpath.setExpression(expression);
391
392 // get XObject of XPath expression
393 XObject object = xpath.execute(xpathContext, modelNode, prefixResolver);
394
395 // NodeList nodeList = XPathAPI.selectNodeList( modelNode, path );
396
397 // convert XObject to node list
398 if (object.getType() == XObject.CLASS_NODESET)
399 {
400 NodeIterator nodeIterator = object.nodeset();
401
402 if (nodeIterator != null)
403 {
404 while (true)
405 {
406 Node tmpNode = nodeIterator.nextNode();
407 if (tmpNode == null) break;
408
409 xmlNodeList.add( new XalanModelNode(this, tmpNode) );
410 }
411 }
412 }
413 else
414 if (object.getType() == XObject.CLASS_BOOLEAN)
415 {
416 // get boolean value
417 if (object.bool())
418 xmlNodeList.add(new XalanModelNode(this, "true"));
419 else
420 xmlNodeList.add(new XalanModelNode(this, "false"));
421 }
422 else
423 if (object.getType() == XObject.CLASS_NUMBER)
424 {
425 // get number value
426 xmlNodeList.add(new XalanModelNode(this, String.valueOf(object.num())));
427 }
428 else
429 if (object.getType() == XObject.CLASS_STRING)
430 {
431 // get string value
432 xmlNodeList.add(new XalanModelNode(this, String.valueOf(object.toString())));
433 }
434 }
435 catch ( TransformerException exception )
436 {
437 throw new ModelException(exception);
438 }
439
440 return xmlNodeList;
441 }
442
443 /**
444 * Replaces the current {@link ModelTree} with the given XML.
445 *
446 * @param xmlFile A path to the XML file.
447 *
448 * @exception ModelParserException The file could not be parsed.
449 *
450 */
451
452 public void loadFromFile( String xmlFile ) throws ModelParserException
453 {
454 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
455
456 try
457 {
458 DocumentBuilder builder = factory.newDocumentBuilder();
459 modelTreeDocument = builder.parse( new File(xmlFile) );
460 }
461 catch (Exception exception)
462 {
463 throw new ModelParserException(exception);
464 }
465 }
466
467 /**
468 * Replaces the current {@link ModelTree} with the given XML stream.
469 *
470 * @param xmlStream An {@link java.io.InputStream} instance.
471 *
472 * @exception ModelParserException The stream could not be parsed.
473 *
474 */
475
476 public void loadFromStream( InputStream xmlStream ) throws ModelParserException
477 {
478 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
479
480 try
481 {
482 DocumentBuilder builder = factory.newDocumentBuilder();
483 modelTreeDocument = builder.parse( xmlStream );
484 }
485 catch (Exception exception)
486 {
487 throw new ModelException(exception);
488 }
489 }
490 }
491