001: /* $Id: NodeCreateRule.java 472835 2006-11-09 09:59:53Z skitching $
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: package org.apache.commons.digester;
020:
021: import javax.xml.parsers.DocumentBuilder;
022: import javax.xml.parsers.DocumentBuilderFactory;
023: import javax.xml.parsers.ParserConfigurationException;
024:
025: import org.w3c.dom.Attr;
026: import org.w3c.dom.DOMException;
027: import org.w3c.dom.Document;
028: import org.w3c.dom.Element;
029: import org.w3c.dom.Node;
030: import org.xml.sax.Attributes;
031: import org.xml.sax.ContentHandler;
032: import org.xml.sax.SAXException;
033: import org.xml.sax.helpers.DefaultHandler;
034:
035: /**
036: * A rule implementation that creates a DOM
037: * {@link org.w3c.dom.Node Node} containing the XML at the element that matched
038: * the rule. Two concrete types of nodes can be created by this rule:
039: * <ul>
040: * <li>the default is to create an {@link org.w3c.dom.Element Element} node.
041: * The created element will correspond to the element that matched the rule,
042: * containing all XML content underneath that element.</li>
043: * <li>alternatively, this rule can create nodes of type
044: * {@link org.w3c.dom.DocumentFragment DocumentFragment}, which will contain
045: * only the XML content under the element the rule was trigged on.</li>
046: * </ul>
047: * The created node will be normalized, meaning it will not contain text nodes
048: * that only contain white space characters.
049: *
050:
051: *
052: * <p>The created <code>Node</code> will be pushed on Digester's object stack
053: * when done. To use it in the context of another DOM
054: * {@link org.w3c.dom.Document Document}, it must be imported first, using the
055: * Document method
056: * {@link org.w3c.dom.Document#importNode(org.w3c.dom.Node, boolean) importNode()}.
057: * </p>
058: *
059: * <p><strong>Important Note:</strong> This is implemented by replacing the SAX
060: * {@link org.xml.sax.ContentHandler ContentHandler} in the parser used by
061: * Digester, and resetting it when the matched element is closed. As a side
062: * effect, rules that would match XML nodes under the element that matches
063: * a <code>NodeCreateRule</code> will never be triggered by Digester, which
064: * usually is the behavior one would expect.</p>
065: *
066: * <p><strong>Note</strong> that the current implementation does not set the namespace prefixes
067: * in the exported nodes. The (usually more important) namespace URIs are set,
068: * of course.</p>
069: *
070: * @since Digester 1.4
071: */
072:
073: public class NodeCreateRule extends Rule {
074:
075: // ---------------------------------------------------------- Inner Classes
076:
077: /**
078: * The SAX content handler that does all the actual work of assembling the
079: * DOM node tree from the SAX events.
080: */
081: private class NodeBuilder extends DefaultHandler {
082:
083: // ------------------------------------------------------- Constructors
084:
085: /**
086: * Constructor.
087: *
088: * <p>Stores the content handler currently used by Digester so it can
089: * be reset when done, and initializes the DOM objects needed to
090: * build the node.</p>
091: *
092: * @param doc the document to use to create nodes
093: * @param root the root node
094: * @throws ParserConfigurationException if the DocumentBuilderFactory
095: * could not be instantiated
096: * @throws SAXException if the XMLReader could not be instantiated by
097: * Digester (should not happen)
098: */
099: public NodeBuilder(Document doc, Node root)
100: throws ParserConfigurationException, SAXException {
101:
102: this .doc = doc;
103: this .root = root;
104: this .top = root;
105:
106: oldContentHandler = digester.getCustomContentHandler();
107:
108: }
109:
110: // ------------------------------------------------- Instance Variables
111:
112: /**
113: * The content handler used by Digester before it was set to this
114: * content handler.
115: */
116: protected ContentHandler oldContentHandler = null;
117:
118: /**
119: * Depth of the current node, relative to the element where the content
120: * handler was put into action.
121: */
122: protected int depth = 0;
123:
124: /**
125: * A DOM Document used to create the various Node instances.
126: */
127: protected Document doc = null;
128:
129: /**
130: * The DOM node that will be pushed on Digester's stack.
131: */
132: protected Node root = null;
133:
134: /**
135: * The current top DOM mode.
136: */
137: protected Node top = null;
138:
139: // --------------------------------------------- ContentHandler Methods
140:
141: /**
142: * Appends a {@link org.w3c.dom.Text Text} node to the current node.
143: *
144: * @param ch the characters from the XML document
145: * @param start the start position in the array
146: * @param length the number of characters to read from the array
147: * @throws SAXException if the DOM implementation throws an exception
148: */
149: public void characters(char[] ch, int start, int length)
150: throws SAXException {
151:
152: try {
153: String str = new String(ch, start, length);
154: if (str.trim().length() > 0) {
155: top.appendChild(doc.createTextNode(str));
156: }
157: } catch (DOMException e) {
158: throw new SAXException(e.getMessage());
159: }
160:
161: }
162:
163: /**
164: * Checks whether control needs to be returned to Digester.
165: *
166: * @param namespaceURI the namespace URI
167: * @param localName the local name
168: * @param qName the qualified (prefixed) name
169: * @throws SAXException if the DOM implementation throws an exception
170: */
171: public void endElement(String namespaceURI, String localName,
172: String qName) throws SAXException {
173:
174: try {
175: if (depth == 0) {
176: getDigester().setCustomContentHandler(
177: oldContentHandler);
178: getDigester().push(root);
179: getDigester().endElement(namespaceURI, localName,
180: qName);
181: }
182:
183: top = top.getParentNode();
184: depth--;
185: } catch (DOMException e) {
186: throw new SAXException(e.getMessage());
187: }
188:
189: }
190:
191: /**
192: * Adds a new
193: * {@link org.w3c.dom.ProcessingInstruction ProcessingInstruction} to
194: * the current node.
195: *
196: * @param target the processing instruction target
197: * @param data the processing instruction data, or null if none was
198: * supplied
199: * @throws SAXException if the DOM implementation throws an exception
200: */
201: public void processingInstruction(String target, String data)
202: throws SAXException {
203:
204: try {
205: top.appendChild(doc.createProcessingInstruction(target,
206: data));
207: } catch (DOMException e) {
208: throw new SAXException(e.getMessage());
209: }
210:
211: }
212:
213: /**
214: * Adds a new child {@link org.w3c.dom.Element Element} to the current
215: * node.
216: *
217: * @param namespaceURI the namespace URI
218: * @param localName the local name
219: * @param qName the qualified (prefixed) name
220: * @param atts the list of attributes
221: * @throws SAXException if the DOM implementation throws an exception
222: */
223: public void startElement(String namespaceURI, String localName,
224: String qName, Attributes atts) throws SAXException {
225:
226: try {
227: Node previousTop = top;
228: if ((localName == null) || (localName.length() == 0)) {
229: top = doc.createElement(qName);
230: } else {
231: top = doc.createElementNS(namespaceURI, localName);
232: }
233: for (int i = 0; i < atts.getLength(); i++) {
234: Attr attr = null;
235: if ((atts.getLocalName(i) == null)
236: || (atts.getLocalName(i).length() == 0)) {
237: attr = doc.createAttribute(atts.getQName(i));
238: attr.setNodeValue(atts.getValue(i));
239: ((Element) top).setAttributeNode(attr);
240: } else {
241: attr = doc.createAttributeNS(atts.getURI(i),
242: atts.getLocalName(i));
243: attr.setNodeValue(atts.getValue(i));
244: ((Element) top).setAttributeNodeNS(attr);
245: }
246: }
247: previousTop.appendChild(top);
248: depth++;
249: } catch (DOMException e) {
250: throw new SAXException(e.getMessage());
251: }
252:
253: }
254:
255: }
256:
257: // ----------------------------------------------------------- Constructors
258:
259: /**
260: * Default constructor. Creates an instance of this rule that will create a
261: * DOM {@link org.w3c.dom.Element Element}.
262: */
263: public NodeCreateRule() throws ParserConfigurationException {
264:
265: this (Node.ELEMENT_NODE);
266:
267: }
268:
269: /**
270: * Constructor. Creates an instance of this rule that will create a DOM
271: * {@link org.w3c.dom.Element Element}, but lets you specify the JAXP
272: * <code>DocumentBuilder</code> that should be used when constructing the
273: * node tree.
274: *
275: * @param documentBuilder the JAXP <code>DocumentBuilder</code> to use
276: */
277: public NodeCreateRule(DocumentBuilder documentBuilder) {
278:
279: this (Node.ELEMENT_NODE, documentBuilder);
280:
281: }
282:
283: /**
284: * Constructor. Creates an instance of this rule that will create either a
285: * DOM {@link org.w3c.dom.Element Element} or a DOM
286: * {@link org.w3c.dom.DocumentFragment DocumentFragment}, depending on the
287: * value of the <code>nodeType</code> parameter.
288: *
289: * @param nodeType the type of node to create, which can be either
290: * {@link org.w3c.dom.Node#ELEMENT_NODE Node.ELEMENT_NODE} or
291: * {@link org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE Node.DOCUMENT_FRAGMENT_NODE}
292: * @throws IllegalArgumentException if the node type is not supported
293: */
294: public NodeCreateRule(int nodeType)
295: throws ParserConfigurationException {
296:
297: this (nodeType, DocumentBuilderFactory.newInstance()
298: .newDocumentBuilder());
299:
300: }
301:
302: /**
303: * Constructor. Creates an instance of this rule that will create either a
304: * DOM {@link org.w3c.dom.Element Element} or a DOM
305: * {@link org.w3c.dom.DocumentFragment DocumentFragment}, depending on the
306: * value of the <code>nodeType</code> parameter. This constructor lets you
307: * specify the JAXP <code>DocumentBuilder</code> that should be used when
308: * constructing the node tree.
309: *
310: * @param nodeType the type of node to create, which can be either
311: * {@link org.w3c.dom.Node#ELEMENT_NODE Node.ELEMENT_NODE} or
312: * {@link org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE Node.DOCUMENT_FRAGMENT_NODE}
313: * @param documentBuilder the JAXP <code>DocumentBuilder</code> to use
314: * @throws IllegalArgumentException if the node type is not supported
315: */
316: public NodeCreateRule(int nodeType, DocumentBuilder documentBuilder) {
317:
318: if (!((nodeType == Node.DOCUMENT_FRAGMENT_NODE) || (nodeType == Node.ELEMENT_NODE))) {
319: throw new IllegalArgumentException(
320: "Can only create nodes of type DocumentFragment and Element");
321: }
322: this .nodeType = nodeType;
323: this .documentBuilder = documentBuilder;
324:
325: }
326:
327: // ----------------------------------------------------- Instance Variables
328:
329: /**
330: * The JAXP <code>DocumentBuilder</code> to use.
331: */
332: private DocumentBuilder documentBuilder = null;
333:
334: /**
335: * The type of the node that should be created. Must be one of the
336: * constants defined in {@link org.w3c.dom.Node Node}, but currently only
337: * {@link org.w3c.dom.Node#ELEMENT_NODE Node.ELEMENT_NODE} and
338: * {@link org.w3c.dom.Node#DOCUMENT_FRAGMENT_NODE Node.DOCUMENT_FRAGMENT_NODE}
339: * are allowed values.
340: */
341: private int nodeType = Node.ELEMENT_NODE;
342:
343: // ----------------------------------------------------------- Rule Methods
344:
345: /**
346: * When this method fires, the digester is told to forward all SAX
347: * ContentHandler events to the builder object, resulting in a DOM being
348: * built instead of normal digester rule-handling occurring. When the
349: * end of the current xml element is encountered, the original content
350: * handler is restored (expected to be NULL, allowing normal Digester
351: * operations to continue).
352: *
353: * @param namespaceURI the namespace URI of the matching element, or an
354: * empty string if the parser is not namespace aware or the element has
355: * no namespace
356: * @param name the local name if the parser is namespace aware, or just
357: * the element name otherwise
358: * @param attributes The attribute list of this element
359: * @throws Exception indicates a JAXP configuration problem
360: */
361: public void begin(String namespaceURI, String name,
362: Attributes attributes) throws Exception {
363:
364: Document doc = documentBuilder.newDocument();
365: NodeBuilder builder = null;
366: if (nodeType == Node.ELEMENT_NODE) {
367: Element element = null;
368: if (getDigester().getNamespaceAware()) {
369: element = doc.createElementNS(namespaceURI, name);
370: for (int i = 0; i < attributes.getLength(); i++) {
371: element.setAttributeNS(attributes.getURI(i),
372: attributes.getQName(i), attributes
373: .getValue(i));
374: }
375: } else {
376: element = doc.createElement(name);
377: for (int i = 0; i < attributes.getLength(); i++) {
378: element.setAttribute(attributes.getQName(i),
379: attributes.getValue(i));
380: }
381: }
382: builder = new NodeBuilder(doc, element);
383: } else {
384: builder = new NodeBuilder(doc, doc.createDocumentFragment());
385: }
386: // the NodeBuilder constructor has already saved the original
387: // value of the digester's custom content handler (expected to
388: // be null, but we save it just in case). So now we just
389: // need to tell the digester to forward events to the builder.
390: getDigester().setCustomContentHandler(builder);
391: }
392:
393: /**
394: * Pop the Node off the top of the stack.
395: */
396: public void end() throws Exception {
397:
398: digester.pop();
399:
400: }
401:
402: }
|