View Javadoc

1   /*
2    * Copyright (c) 2002 Peter Antman, Teknik i Media  <peter.antman@tim.se>
3    *
4    * $Id: XmlBeanUtil.java,v 1.1.1.1 2004/05/19 12:07:31 pra Exp $
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2 of the License, or (at your option) any later version
10   * 
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  package org.backsource.utils.xml;
21  
22  import java.util.Collection;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Iterator;
26  import java.util.HashMap;
27  
28  import org.w3c.dom.Node;
29  import org.w3c.dom.Text;
30  import org.w3c.dom.Element;
31  import org.w3c.dom.Attr;
32  import org.w3c.dom.DocumentFragment;
33  import org.w3c.dom.Comment;
34  import org.w3c.dom.NodeList;
35  
36  import org.apache.commons.beanutils.BeanUtils;
37  import org.apache.commons.beanutils.PropertyUtils;
38  import org.apache.commons.beanutils.ConvertUtils;
39  /***
40   * <p>XPath based property setter util. Used in three situations:
41   </p>
42   * 
43   * <ul class="noindent">
44   * <li>1. to get a particular value from a Node by Xpath</li>
45   * 
46   * <li>2. to set a particular vaue in a Node by Xpath, where the value
47   * may be a simple object or a JavaBean with property specifyed.</li>
48   * 
49   * <li>3. to set properties in a bean, by getting them from a Node
50   * with XPath.</li>
51   * </ul>
52   * 
53   * <p>It is loosely based on the apache BeansUtils, which it also uses
54   * under the hood.</p>
55   *
56   * @author <a href="mailto:pra@tim.se">Peter Antman</a>
57   * @version $Revision: 1.1.1.1 $
58   */
59  
60  public class XmlBeanUtil{
61     
62     /***
63      * Set value in node based on xpath. What will be set i dependant on both what the xpath retrurns and what type of object value is.
64      *
65      * @param xml the xml node to set a (Node) value on.
66      * @param xpath xpath pointing to a node where value should be inserted. if pointing to more than one node, the value will be inserted in all places.
67      * @param value the value to be inserted, if of Node type inserted as node, if of text type will be inserted as text, if the returned not only contains text, otherwise it will be converted to a DocumentFragment a inserted AFTER the hitted node. 
68      * 
69      */
70     public static void setXmlProperty(Node xml, String xpath, Object value) throws XmlException {
71        List nodes = XPathUtil.selectNodes(xml,xpath);
72        Iterator i = nodes.iterator();
73        while ( i.hasNext()) {
74           Node n = (Node)i.next();
75           setValue(n, value);
76        } // end of while ()      
77     }
78     
79     /***
80      * <p>Set value in node base on xpath and derived from bean with property prop.
81      *
82      * <p>if the xpath returns multiple nodes and prop point to an array or collection, set the value of each node by traversing the collection.
83      */
84     public static void setXmlProperty(Node xml, String xpath, Object bean, String prop) throws XmlException 
85     {
86        try {
87            
88        // Get nodes
89        List nodes = XPathUtil.selectNodes(xml,xpath);
90        
91        // Get value from bean
92        Object beansValue = PropertyUtils.getProperty(bean, prop);
93  
94        // Check if collection
95        if ( beansValue != null && Collection.class.isInstance(beansValue)) {
96           beansValue = ((Collection)beansValue).toArray();
97           
98        } // end of if ()
99  
100       if ( beansValue != null && beansValue.getClass().isArray()) {
101          Object[] bv = (Object[])beansValue;
102          int length = bv.length;
103          Iterator ns = nodes.iterator();
104          for ( int i = 0; i < length && ns.hasNext();i++) {
105             Node n = (Node)ns.next();
106             setValue(n,bv[i]);
107          } // end of for ()
108          
109       } else {
110          Iterator i = nodes.iterator();
111          while ( i.hasNext()) {
112             Node n = (Node)i.next();
113             setValue(n, beansValue);
114          } // end of while ()  
115       } // end of else
116       } catch ( Exception ex) {
117          throw new XmlException("Could not set Xml by xpath "+xpath + " with propery " + prop + " from bean " + bean, ex);
118       } // end of try-catch
119       
120    }
121    
122   
123 
124    /***
125     * <p>Set the property in the given bean, getting the value from the Node specifyed with xpath.
126     *
127     * <p>if the property in the bean is of the same Node type as that one returned by the xpath the result will be cloned and set as is.
128     *
129     * <p>This will for now only ever get one Node and set the prop with.
130     *
131     * <p>What xpath returns is the same as in setXmlProperty. The same type of concerion that normally occurs in apache BeanUtils will also take place.
132     * @param bean the JavaBean to populate
133     * @param prop the string property expression, following the style in apache BeanUtils.
134     * @param xml the node to query for a value.
135     * @param xpath the xpath expression used to query the node.
136     * @throws XmlException if nothing was found with xpath.
137     */
138    
139    public static void setBeanProperty(Object bean, String prop, Node xml, String xpath) throws XmlException{
140       try {      
141       Node n = XPathUtil.selectSingleNode(xml, xpath);
142 
143       if ( n == null) {
144          throw new XmlException("Nothing found with xpath: " + xpath);
145       } // end of if ()
146       
147 
148       Class propType = PropertyUtils.getPropertyType(bean, prop);
149       Object value;
150       if ( Node.class.isAssignableFrom(propType)) {
151          value = n.cloneNode(true);
152       } else {
153          
154          value = getValue(n);
155       } // end of else
156       Map map = new HashMap();
157       map.put(prop,value);
158       BeanUtils.populate(bean,map);
159                 
160       } catch (Exception ex) {
161          throw new XmlException("Could not set bean property " + prop + " on bean " + bean + " with xpath " + xpath,ex);
162       } // end of try-catch
163    }
164 
165    
166 
167    /***
168     * <p>Populate the Node with xpath/object mappings given in map.
169     *
170     * <p>Same as setXmlProperty except that mutiple xpath/value mapping may be given in map.
171     */
172    public static void populateXml(Node xml, Map map) {
173       throw new Error("NYI");
174    }
175    
176 
177    /***
178     * <p>Populate the given bean, with values from node gotten through the property/xpath mapping given in map.
179     *
180     * <p>Same as setBeanPropert except that multiple property/xpath mappings may be given. 
181     */
182    public static void populateBean(Object bean, Node xml, Map map) {
183       throw new Error("NYI");
184    }
185 
186    /***
187     * <p>Get the value from node the xpath points at.
188     *
189     * <p>if the Node returned by the xpath is an Element node with only text content, that content will be returned.
190     * <p> if the Node is a Text node (and CDataSection) the text will be returned.
191     * <p>if the Node is an Attribute the value of the attribute will be returned.
192     * <p> if none of the above does apply, the serialized XML form of the node will be returned.
193     * <p>This one will allways only select a single Node even if the xpath might
194     * hit more than one node.
195     *
196     * @returns the String value of the hit node, or null if empty.
197     */
198    public static String getXmlProperty(Node xml, String xpath) throws XmlException {
199       Node node = XPathUtil.selectSingleNode(xml, xpath);
200       return getValue(node);
201    }
202 
203    /***
204     * <p>Get the values from node the xpath points at.
205     *
206     * <p> The differense compared to {@link #getXmlProperty(Node, String)}is that this allways selects as if  the xpath may return more than one node.
207     *
208     * @returns String array wich is never null, but may be empty.
209     */
210    public static String[] getXmlArrayProperty(Node xml, String xpath) throws XmlException {
211       List nodes = XPathUtil.selectNodes(xml, xpath);
212       String[] values = new String[ nodes.size() ];
213       Iterator i = nodes.iterator();
214       for ( int j = 0;i.hasNext();j++ ) {
215          Node n = (Node)i.next();
216          values[j] = getValue( n );
217       } // end of for ()
218 
219       return values;
220       
221    }
222 
223    /***
224     * <p>Get the String value from node
225     * <p>if the Node  is an Element node with only text content, that content will be returned.
226     * <p> if the Node is a Text node (and CDataSection) the text will be returned.
227     * <p>if the Node is an Attribute the value of the attribute will be returned.
228     * <p> if none of the above does apply, the serialized XML form of the node will be returned, without a prolog.
229     * @param node a node to get the string value from.
230     * @return the String form of the value, or null if node is null.
231     */
232    public static String getValue(Node node) throws XmlException{
233       String value = null;
234       if ( node != null) {
235          if ( node.getNodeType() == Node.ATTRIBUTE_NODE) {
236             value = node.getNodeValue();
237          } // end of if ()
238          else  if ( node.getNodeType() == Node.ELEMENT_NODE) {
239             // Either it contains element childre, we serialize
240             if ( !containsElementChildren( node ))
241                value = ElementUtil.getContent((Element)node);
242             else  
243                value =  DocumentUtil.getXml(node,true,null,true);
244          } else if (node instanceof Text ) {
245             value = node.getNodeValue();
246          }else {
247             value =  DocumentUtil.getXml(node,true,null,true);
248          } // end of else
249       } // end of if ()
250       return value;
251    }
252    
253    /***
254     * <p>Set the value of node with object.
255     *
256     * <p>if value is of Node type replace node. if the node is a DocumentFragment this will insert all the children of the document fragment.
257     * <p>if Node is of text, set value from value, converted to String.
258     * <p>if Node is of element, set content from value. if node only contains text, set text -owerwriting any prevoius content, if elemnt contains children, or is empty (skipping any comments and whitespace) check if value is possible to convert to a DocumentFragmen where all children are elements and insert , otherwise insert as text. 
259     *
260     * @param value the value to be inserted, if of Node type inserted as node, if of text type will be inserted as text, if the returned not only contains text, otherwise it will be converted to a DocumentFragment a inserted AFTER the hitted node. 
261     * 
262     */
263    public static void setValue(Node node, Object value) throws XmlException {
264       // Assert, node may to be null.
265       if ( node == null) {
266          return;
267       } // end of if ()
268       
269 
270 
271       if ( value instanceof Node) {
272          DocumentUtil.replace(node, (Node)value);       
273       } else if ( node instanceof Text || node instanceof Attr) {
274          node.setNodeValue( ConvertUtils.convert(value) );
275       } else if ( node instanceof Element) {
276          //
277          // Check if we might have an XML fragment to insert. We only do
278          // this if element contains any element children or is empty and
279          // value really is a String
280          //
281          if ( (containsElementChildren( node ) || 
282                !hasChildNodes(node)) &&
283               value instanceof String
284               ) {
285             // See if value is a string and is possible to convert to XML
286             try {
287                /*
288                Element tmp;
289                tmp = DocumentUtil.getDocumentElement( (String)value);
290                ElementUtil.setElement((Element)node, tmp );
291                */
292                DocumentFragment frag = DocumentUtil.getDocumentFragment((String)value,
293                                                                         true);
294                ElementUtil.setElement((Element)node, frag );
295                return; //We are happy
296             } catch (XmlException ex) {
297                // NOOP, we continue
298             } // end of try-catch
299             
300          } // end of if ()
301          
302          ElementUtil.setContent((Element)node,ConvertUtils.convert(value));
303       } else {
304          // Node is of type we can not handle
305          throw new XmlException("Node of type " + node.getNodeType() + " not possible to set with value " + value);
306 
307       } // end of else
308       
309    }
310    
311    /***
312     * Ckeck if node contains any Element children.
313     * @return true if the node contains Element children.
314     */
315    public static boolean containsElementChildren(Node node) throws XmlException{
316       List nodes = ElementUtil.getElements((Element)node);
317       return nodes.size() > 0;
318 
319    } 
320 
321    /***
322     * Check if node contains any child nodes other that whitespace text and comments.
323     */
324    public static boolean hasChildNodes(Node node) {
325       NodeList list = node.getChildNodes();
326       int l = list.getLength();
327       for ( int i=0;i<l;i++) {
328          Node n = list.item(i);
329          if ( n instanceof Text && "".equals(n.getNodeValue().trim())) {
330             //Pass
331          } else if ( n instanceof Comment){
332             //Pass
333          } else {
334             // Somethings in there
335             return true;
336          } // end of else
337  
338       } // end of for ()
339       // if we are here, we do not find anything of interest
340       return false;
341    }
342    public static void main(String[] args){
343       
344 
345    }
346 } // XmlBeanUtil