View Javadoc

1   /*
2    * Copyright 2002,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jelly.tags.xml;
17  
18  import org.apache.commons.jelly.JellyTagException;
19  import org.apache.commons.jelly.MissingAttributeException;
20  import org.apache.commons.jelly.XMLOutput;
21  import org.apache.commons.jelly.xpath.XPathComparator;
22  import org.apache.commons.jelly.xpath.XPathTagSupport;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  import org.dom4j.Node;
28  
29  import org.jaxen.XPath;
30  import org.jaxen.JaxenException;
31  
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.ListIterator;
37  
38  /*** A tag which defines a variable from an XPath expression.
39    * This function creates a variable of type {@link List} or {@link org.dom4j.Node}
40    * (for example {@link org.dom4j.Element} or {@link org.dom4j.Attribute}).
41    * Thus, the variable created from xml:set can be
42    * used from the other xml library functions.
43    *
44    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
45    * @version $Revision: 155420 $
46    */
47  public class SetTag extends XPathTagSupport {
48  
49      private static final int RETURN_NODE_LIST = 0;
50      private static final int RETURN_FIRST_NODE = 1;
51      private static final int RETURN_STRING_LIST = 2;
52      private static final int RETURN_DELIMITED_STRING_LIST = 3;
53      private static final int RETURN_FIRST_AS_STRING = 4;
54      
55      /*** The Log to which logging calls will be made. */
56      private Log log = LogFactory.getLog(SetTag.class);
57  
58      /*** The variable name to export. */
59      private String var;
60  
61      /*** The XPath expression to evaluate. */
62      private XPath select;
63  
64      /*** Xpath comparator for sorting */
65      private XPathComparator xpCmp = null;
66  
67      private Boolean single = null;
68      
69      private Boolean asString = null;
70      
71      private String delimiter = null;
72  
73      private String delim = null;
74  
75      public SetTag() {
76  
77      }
78  
79      // Tag interface
80      //-------------------------------------------------------------------------
81      public void doTag(XMLOutput output) throws MissingAttributeException, JellyTagException {
82          if (var == null) {
83              throw new MissingAttributeException( "var" );
84          }
85          if (select == null) {
86              throw new MissingAttributeException( "select" );
87          }
88  
89          Object xpathContext = getXPathContext();
90          Object value = null;
91          try {
92              if( single != null && single.booleanValue() == true ) {
93                  value = select.selectSingleNode(xpathContext);
94              } else {
95                  value = select.evaluate(xpathContext);
96              }
97          }
98          catch (JaxenException e) {
99              throw new JellyTagException(e);
100         }
101         
102         if (value instanceof List) {
103             List list = (List) value;
104             // sort the list if xpCmp is set.
105             if (xpCmp != null && (xpCmp.getXpath() != null)) {
106                 Collections.sort(list, xpCmp);
107             }
108             if(list.isEmpty()) {
109                 value = null;
110             }
111         }
112         
113 
114         // handle single
115         if (single!=null) {
116             if (single.booleanValue() == true) {
117                 if(value instanceof List) {
118                     List l = (List) value;
119                     if (l.size() == 0)
120                         value=null;
121                     else
122                         value=l.get(0);
123                 }
124             } else { // single == false
125                 if(! (value instanceof List) ) {
126                     List l = null;
127                     if (value==null) {
128                         l = new ArrayList(0);
129                     } else {
130                         l = new ArrayList(1);
131                         l.add(value);
132                     }
133                     value = l;
134                 }
135             }
136         }
137         
138         // now convert the result(s) to string if need
139         if(asString != null && asString.booleanValue()) {
140             if(value instanceof Node) {
141                 value = ((Node) value).getStringValue();
142             } else if(value instanceof List) {
143                 for(ListIterator it = ((List) value).listIterator(); it.hasNext(); ) {
144                     Object v = it.next();
145                     if(v instanceof Node) {
146                         v = ((Node) v).getStringValue();
147                         it.set(v);
148                     }
149                 }
150             }
151         }
152         
153         // finally convert the result to a concatenated string if delimiter is defined
154         if(delimiter != null && value instanceof List) {
155             StringBuffer buff = new StringBuffer();
156             for(Iterator it = ((List) value).iterator(); it.hasNext(); ) {
157                 Object v = it.next();
158                 if (v instanceof Node) {
159                     buff.append( ((Node) v).getStringValue());
160                 } else {
161                     buff.append(v.toString());
162                 }
163                 if(it.hasNext()) {
164                     buff.append(delimiter);
165                 }
166             }
167             buff.setLength(buff.length());
168             value = buff.toString();
169         }
170         
171 
172         //log.info( "Evaluated xpath: " + select + " as: " + value + " of type: " + value.getClass().getName() );
173 
174         context.setVariable(var, value);
175     }
176 
177     private List valueAsList( final Object value ) {
178         if (value instanceof List) {
179             return (List)value;
180         } else {
181             if (value == null) {
182                 return Collections.EMPTY_LIST;
183             } else {
184                 return Collections.singletonList(value);
185             }
186         }
187     }
188 
189     private Object valueAsSingle( final Object value ) {
190         if (value instanceof List) {
191             List l = (List) value;
192             if (l.isEmpty())
193                 return null;
194             else
195                 return l.get(0);
196         } else {
197             return value;
198         }
199     }
200     
201     private String singleValueAsString( final Object value ) {
202         if (value instanceof Node) {
203             return ((Node) value).getStringValue();
204         } else {
205             return null;
206         }
207     }
208 
209     private List nodeListToStringList( final List values ) {
210         List l = new ArrayList(values.size());
211         for (Iterator it = values.iterator(); it.hasNext(); ) {
212             Object v = it.next();
213             String s = singleValueAsString(v);
214             if (s != null) {
215                 l.add(s);
216             }
217         }
218         return l;
219     }
220 
221     private String joinDelimitedElements( final List values ) {
222         StringBuffer sb = new StringBuffer();
223         int sz = values.size();
224         for (int i = 0; i < sz; i++) {
225             String s = (String)values.get(i); 
226             sb.append(s);
227             if (i < sz - 1)
228                 sb.append(delim);
229         }
230         return sb.toString();
231     }
232 
233     private int determineReturnType() {
234         int resultType;
235         if (single != null && single.booleanValue()) { // first node
236             if (asString != null && asString.booleanValue()) {
237                 resultType = RETURN_FIRST_AS_STRING;
238             } else {
239                 resultType = RETURN_FIRST_NODE;
240             }
241         } else { // all nodes
242             if (asString != null && asString.booleanValue()) {
243                 if (delim != null) {
244                     resultType = RETURN_DELIMITED_STRING_LIST;
245                 } else {
246                     resultType = RETURN_STRING_LIST;
247                 }
248             } else {
249                 resultType = RETURN_NODE_LIST;
250             }
251         }
252         return resultType;
253     }
254 
255     // Properties
256     //-------------------------------------------------------------------------
257 
258     /*** Sets the variable name to define for this expression
259      */
260     public void setVar(String var) {
261         this.var = var;
262     }
263 
264     /*** Sets the XPath expression to evaluate. */
265     public void setSelect(XPath select) {
266         this.select = select;
267     }
268 
269     /*** If set to true will only take the first element matching.
270         It then guarantees that the result is of type
271         {@link org.dom4j.Node} thereby making sure that, for example,
272         when an element is selected, one can directly call such methods
273         as setAttribute.<br/>
274         If set to false, guarantees that a list is returned.
275         If set to false, guarantees that a list is returned.
276         */
277     public void setSingle(boolean single) {
278         this.single = new Boolean(single);
279     }
280     
281     /*** If set to true, will ensure that the (XPath) text-value
282       * of the selected node is taken instead of the node
283       * itself.
284       * This ensures that, thereafter, string manipulations
285       * can be performed on the result.
286       */
287     public void setAsString(boolean asString) {
288         this.asString = new Boolean(asString);
289     }
290 
291     /*** If set, returns a string delimited by this delimiter.
292       * Implies <code>asString</code> to be true.
293       */
294     public void setDelim(String delim) {
295         this.delimiter = delim;
296         if( delim!=null ) {
297             this.asString = Boolean.TRUE;
298         }
299     }
300 
301     /*** Sets the xpath expression to use to sort selected nodes.
302      *  Ignored if single is true.
303      */
304     public void setSort(XPath sortXPath) throws JaxenException {
305         if (xpCmp == null) xpCmp = new XPathComparator();
306         xpCmp.setXpath(sortXPath);
307     }
308 
309     /***
310      * Set whether to sort ascending or descending.
311      */
312     public void setDescending(boolean descending) {
313         if (xpCmp == null) xpCmp = new XPathComparator();
314         xpCmp.setDescending(descending);
315     }
316 }