KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > html > dom > HTMLCollectionImpl


1 /*
2  * Copyright 1999,2000,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.html.dom;
17
18
19 import org.w3c.dom.Element JavaDoc;
20 import org.w3c.dom.Node JavaDoc;
21 import org.w3c.dom.html.HTMLAnchorElement;
22 import org.w3c.dom.html.HTMLAppletElement;
23 import org.w3c.dom.html.HTMLAreaElement;
24 import org.w3c.dom.html.HTMLCollection;
25 import org.w3c.dom.html.HTMLElement;
26 import org.w3c.dom.html.HTMLFormElement;
27 import org.w3c.dom.html.HTMLImageElement;
28 import org.w3c.dom.html.HTMLObjectElement;
29 import org.w3c.dom.html.HTMLOptionElement;
30 import org.w3c.dom.html.HTMLTableCellElement;
31 import org.w3c.dom.html.HTMLTableRowElement;
32 import org.w3c.dom.html.HTMLTableSectionElement;
33
34
35 /**
36  * Implements {@link org.w3c.dom.html.HTMLCollection} to traverse any named
37  * elements on a {@link org.w3c.dom.html.HTMLDocument}. The elements type to
38  * look for is identified in the constructor by code. This collection is not
39  * optimized for traversing large trees.
40  * <p>
41  * The collection has to meet two requirements: it has to be live, and it has
42  * to traverse depth first and always return results in that order. As such,
43  * using an object container (such as {@link java.util.Vector}) is expensive on
44  * insert/remove operations. Instead, the collection has been implemented using
45  * three traversing functions. As a result, operations on large documents will
46  * result in traversal of the entire document tree and consume a considerable
47  * amount of time.
48  * <p>
49  * Note that synchronization on the traversed document cannot be achieved.
50  * The document itself cannot be locked, and locking each traversed node is
51  * likely to lead to a dead lock condition. Therefore, there is a chance of the
52  * document being changed as results are fetched; in all likelihood, the results
53  * might be out dated, but not erroneous.
54  *
55  * @xerces.internal
56  *
57  * @version $Revision: 1.9 $ $Date: 2004/10/05 03:23:48 $
58  * @author <a HREF="mailto:arkin@exoffice.com">Assaf Arkin</a>
59  * @see org.w3c.dom.html.HTMLCollection
60  */

61 class HTMLCollectionImpl
62     implements HTMLCollection
63 {
64     
65
66     /**
67      * Request collection of all anchors in document: &lt;A&gt; elements that
68      * have a <code>name</code> attribute.
69      */

70     static final short ANCHOR = 1;
71     
72     
73     /**
74      * Request collection of all forms in document: &lt;FORM&gt; elements.
75      */

76     static final short FORM = 2;
77     
78     
79     /**
80      * Request collection of all images in document: &lt;IMAGE&gt; elements.
81      */

82     static final short IMAGE = 3;
83     
84     
85     /**
86      * Request collection of all Applets in document: &lt;APPLET&gt; and
87      * &lt;OBJECT&gt; elements (&lt;OBJECT&gt; must contain an Applet).
88      */

89     static final short APPLET = 4;
90     
91     
92     /**
93      * Request collection of all links in document: &lt;A&gt; and &lt;AREA&gt;
94      * elements (must have a <code>href</code> attribute).
95      */

96     static final short LINK = 5;
97     
98     
99     /**
100      * Request collection of all options in selection: &lt;OPTION&gt; elments in
101      * &lt;SELECT&gt; or &lt;OPTGROUP&gt;.
102      */

103     static final short OPTION = 6;
104     
105     
106     /**
107      * Request collection of all rows in table: &lt;TR&gt; elements in table or
108      * table section.
109      */

110     static final short ROW = 7;
111
112     
113     /**
114      * Request collection of all form elements: &lt;INPUT&gt;, &lt;BUTTON&gt;,
115      * &lt;SELECT&gt;, &lt;TEXT&gt; and &lt;TEXTAREA&gt; elements inside form
116      * &lt;FORM&gt;.
117      */

118     static final short ELEMENT = 8;
119     
120     
121     /**
122      * Request collection of all areas in map: &lt;AREA&gt; element in &lt;MAP&gt;
123      * (non recursive).
124      */

125     static final short AREA = -1;
126     
127
128     /**
129      * Request collection of all table bodies in table: &lt;TBODY&gt; element in
130      * table &lt;TABLE&gt; (non recursive).
131      */

132     static final short TBODY = -2;
133
134     
135     /**
136      * Request collection of all cells in row: &lt;TD&gt; elements in &lt;TR&gt;
137      * (non recursive).
138      */

139     static final short CELL = -3;
140
141     
142     /**
143      * Indicates what this collection is looking for. Holds one of the enumerated
144      * values and used by {@link #collectionMatch}. Set by the constructor and
145      * determine the collection's use for its life time.
146      */

147     private short _lookingFor;
148     
149     
150     /**
151      * This is the top level element underneath which the collection exists.
152      */

153     private Element JavaDoc _topLevel;
154
155
156     /**
157      * Construct a new collection that retrieves element of the specific type
158      * (<code>lookingFor</code>) from the specific document portion
159      * (<code>topLevel</code>).
160      *
161      * @param topLevel The element underneath which the collection exists
162      * @param lookingFor Code indicating what elements to look for
163      */

164     HTMLCollectionImpl( HTMLElement topLevel, short lookingFor )
165     {
166         if ( topLevel == null )
167             throw new NullPointerException JavaDoc( "HTM011 Argument 'topLevel' is null." );
168         _topLevel = topLevel;
169        _lookingFor = lookingFor;
170     }
171   
172   
173     /**
174      * Returns the length of the collection. This method might traverse the
175      * entire document tree.
176      *
177      * @return Length of the collection
178      */

179     public final int getLength()
180     {
181         // Call recursive function on top-level element.
182
return getLength( _topLevel );
183     }
184
185
186     /**
187      * Retrieves the indexed node from the collection. Nodes are numbered in
188      * tree order - depth-first traversal order. This method might traverse
189      * the entire document tree.
190      *
191      * @param index The index of the node to return
192      * @return The specified node or null if no such node found
193      */

194     public final Node JavaDoc item( int index )
195     {
196         if ( index < 0 )
197             throw new IllegalArgumentException JavaDoc( "HTM012 Argument 'index' is negative." );
198         // Call recursive function on top-level element.
199
return item( _topLevel, new CollectionIndex( index ) );
200     }
201     
202     
203     /**
204      * Retrieves the named node from the collection. The name is matched case
205      * sensitive against the <TT>id</TT> attribute of each element in the
206      * collection, returning the first match. The tree is traversed in
207      * depth-first order. This method might traverse the entire document tree.
208      *
209      * @param name The name of the node to return
210      * @return The specified node or null if no such node found
211      */

212     public final Node JavaDoc namedItem( String JavaDoc name )
213     {
214         if ( name == null )
215             throw new NullPointerException JavaDoc( "HTM013 Argument 'name' is null." );
216         // Call recursive function on top-level element.
217
return namedItem( _topLevel, name );
218     }
219     
220     
221     /**
222      * Recursive function returns the number of elements of a particular type
223      * that exist under the top level element. This is a recursive function
224      * and the top level element is passed along.
225      *
226      * @param topLevel Top level element from which to scan
227      * @return Number of elements
228      */

229     private int getLength( Element JavaDoc topLevel )
230     {
231         int length;
232         Node JavaDoc node;
233     
234         synchronized ( topLevel )
235         {
236             // Always count from zero and traverse all the childs of the
237
// current element in the order they appear.
238
length = 0;
239             node = topLevel.getFirstChild();
240             while ( node != null )
241             {
242                 // If a particular node is an element (could be HTML or XML),
243
// do two things: if it's the one we're looking for, count
244
// another matched element; at any rate, traverse it's
245
// children as well.
246
if ( node instanceof Element JavaDoc )
247                 {
248                     if ( collectionMatch( (Element JavaDoc) node, null ) )
249                         ++ length;
250                     else if ( recurse() )
251                         length += getLength( (Element JavaDoc) node );
252                 }
253                 node = node.getNextSibling();
254             }
255         }
256         return length;
257     }
258     
259         
260     /**
261      * Recursive function returns the numbered element of a particular type
262      * that exist under the top level element. This is a recursive function
263      * and the top level element is passed along.
264      * <p>
265      * Note that this function must call itself with an index and get back both
266      * the element (if one was found) and the new index which is decremeneted
267      * for any like element found. Since integers are only passed by value,
268      * this function makes use of a separate class ({@link CollectionIndex})
269      * to hold that index.
270      *
271      * @param topLevel Top level element from which to scan
272      * @param index The index of the item to retreive
273      * @return Number of elements
274      * @see CollectionIndex
275      */

276     private Node JavaDoc item( Element JavaDoc topLevel, CollectionIndex index )
277     {
278         Node JavaDoc node;
279         Node JavaDoc result;
280
281         synchronized ( topLevel )
282         {
283             // Traverse all the childs of the current element in the order
284
// they appear. Count from the index backwards until you reach
285
// matching element with an index of zero. Return that element.
286
node = topLevel.getFirstChild();
287             while ( node != null )
288             {
289                 // If a particular node is an element (could be HTML or XML),
290
// do two things: if it's the one we're looking for, decrease
291
// the index and if zero, return this node; at any rate,
292
// traverse it's children as well.
293
if ( node instanceof Element JavaDoc )
294                 {
295                     if ( collectionMatch( (Element JavaDoc) node, null ) )
296                     {
297                         if ( index.isZero() )
298                             return node;
299                         index.decrement();
300                     } else if ( recurse() )
301                     {
302                         result = item( (Element JavaDoc) node, index );
303                         if ( result != null )
304                             return result;
305                     }
306                 }
307                 node = node.getNextSibling();
308             }
309         }
310         return null;
311     }
312     
313     
314     /**
315      * Recursive function returns an element of a particular type with the
316      * specified name (<TT>id</TT> attribute).
317      *
318      * @param topLevel Top level element from which to scan
319      * @param name The named element to look for
320      * @return The first named element found
321      */

322     private Node JavaDoc namedItem( Element JavaDoc topLevel, String JavaDoc name )
323     {
324         Node JavaDoc node;
325         Node JavaDoc result;
326
327         synchronized ( topLevel )
328         {
329             // Traverse all the childs of the current element in the order
330
// they appear.
331
node = topLevel.getFirstChild();
332             while ( node != null )
333             {
334                 // If a particular node is an element (could be HTML or XML),
335
// do two things: if it's the one we're looking for, and the
336
// name (id attribute) attribute is the one we're looking for,
337
// return this element; otherwise, traverse it's children.
338
if ( node instanceof Element JavaDoc )
339                 {
340                     if ( collectionMatch( (Element JavaDoc) node, name ) )
341                         return node;
342                     else if ( recurse() )
343                     {
344                         result = namedItem( (Element JavaDoc) node, name );
345                         if ( result != null )
346                             return result;
347                     }
348                 }
349                 node = node.getNextSibling();
350             }
351             return node;
352         }
353     }
354     
355     
356     /**
357      * Returns true if scanning methods should iterate through the collection.
358      * When looking for elements in the document, recursing is needed to traverse
359      * the full document tree. When looking inside a specific element (e.g. for a
360      * cell inside a row), recursing can lead to erroneous results.
361      *
362      * @return True if methods should recurse to traverse entire tree
363      */

364     protected boolean recurse()
365     {
366         return _lookingFor > 0;
367     }
368     
369
370     /**
371      * Determines if current element matches based on what we're looking for.
372      * The element is passed along with an optional identifier name. If the
373      * element is the one we're looking for, return true. If the name is also
374      * specified, the name must match the <code>id</code> attribute
375      * (match <code>name</code> first for anchors).
376      *
377      * @param elem The current element
378      * @param name The identifier name or null
379      * @return The element matches what we're looking for
380      */

381     protected boolean collectionMatch( Element JavaDoc elem, String JavaDoc name )
382     {
383         boolean match;
384         
385         synchronized ( elem )
386         {
387             // Begin with no matching. Depending on what we're looking for,
388
// attempt to match based on the element type. This is the quickest
389
// way to match involving only a cast. Do the expensive string
390
// comparison later on.
391
match = false;
392             switch ( _lookingFor )
393             {
394             case ANCHOR:
395                 // Anchor is an <A> element with a 'name' attribute. Otherwise, it's
396
// just a link.
397
match = ( elem instanceof HTMLAnchorElement ) &&
398                         elem.getAttribute( "name" ).length() > 0;
399                 break;
400             case FORM:
401                 // Any <FORM> element.
402
match = ( elem instanceof HTMLFormElement );
403                 break;
404             case IMAGE:
405                 // Any <IMG> element. <OBJECT> elements with images are not returned.
406
match = ( elem instanceof HTMLImageElement );
407                 break;
408             case APPLET:
409                 // Any <APPLET> element, and any <OBJECT> element which represents an
410
// Applet. This is determined by 'codetype' attribute being
411
// 'application/java' or 'classid' attribute starting with 'java:'.
412
match = ( elem instanceof HTMLAppletElement ) ||
413                         ( elem instanceof HTMLObjectElement &&
414                           ( "application/java".equals( elem.getAttribute( "codetype" ) ) ||
415                             elem.getAttribute( "classid" ).startsWith( "java:" ) ) );
416                 break;
417             case ELEMENT:
418                 // All form elements implement HTMLFormControl for easy identification.
419
match = ( elem instanceof HTMLFormControl );
420                 break;
421             case LINK:
422                 // Any <A> element, and any <AREA> elements with an 'href' attribute.
423
match = ( ( elem instanceof HTMLAnchorElement ||
424                             elem instanceof HTMLAreaElement ) &&
425                           elem.getAttribute( "href" ).length() > 0 );
426                 break;
427             case AREA:
428                 // Any <AREA> element.
429
match = ( elem instanceof HTMLAreaElement );
430                 break;
431             case OPTION:
432                 // Any <OPTION> element.
433
match = ( elem instanceof HTMLOptionElement );
434                 break;
435             case ROW:
436                 // Any <TR> element.
437
match = ( elem instanceof HTMLTableRowElement );
438                 break;
439             case TBODY:
440                 // Any <TBODY> element (one of three table section types).
441
match = ( elem instanceof HTMLTableSectionElement &&
442                           elem.getTagName().equals( "tbody" ) );
443                 break;
444             case CELL:
445                 // Any <TD> element.
446
match = ( elem instanceof HTMLTableCellElement );
447                 break;
448             }
449         
450             // If element type was matched and a name was specified, must also match
451
// the name against either the 'id' or the 'name' attribute. The 'name'
452
// attribute is relevant only for <A> elements for backward compatibility.
453
if ( match && name != null )
454             {
455                 // If an anchor and 'name' attribute matches, return true. Otherwise,
456
// try 'id' attribute.
457
if ( elem instanceof HTMLAnchorElement &&
458                      name.equals( elem.getAttribute( "name" ) ) )
459                     return true;
460                 match = name.equals( elem.getAttribute( "id" ) );
461             }
462         }
463         return match;
464     }
465
466     
467 }
468
469
470 /**
471  * {@link CollectionImpl#item} must traverse down the tree and decrement the
472  * index until it matches an element who's index is zero. Since integers are
473  * passed by value, this class servers to pass the index into each recursion
474  * by reference. It encompasses all the operations that need be performed on
475  * the index, although direct access is possible.
476  *
477  * @xerces.internal
478  *
479  * @see CollectionImpl#item
480  */

481 class CollectionIndex
482 {
483     
484     
485     /**
486      * Returns the current index.
487      *
488      * @return Current index
489      */

490     int getIndex()
491     {
492         return _index;
493     }
494     
495     
496     /**
497      * Decrements the index by one.
498      */

499     void decrement()
500     {
501         -- _index;
502     }
503     
504     
505     /**
506      * Returns true if index is zero (or negative).
507      *
508      * @return True if index is zero
509      */

510     boolean isZero()
511     {
512         return _index <= 0;
513     }
514     
515     
516     /**
517      * Constructs a new index with the specified initial value. The index will
518      * then be decremeneted until it reaches zero.
519      *
520      * @param index The initial value
521      */

522     CollectionIndex( int index )
523     {
524         _index = index;
525     }
526     
527     
528     /**
529      * Holds the actual value that is passed by reference using this class.
530      */

531     private int _index;
532     
533
534 }
535
Popular Tags