KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > gargoylesoftware > htmlunit > javascript > ElementArray


1 /*
2  * Copyright (c) 2002, 2005 Gargoyle Software Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12  * 3. The end-user documentation included with the redistribution, if any, must
13  * include the following acknowledgment:
14  *
15  * "This product includes software developed by Gargoyle Software Inc.
16  * (http://www.GargoyleSoftware.com/)."
17  *
18  * Alternately, this acknowledgment may appear in the software itself, if
19  * and wherever such third-party acknowledgments normally appear.
20  * 4. The name "Gargoyle Software" must not be used to endorse or promote
21  * products derived from this software without prior written permission.
22  * For written permission, please contact info@GargoyleSoftware.com.
23  * 5. Products derived from this software may not be called "HtmlUnit", nor may
24  * "HtmlUnit" appear in their name, without prior written permission of
25  * Gargoyle Software Inc.
26  *
27  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30  * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  */

38 package com.gargoylesoftware.htmlunit.javascript;
39
40 import java.util.ArrayList JavaDoc;
41 import java.util.Iterator JavaDoc;
42 import java.util.List JavaDoc;
43
44 import org.apache.commons.collections.CollectionUtils;
45 import org.apache.commons.collections.Transformer;
46 import org.apache.commons.collections.functors.NOPTransformer;
47 import org.jaxen.JaxenException;
48 import org.jaxen.XPath;
49 import org.mozilla.javascript.Context;
50 import org.mozilla.javascript.Function;
51 import org.mozilla.javascript.JavaScriptException;
52 import org.mozilla.javascript.Scriptable;
53 import org.saxpath.SAXPathException;
54
55 import com.gargoylesoftware.htmlunit.WebWindow;
56 import com.gargoylesoftware.htmlunit.html.DomNode;
57 import com.gargoylesoftware.htmlunit.html.HtmlElement;
58 import com.gargoylesoftware.htmlunit.html.Util;
59
60 /**
61  * An array of elements. Used for the element arrays returned by <tt>document.all</tt>,
62  * <tt>document.all.tags('x')</tt>, <tt>document.forms</tt>, <tt>window.frames</tt>, etc.
63  * Note that this class must not be used for collections that can be modified, for example
64  * <tt>map.areas</tt> and <tt>select.options</tt>.
65  * <br>
66  * This class (like all classes in this package) is specific for the javascript engine.
67  * Users of HtmlUnit shouldn't use it directly.
68  * @version $Revision: 100 $
69  * @author Daniel Gredler
70  * @author Marc Guillemot
71  * @author Chris Erskine
72  */

73 public class ElementArray extends SimpleScriptable implements Function {
74     /**
75      * The name of the js object corresponding to this class as registered by the javascript context
76      */

77     public static final String JavaDoc JS_OBJECT_NAME = "ElementArray";
78     private static final long serialVersionUID = 4049916048017011764L;
79     
80     private XPath xpath_;
81     private DomNode node_;
82     /**
83      * The transformer used to get the element to return from the html element.
84      * It returns the html element itself except for frames where it returns the nested window.
85      */

86     private Transformer transformer_;
87
88     /**
89      * Create an instance. Javascript objects must have a default constructor.
90      */

91     public ElementArray() {
92         
93     }
94
95     /**
96      * Init the content of this collection. The elements will be "calculated" at each
97      * access using the xpath applied on the node.
98      * @param node the node to serve as root for the xpath expression
99      * @param xpath the xpath giving the elements of the collection
100      */

101     public void init(final DomNode node, final XPath xpath) {
102         init(node, xpath, NOPTransformer.INSTANCE);
103         
104     }
105
106     /**
107      * Init the content of this collection. The elements will be "calculated" at each
108      * access using the xpath applied on the node and transformed using the transformer.
109      * @param node the node to serve as root for the xpath expression
110      * @param xpath the xpath giving the elements of the collection
111      * @param transformer the transformer allowing to get the expected objects from the xpath
112      * evaluation
113      */

114     public void init(final DomNode node, final XPath xpath, final Transformer transformer) {
115         node_ = node;
116         xpath_ = xpath;
117         transformer_ = transformer;
118     }
119
120     /**
121      * Javascript constructor. This must be declared in every javascript file because
122      * the rhino engine won't walk up the hierarchy looking for constructors.
123      */

124     public final void jsConstructor() {
125         // Empty.
126
}
127
128     /**
129      * @see Function#call(Context, Scriptable, Scriptable, Object[])
130      */

131     public final Object JavaDoc call(
132             final Context context, final Scriptable scope,
133             final Scriptable thisObj, final Object JavaDoc[] args)
134         throws JavaScriptException {
135         if( args.length == 0 ) {
136             throw Context.reportRuntimeError( "Zero arguments; need an index or a key." );
137         }
138         return get( args[0] );
139     }
140
141     /**
142      * @see Function#construct(Context, Scriptable, Object[])
143      */

144     public final Scriptable construct(
145             final Context arg0, final Scriptable arg1, final Object JavaDoc[] arg2)
146         throws JavaScriptException {
147         return null;
148     }
149
150     /**
151      * Private helper that retrieves the item or items corresponding to the specified
152      * index or key.
153      * @param o The index or key corresponding to the element or elements to return.
154      * @return The element or elements corresponding to the specified index or key.
155      */

156     private Object JavaDoc get( final Object JavaDoc o ) {
157         if( o instanceof Number JavaDoc ) {
158             final Number JavaDoc n = (Number JavaDoc) o;
159             final int i = n.intValue();
160             return get( i, this );
161         }
162         else {
163             final String JavaDoc key = String.valueOf( o );
164             return get( key, this );
165         }
166     }
167
168     /**
169      * Returns the element at the specified index, or <tt>NOT_FOUND</tt> if the
170      * index is invalid.
171      * @see org.mozilla.javascript.ScriptableObject#get(int, Scriptable)
172      */

173     public final Object JavaDoc get( final int index, final Scriptable start ) {
174         final ElementArray array = (ElementArray) start;
175         final List JavaDoc elements = array.getElementsSorted();
176         
177         if( index >= 0 && index < elements.size()) {
178             return getScriptableFor(elements.get(index));
179         }
180         else {
181             return NOT_FOUND;
182         }
183     }
184
185     /**
186      * Due to bug in Jaxen: http://jira.codehaus.org/browse/JAXEN-55
187      * the nodes returned by the xpath evaluation are not correctly sorted.
188      * We have therefore to sort them.
189      * @return the sorted list.
190      */

191     private List JavaDoc getElementsSorted() {
192         final List JavaDoc nodes = getElements();
193         final List JavaDoc sortedNodes;
194         if (nodes.size() > 1) {
195             sortedNodes = new ArrayList JavaDoc();
196             for (final Iterator JavaDoc iter = Util.getFollowingAxisIterator(node_); iter.hasNext();) {
197                 final Object JavaDoc node = iter.next();
198                 if (nodes.contains(node)) {
199                     sortedNodes.add(node);
200                     nodes.remove(node);
201                     if (nodes.isEmpty()) {
202                         break; // nothing to sort anymore
203
}
204                 }
205             }
206         }
207         else {
208             sortedNodes = nodes; // already "sorted"
209
}
210         
211         CollectionUtils.transform(sortedNodes, transformer_);
212         return sortedNodes;
213     }
214
215     /**
216      * Gets the html elements. Avoid calling it multiple times within a method because the evaluation
217      * needs to be performed each time again
218      * @return the list of {@link HtmlElement} contained in this collection
219      */

220     private List JavaDoc getElements() {
221         try {
222             final List JavaDoc list = xpath_.selectNodes(node_);
223             return list;
224         }
225         catch (final JaxenException e) {
226             throw Context.reportRuntimeError("Exeption getting elements: " + e.getMessage());
227         }
228     }
229
230     /**
231      * Returns the element or elements that match the specified key. If it is the name
232      * of a property, the property value is returned. If it is the id of an element in
233      * the array, that element is returned. Finally, if it is the name of an element or
234      * elements in the array, then all those elements are returned. Otherwise,
235      * {@link #NOT_FOUND} is returned.
236      * @see org.mozilla.javascript.ScriptableObject#get(String, Scriptable)
237      */

238     public final Object JavaDoc get( final String JavaDoc name, final Scriptable start ) {
239         // If the name of a property was specified, return the property value.
240
final Object JavaDoc result = super.get(name, start);
241         if( result != NOT_FOUND ) {
242             return result;
243         }
244         
245         final ElementArray currentArray = ((ElementArray) start);
246         final List JavaDoc elements = currentArray.getElements();
247         CollectionUtils.transform(elements, transformer_);
248
249         // See if there is an element in the element array with the specified id.
250
for (final Iterator JavaDoc iter = elements.iterator(); iter.hasNext();) {
251             final Object JavaDoc next = iter.next();
252             if (next instanceof HtmlElement) {
253                 final HtmlElement element = (HtmlElement) next;
254                 final String JavaDoc id = element.getId();
255                 if( id != null && id.equals(name) ) {
256                     getLog().debug("Property \"" + name + "\" evaluated (by id) to " + element);
257                     return getScriptableFor( element );
258                 }
259             }
260             else if (next instanceof WebWindow) {
261                 final WebWindow window = (WebWindow) next;
262                 final String JavaDoc windowName = window.getName();
263                 if (windowName != null && windowName.equals(name)) {
264                     getLog().debug("Property \"" + name + "\" evaluated (by name) to " + window);
265                     return getScriptableFor( window );
266                 }
267             }
268             else {
269                 getLog().debug("Unrecognized type in array: \"" + next.getClass().getName() + "\"");
270             }
271         }
272
273         // See if there are any elements in the element array with the specified name.
274
final ElementArray array = (ElementArray) currentArray.makeJavaScriptObject(JS_OBJECT_NAME);
275         try {
276             final String JavaDoc newCondition = "@name = '" + name + "'";
277             final String JavaDoc currentXPathExpr = currentArray.xpath_.toString();
278             final String JavaDoc xpathExpr;
279             if (currentXPathExpr.endsWith("]")) {
280                 xpathExpr = currentXPathExpr.substring(0, currentXPathExpr.length()-1) + " and " + newCondition + "]";
281             }
282             else {
283                 xpathExpr = currentXPathExpr + "[" + newCondition + "]";
284             }
285             final XPath xpathName = currentArray.xpath_.getNavigator().parseXPath(xpathExpr);
286             array.init(currentArray.node_, xpathName);
287         }
288         catch (final SAXPathException e) {
289             throw Context.reportRuntimeError("Failed getting sub elements by name" + e.getMessage());
290         }
291         
292         // Test to see if we are trying to get the length of this array? If so, pass the processing up
293
// to the higher level processing
294
if ("length".equals(name)) {
295             return NOT_FOUND;
296         }
297         
298         final List JavaDoc subElements = array.getElements();
299         if (subElements.size() > 1) {
300             getLog().debug("Property \"" + name + "\" evaluated (by name) to " + array + " with "
301                     + subElements.size() + " elements");
302             return array;
303         }
304         else if (subElements.size() == 1) {
305             final SimpleScriptable singleResult = getScriptableFor(subElements.get(0));
306             getLog().debug("Property \"" + name + "\" evaluated (by name) to " + singleResult);
307             return singleResult;
308         }
309
310         // Nothing was found.
311
return NOT_FOUND;
312     }
313
314     /**
315      * Returns the length of this element array.
316      * @return The length of this element array.
317      * @see <a HREF="http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/length.asp">MSDN doc</a>
318      */

319     public final int jsGet_length() {
320         return getElements().size();
321     }
322
323     /**
324      * Retrieves the item or items corresponding to the specified index or key.
325      * @param index The index or key corresponding to the element or elements to return.
326      * @return The element or elements corresponding to the specified index or key.
327      * @see <a HREF="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/item.asp">MSDN doc</a>
328      */

329     public final Object JavaDoc jsFunction_item( final Object JavaDoc index ) {
330         return get( index );
331     }
332
333     /**
334      * Retrieves the item or items corresponding to the specified name (checks ids, and if
335      * that does not work, then names).
336      * @param name The name or id the element or elements to return.
337      * @return The element or elements corresponding to the specified name or id.
338      * @see <a HREF="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/nameditem.asp">MSDN doc</a>
339      */

340     public final Object JavaDoc jsFunction_namedItem( final String JavaDoc name ) {
341         return get( name );
342     }
343
344     /**
345      * Returns all the elements in this element array that have the specified tag name.
346      * This method returns an empty element array if there are no elements with the
347      * specified tag name.
348      * @param tagName The name of the tag of the elements to return.
349      * @return All the elements in this element array that have the specified tag name.
350      * @see <a HREF="http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/tags.asp">MSDN doc</a>
351      */

352     public final Object JavaDoc jsFunction_tags( final String JavaDoc tagName ) {
353         final ElementArray array = (ElementArray) makeJavaScriptObject(JS_OBJECT_NAME);
354         try {
355             final String JavaDoc newXPathExpr = xpath_.toString() + "[name() = '" + tagName.toLowerCase() + "']";
356             array.init(node_, xpath_.getNavigator().parseXPath(newXPathExpr));
357         }
358         catch (final SAXPathException e) {
359             // should never occur
360
throw Context.reportRuntimeError("Failed call tags: " + e.getMessage());
361         }
362
363         return array;
364     }
365
366     
367     /**
368      * Just for debug purpose.
369      * @see java.lang.Object#toString()
370      */

371     public String JavaDoc toString() {
372         if (xpath_ != null) {
373             return super.toString() + "<" + xpath_.toString() + ">";
374         }
375         return super.toString();
376     }
377 }
378
Popular Tags