1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 }
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
89 List nodes = XPathUtil.selectNodes(xml,xpath);
90
91
92 Object beansValue = PropertyUtils.getProperty(bean, prop);
93
94
95 if ( beansValue != null && Collection.class.isInstance(beansValue)) {
96 beansValue = ((Collection)beansValue).toArray();
97
98 }
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 }
108
109 } else {
110 Iterator i = nodes.iterator();
111 while ( i.hasNext()) {
112 Node n = (Node)i.next();
113 setValue(n, beansValue);
114 }
115 }
116 } catch ( Exception ex) {
117 throw new XmlException("Could not set Xml by xpath "+xpath + " with propery " + prop + " from bean " + bean, ex);
118 }
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 }
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 }
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 }
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 }
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 }
238 else if ( node.getNodeType() == Node.ELEMENT_NODE) {
239
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 }
249 }
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
265 if ( node == null) {
266 return;
267 }
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
278
279
280
281 if ( (containsElementChildren( node ) ||
282 !hasChildNodes(node)) &&
283 value instanceof String
284 ) {
285
286 try {
287
288
289
290
291
292 DocumentFragment frag = DocumentUtil.getDocumentFragment((String)value,
293 true);
294 ElementUtil.setElement((Element)node, frag );
295 return;
296 } catch (XmlException ex) {
297
298 }
299
300 }
301
302 ElementUtil.setContent((Element)node,ConvertUtils.convert(value));
303 } else {
304
305 throw new XmlException("Node of type " + node.getNodeType() + " not possible to set with value " + value);
306
307 }
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
331 } else if ( n instanceof Comment){
332
333 } else {
334
335 return true;
336 }
337
338 }
339
340 return false;
341 }
342 public static void main(String[] args){
343
344
345 }
346 }