001: package net.sf.saxon.dom;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.event.Receiver;
005: import net.sf.saxon.event.PipelineConfiguration;
006: import net.sf.saxon.expr.XPathContext;
007: import net.sf.saxon.om.*;
008: import net.sf.saxon.trans.XPathException;
009: import net.sf.saxon.trans.DynamicError;
010: import net.sf.saxon.value.SequenceExtent;
011: import net.sf.saxon.value.Value;
012: import org.w3c.dom.Document;
013: import org.w3c.dom.Node;
014: import org.w3c.dom.NodeList;
015:
016: import javax.xml.transform.Result;
017: import javax.xml.transform.Source;
018: import javax.xml.transform.dom.DOMResult;
019: import javax.xml.transform.dom.DOMSource;
020: import javax.xml.parsers.DocumentBuilderFactory;
021: import javax.xml.parsers.DocumentBuilder;
022: import javax.xml.parsers.ParserConfigurationException;
023: import java.util.ArrayList;
024: import java.util.List;
025: import java.util.HashSet;
026: import java.io.Serializable;
027:
028: /**
029: * This interface must be implemented by any third-party object model that can
030: * be wrapped with a wrapper that implements the Saxon Object Model (the NodeInfo interface).
031: * This implementation of the interface supports wrapping of DOM Documents.
032: */
033:
034: public class DOMObjectModel implements ExternalObjectModel,
035: Serializable {
036:
037: public DOMObjectModel() {
038: }
039:
040: /**
041: * Test whether this object model recognizes a given node as one of its own
042: */
043:
044: public boolean isRecognizedNode(Object object) {
045: return object instanceof Node;
046: }
047:
048: /**
049: * Test whether this object model recognizes a given class as representing a
050: * node in that object model. This method will generally be called at compile time.
051: *
052: * @param nodeClass A class that possibly represents nodes
053: * @return true if the class is used to represent nodes in this object model
054: */
055:
056: public boolean isRecognizedNodeClass(Class nodeClass) {
057: return Node.class.isAssignableFrom(nodeClass);
058: }
059:
060: /**
061: * Test whether this object model recognizes a given class as representing a
062: * list of nodes in that object model. This method will generally be called at compile time.
063: *
064: * @param nodeClass A class that possibly represents nodes
065: * @return true if the class is used to represent nodes in this object model
066: */
067:
068: public boolean isRecognizedNodeListClass(Class nodeClass) {
069: return NodeList.class.isAssignableFrom(nodeClass);
070: }
071:
072: /**
073: * Test whether this object model recognizes a particular kind of JAXP Result object,
074: * and if it does, return a Receiver that builds an instance of this data model from
075: * a sequence of events. If the Result is not recognised, return null.
076: */
077:
078: public Receiver getDocumentBuilder(Result result)
079: throws XPathException {
080: if (result instanceof DOMResult) {
081: DOMEmitter emitter = new DOMEmitter();
082: Node root = ((DOMResult) result).getNode();
083: if (root == null) {
084: try {
085: DocumentBuilderFactory dfactory = DocumentBuilderFactory
086: .newInstance();
087: DocumentBuilder docBuilder = dfactory
088: .newDocumentBuilder();
089: Document out = docBuilder.newDocument();
090: ((DOMResult) result).setNode(out);
091: emitter.setNode(out);
092: } catch (ParserConfigurationException e) {
093: throw new DynamicError(e);
094: }
095: } else {
096: emitter.setNode(root);
097: }
098: return emitter;
099: }
100: return null;
101: }
102:
103: /**
104: * Test whether this object model recognizes a particular kind of JAXP Source object,
105: * and if it does, send the contents of the document to a supplied Receiver, and return true.
106: * Otherwise, return false
107: */
108:
109: public boolean sendSource(Source source, Receiver receiver,
110: PipelineConfiguration pipe) throws XPathException {
111: if (source instanceof DOMSource) {
112: Node startNode = ((DOMSource) source).getNode();
113: DOMSender driver = new DOMSender();
114: driver.setStartNode(startNode);
115: driver.setReceiver(receiver);
116: driver.setPipelineConfiguration(pipe);
117: driver.setSystemId(source.getSystemId());
118: driver.send();
119: return true;
120: }
121: return false;
122: }
123:
124: /**
125: * Wrap or unwrap a node using this object model to return the corresponding Saxon node. If the supplied
126: * source does not belong to this object model, return null
127: */
128:
129: public NodeInfo unravel(Source source, Configuration config) {
130:
131: if (source instanceof DOMSource) {
132: NodeInfo start;
133: Node dsnode = ((DOMSource) source).getNode();
134: if (dsnode instanceof NodeOverNodeInfo) {
135: // Supplied source is a DOM Node wrapping a Saxon node: unwrap it
136: start = ((NodeOverNodeInfo) dsnode)
137: .getUnderlyingNodeInfo();
138: } else {
139: // Supplied source is an ordinary DOM Node: wrap it
140: Document dom;
141: if (dsnode.getNodeType() == Node.DOCUMENT_NODE) {
142: dom = (Document) dsnode;
143: } else {
144: dom = dsnode.getOwnerDocument();
145: }
146: DocumentWrapper docWrapper = new DocumentWrapper(dom,
147: source.getSystemId(), config);
148: start = docWrapper.wrap(dsnode);
149: }
150: return start;
151: }
152: return null;
153: }
154:
155: /**
156: * Convert a Java object to an XPath value. If the supplied object is recognized as a representation
157: * of a value using this object model, the object model should convert the value to an XPath value
158: * and return this as the result. If not, it should return null. If the object is recognized but cannot
159: * be converted, an exception should be thrown
160: */
161:
162: public Value convertObjectToXPathValue(Object object,
163: Configuration config) throws XPathException {
164: if (object instanceof org.w3c.dom.NodeList) {
165: NodeList list = ((NodeList) object);
166: NodeInfo[] nodes = new NodeInfo[list.getLength()];
167: for (int i = 0; i < list.getLength(); i++) {
168: if (list.item(i) instanceof NodeOverNodeInfo) {
169: nodes[i] = ((NodeOverNodeInfo) list.item(i))
170: .getUnderlyingNodeInfo();
171: } else {
172: DocumentInfo doc = wrapDocument(list.item(i), "",
173: config);
174: NodeInfo node = wrapNode(doc, list.item(i));
175: nodes[i] = node;
176: }
177: }
178: return new SequenceExtent(nodes);
179:
180: // Note, we accept the nodes in the order returned by the function; there
181: // is no requirement that this should be document order.
182: } else if (object instanceof NodeOverNodeInfo) {
183: return Value.asValue(((NodeOverNodeInfo) object)
184: .getUnderlyingNodeInfo());
185: } else {
186: return null;
187: }
188: }
189:
190: /**
191: * Convert an XPath value to an object in this object model. If the supplied value can be converted
192: * to an object in this model, of the specified class, then the conversion should be done and the
193: * resulting object returned. If the value cannot be converted, the method should return null. Note
194: * that the supplied class might be a List, in which case the method should inspect the contents of the
195: * Value to see whether they belong to this object model.
196: * @throws XPathException if the target class is explicitly associated with this object model, but the
197: * supplied value cannot be converted to the appropriate class
198: */
199:
200: public Object convertXPathValueToObject(Value value, Class target,
201: XPathContext context) throws XPathException {
202: // We accept the object if (a) the target class is Node, Node[], or NodeList,
203: // or (b) the supplied object is a node, or sequence of nodes, that wrap DOM nodes,
204: // provided that the target class is Object or a collection class
205: boolean requireDOM = (Node.class.isAssignableFrom(target)
206: || (target == NodeList.class) || (target.isArray() && Node.class
207: .isAssignableFrom(target.getComponentType())));
208:
209: // Note: we allow the declared type of the method argument to be a subclass of Node. If the actual
210: // node supplied is the wrong kind of node, this will result in a Java exception.
211:
212: boolean allowDOM = (target == Object.class
213: || target.isAssignableFrom(ArrayList.class)
214: || target.isAssignableFrom(HashSet.class) || (target
215: .isArray() && target.getComponentType() == Object.class));
216: if (!(requireDOM || allowDOM)) {
217: return null;
218: }
219: List nodes = new ArrayList(20);
220:
221: SequenceIterator iter = value.iterate(context);
222: while (true) {
223: Item item = iter.next();
224: if (item == null) {
225: break;
226: }
227: if (item instanceof VirtualNode) {
228: Object o = ((VirtualNode) item).getUnderlyingNode();
229: if (o instanceof Node) {
230: nodes.add(o);
231: } else {
232: if (requireDOM) {
233: DynamicError err = new DynamicError(
234: "Extension function required class "
235: + target.getName()
236: + "; supplied value of class "
237: + item.getClass().getName()
238: + " could not be converted");
239: throw err;
240: }
241: ;
242: }
243: } else if (requireDOM) {
244: if (item instanceof NodeInfo) {
245: nodes.add(NodeOverNodeInfo.wrap((NodeInfo) item));
246: } else {
247: DynamicError err = new DynamicError(
248: "Extension function required class "
249: + target.getName()
250: + "; supplied value of class "
251: + item.getClass().getName()
252: + " could not be converted");
253: throw err;
254: }
255: } else {
256: return null; // DOM Nodes are not actually required; let someone else try the conversion
257: }
258: }
259:
260: if (nodes.size() == 0 && !requireDOM) {
261: return null; // empty sequence supplied - try a different mapping
262: }
263: if (Node.class.isAssignableFrom(target)) {
264: if (nodes.size() != 1) {
265: DynamicError err = new DynamicError(
266: "Extension function requires a single DOM Node"
267: + "; supplied value contains "
268: + nodes.size() + " nodes");
269: throw err;
270: }
271: return nodes.get(0);
272: // could fail if the node is of the wrong kind
273: } else if (target == NodeList.class) {
274: return new DOMNodeList(nodes);
275: } else if (target.isArray()
276: && target.getComponentType() == Node.class) {
277: Node[] array = new Node[nodes.size()];
278: nodes.toArray(array);
279: return array;
280: } else if (target.isAssignableFrom(ArrayList.class)) {
281: return nodes;
282: } else if (target.isAssignableFrom(HashSet.class)) {
283: return new HashSet(nodes);
284: } else {
285: // after all this work, give up
286: return null;
287: }
288: }
289:
290: /**
291: * Wrap a document node in the external object model in a document wrapper that implements
292: * the Saxon DocumentInfo interface. (However, if the supplied object is a wrapper for a Saxon
293: * NodeInfo object, then we <i>unwrap</i> it.
294: * @param node a node (any node) in the third party document
295: * @param baseURI the base URI of the node (supply "" if unknown)
296: * @param config the Saxon configuration (which among other things provides access to the NamePool)
297: * @return the wrapper, which must implement DocumentInfo
298: */
299:
300: public DocumentInfo wrapDocument(Object node, String baseURI,
301: Configuration config) {
302: if (node instanceof DocumentOverNodeInfo) {
303: return (DocumentInfo) ((DocumentOverNodeInfo) node)
304: .getUnderlyingNodeInfo();
305: }
306: if (node instanceof NodeOverNodeInfo) {
307: return ((NodeOverNodeInfo) node).getUnderlyingNodeInfo()
308: .getDocumentRoot();
309: }
310: if (node instanceof org.w3c.dom.Node) {
311: if (((Node) node).getNodeType() == Node.DOCUMENT_NODE) {
312: Document doc = (org.w3c.dom.Document) node;
313: return new DocumentWrapper(doc, baseURI, config);
314: } else {
315: Document doc = ((org.w3c.dom.Node) node)
316: .getOwnerDocument();
317: return new DocumentWrapper(doc, baseURI, config);
318: }
319: }
320: throw new IllegalArgumentException("Unknown node class "
321: + node.getClass());
322: }
323:
324: /**
325: * Wrap a node within the external object model in a node wrapper that implements the Saxon
326: * VirtualNode interface (which is an extension of NodeInfo)
327: * @param document the document wrapper, as a DocumentInfo object
328: * @param node the node to be wrapped. This must be a node within the document wrapped by the
329: * DocumentInfo provided in the first argument
330: * @return the wrapper for the node, as an instance of VirtualNode
331: */
332:
333: public NodeInfo wrapNode(DocumentInfo document, Object node) {
334: return ((DocumentWrapper) document).wrap((Node) node);
335: }
336:
337: /**
338: * Convert a sequence of values to a NODELIST, as defined in the JAXP XPath API spec. This method
339: * is used when the evaluate() request specifies the return type as NODELIST, regardless of the
340: * actual results of the expression. If the sequence contains things other than nodes, the fallback
341: * is to return the sequence as a Java List object. The method can return null to invoke fallback
342: * behaviour.
343: */
344:
345: public Object convertToNodeList(SequenceExtent extent) {
346: try {
347: NodeList nodeList = DOMNodeList.checkAndMake(extent);
348: return nodeList;
349: } catch (XPathException e) {
350: return null;
351: }
352: }
353: }
354:
355: //
356: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
357: // you may not use this file except in compliance with the License. You may obtain a copy of the
358: // License at http://www.mozilla.org/MPL/
359: //
360: // Software distributed under the License is distributed on an "AS IS" basis,
361: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
362: // See the License for the specific language governing rights and limitations under the License.
363: //
364: // The Original Code is: all this file.
365: //
366: // The Initial Developer of the Original Code is Michael H. Kay.
367: //
368: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
369: //
370: // Contributor(s): Gunther Schadow (changes to allow access to public fields; also wrapping
371: // of extensions and mapping of null to empty sequence).
372: //
|