KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > gargoylesoftware > htmlunit > html > HtmlElement


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.html;
39
40 import java.io.PrintWriter JavaDoc;
41 import java.net.MalformedURLException JavaDoc;
42 import java.net.URL JavaDoc;
43 import java.util.ArrayList JavaDoc;
44 import java.util.Collections JavaDoc;
45 import java.util.HashMap JavaDoc;
46 import java.util.Iterator JavaDoc;
47 import java.util.List JavaDoc;
48 import java.util.Map JavaDoc;
49 import java.util.NoSuchElementException JavaDoc;
50
51 import org.mozilla.javascript.BaseFunction;
52 import org.mozilla.javascript.Function;
53
54 import com.gargoylesoftware.htmlunit.Assert;
55 import com.gargoylesoftware.htmlunit.ElementNotFoundException;
56 import com.gargoylesoftware.htmlunit.javascript.host.EventHandler;
57
58 /**
59  * An abstract wrapper for html elements
60  *
61  * @version $Revision: 100 $
62  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
63  * @author <a HREF="mailto:gudujarlson@sf.net">Mike J. Bresnahan</a>
64  * @author David K. Taylor
65  * @author <a HREF="mailto:cse@dynabean.de">Christian Sell</a>
66  * @author David D. Kilzer
67  * @author Mike Gallaher
68  */

69 public abstract class HtmlElement extends DomNode {
70
71     /** Constant meaning that the specified attribute was not defined */
72     public static final String JavaDoc ATTRIBUTE_NOT_DEFINED = new String JavaDoc("");
73
74     /** Constant meaning that the specified attribute was found but its value was empty */
75     public static final String JavaDoc ATTRIBUTE_VALUE_EMPTY = new String JavaDoc("");
76
77     /** the map holding the attribute values by name */
78     private Map JavaDoc attributes_;
79
80     /** the map holding event handlers */
81     private Map JavaDoc eventHandlers_;
82     
83     /**
84      * Create an instance
85      *
86      * @param htmlPage The page that contains this element
87      * @param attributes a map ready initialized with the attributes for this element, or
88      * <code>null</code>. The map will be stored as is, not copied.
89      */

90     protected HtmlElement(final HtmlPage htmlPage, final Map JavaDoc attributes) {
91
92         super(htmlPage);
93         eventHandlers_ = Collections.EMPTY_MAP;
94         if(attributes != null) {
95             attributes_ = attributes;
96             attributesToEventHandlers();
97         }
98         else {
99             attributes_ = Collections.EMPTY_MAP;
100         }
101     }
102
103     /**
104      * Overrides DomNode.cloneNode so clone gets its own Map of attributes.
105      *
106      * @see DomNode#cloneNode(boolean)
107      */

108     public DomNode cloneNode(final boolean deep) {
109         final HtmlElement newnode = (HtmlElement) super.cloneNode(deep);
110
111         newnode.attributes_ = new HashMap JavaDoc();
112       
113         for (final Iterator JavaDoc it = attributes_.keySet().iterator(); it.hasNext();) {
114             final Object JavaDoc key = it.next();
115
116             if ("id".equals(key)) {
117                 continue;
118             }
119
120             newnode.setAttributeValue((String JavaDoc) key, (String JavaDoc) attributes_.get(key));
121         }
122
123         newnode.setId(ATTRIBUTE_VALUE_EMPTY);
124
125         return newnode;
126     }
127     
128     /**
129      * Return the value of the specified attribute or an empty string. If the
130      * result is an empty string then it will be either {@link #ATTRIBUTE_NOT_DEFINED}
131      * if the attribute wasn't specified or {@link #ATTRIBUTE_VALUE_EMPTY} if the
132      * attribute was specified but it was empty.
133      *
134      * @param attributeName the name of the attribute
135      * @return The value of the attribute or {@link #ATTRIBUTE_NOT_DEFINED}
136      * or {@link #ATTRIBUTE_VALUE_EMPTY}
137      */

138     public final String JavaDoc getAttributeValue( final String JavaDoc attributeName ) {
139
140         final String JavaDoc value = (String JavaDoc)attributes_.get(attributeName.toLowerCase());
141
142         //return value != null ? value : ATTRIBUTE_NOT_DEFINED;
143
if(value != null) {
144             return value;
145         }
146         else {
147             return ATTRIBUTE_NOT_DEFINED;
148         }
149     }
150
151     /**
152      * Set the value of the specified attribute.
153      *
154      * @param attributeName the name of the attribute
155      * @param attributeValue The value of the attribute
156      */

157     public final void setAttributeValue(final String JavaDoc attributeName, final String JavaDoc attributeValue) {
158
159         String JavaDoc value = attributeValue;
160         
161         if(attributes_ == Collections.EMPTY_MAP) {
162             attributes_ = new HashMap JavaDoc();
163         }
164         if(value.length() == 0) {
165             value = ATTRIBUTE_VALUE_EMPTY;
166         }
167         final boolean isId = attributeName.equalsIgnoreCase("id");
168         if (isId) {
169             getPage().removeIdElement(this);
170         }
171         attributes_.put(attributeName.toLowerCase(), value);
172         if (isId) {
173             getPage().addIdElement(this);
174         }
175     }
176
177     /**
178      * remove an attribute from this element
179      * @param attributeName the attribute attributeName
180      */

181     public final void removeAttribute(final String JavaDoc attributeName) {
182         if (attributeName.equalsIgnoreCase("id")) {
183             getPage().removeIdElement(this);
184         }
185         attributes_.remove(attributeName.toLowerCase());
186     }
187
188     /**
189      * Return true if the specified attribute has been defined. This is neccessary
190      * in order to distinguish between an attribute that is set to an empty string
191      * and one that was not defined at all.
192      *
193      * @param attributeName The attribute to check
194      * @return true if the attribute is defined
195      */

196     public boolean isAttributeDefined( final String JavaDoc attributeName ) {
197         return attributes_.get(attributeName.toLowerCase()) != null;
198     }
199
200     /**
201      * @return an iterator over the {@link java.util.Map.Entry} objects representing the
202      * attributes of this element. Each entry holds a string key and a string value
203      */

204     public Iterator JavaDoc getAttributeEntriesIterator() {
205         return attributes_.entrySet().iterator();
206     }
207
208     /**
209      * Return a Function to be executed when a given event occurs.
210      * @param eventName Name of event such as "onclick" or "onblur", etc.
211      * @return A rhino javascript executable Function, or null if no event
212      * handler has been defined
213      */

214     public final Function getEventHandler(final String JavaDoc eventName) {
215        return (Function) eventHandlers_.get(eventName);
216     }
217     
218     /**
219      * Register a Function as an event handler.
220      * @param eventName Name of event such as "onclick" or "onblur", etc.
221      * @param eventHandler A rhino javascript executable Function
222      */

223     public final void setEventHandler(final String JavaDoc eventName, final Function eventHandler) {
224         if (eventHandlers_ == Collections.EMPTY_MAP) {
225             eventHandlers_ = new HashMap JavaDoc();
226         }
227         eventHandlers_.put(eventName, eventHandler);
228     }
229     
230     /**
231      * Register a snippet of javascript code as an event handler. The javascript code will
232      * be wrapped inside a unique function declaration which provides one argument named
233      * "event"
234      * @param eventName Name of event such as "onclick" or "onblur", etc.
235      * @param jsSnippet executable javascript code
236      */

237     public final void setEventHandler(final String JavaDoc eventName, final String JavaDoc jsSnippet) {
238        
239         final BaseFunction function = new EventHandler(this, jsSnippet);
240         setEventHandler(eventName, function);
241         getLog().debug("Created event handler " + function.getFunctionName()
242                 + " for " + eventName + " on " + this);
243     }
244
245     /**
246      * Return the tag name of this element. The tag name is the actual html name. For example
247      * the tag name for HtmlAnchor is "a" and the tag name for HtmlTable is "table".
248      * This tag name will always be in lowercase, no matter what case was used in the original
249      * document.
250      *
251      * @return the tag name of this element.
252      */

253     public abstract String JavaDoc getTagName();
254
255     /** @return the node type */
256     public short getNodeType() {
257         return DomNode.ELEMENT_NODE;
258     }
259
260     /**
261      * @return The same value as returned by {@link #getTagName()},
262      */

263     public String JavaDoc getNodeName() {
264         return getTagName();
265     }
266
267     /**
268      * @return the identifier of this element.
269      */

270     public final String JavaDoc getId() {
271         return getAttributeValue("id");
272     }
273
274
275     /**
276      * Set the identifier this element.
277      *
278      * @param newId The new identifier of this element.
279      */

280     public final void setId(final String JavaDoc newId) {
281         setAttributeValue("id", newId);
282     }
283
284
285     /**
286      * Return the element with the given name that enclosed this element or null if this element is
287      * no such element is found.
288      * @param tagName the name of the tag searched (case insensitive)
289      * @return See above
290      */

291     public HtmlElement getEnclosingElement(final String JavaDoc tagName) {
292         final String JavaDoc tagNameLC = tagName.toLowerCase();
293
294         DomNode currentNode = getParentNode();
295         while (currentNode != null) {
296             if (currentNode instanceof HtmlElement
297                     && currentNode.getNodeName().equals(tagNameLC)) {
298
299                 return (HtmlElement) currentNode;
300             }
301             currentNode = currentNode.getParentNode();
302         }
303         return null;
304     }
305
306     /**
307      * Return the form that enclosed this element or null if this element is
308      * not within a form.
309      *
310      * @return See above
311      */

312     public HtmlForm getEnclosingForm() {
313         return (HtmlForm) getEnclosingElement("form");
314     }
315
316
317     /**
318      * Return the form that enclosed this element or throw an exception if this element is
319      * not within a form.
320      *
321      * @return See above
322      * @throws IllegalStateException If the element is not within a form.
323      */

324     public HtmlForm getEnclosingFormOrDie() throws IllegalStateException JavaDoc {
325         final HtmlForm form = getEnclosingForm();
326         if( form == null ) {
327             throw new IllegalStateException JavaDoc("Element is not comtained within a form: "+this);
328         }
329
330         return form;
331     }
332
333     /**
334      * recursively write the XML data for the node tree starting at <code>node</code>
335      *
336      * @param indent white space to indent child nodes
337      * @param printWriter writer where child nodes are written
338      */

339     protected void printXml( final String JavaDoc indent, final PrintWriter JavaDoc printWriter ) {
340
341         final boolean hasChildren = (getFirstChild() != null);
342         printWriter.print(indent+"<"+getTagName().toLowerCase());
343         final Map JavaDoc attributeMap = attributes_;
344         for( final Iterator JavaDoc it=attributeMap.keySet().iterator(); it.hasNext(); ) {
345             printWriter.print(" ");
346             final String JavaDoc name = (String JavaDoc)it.next();
347             printWriter.print(name);
348             printWriter.print("=\"");
349             printWriter.print(attributeMap.get(name));
350             printWriter.print("\"");
351         }
352
353         if( ! hasChildren ) {
354             printWriter.print("/");
355         }
356         printWriter.println(">");
357         printChildrenAsXml( indent, printWriter );
358         if( hasChildren ) {
359             printWriter.println(indent+"</"+getTagName().toLowerCase()+">");
360         }
361     }
362
363
364     /**
365      * Return a string representation of this object
366      *
367      * @return See above
368      */

369     public String JavaDoc toString() {
370         final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
371
372         final String JavaDoc className = getClass().getName();
373         final int index = className.lastIndexOf( '.' );
374         if( index == -1 ) {
375             buffer.append( className );
376         }
377         else {
378             buffer.append( className.substring( index + 1 ) );
379         }
380
381         buffer.append( "[<" );
382         buffer.append(getTagName());
383
384         for(final Iterator JavaDoc iterator=attributes_.keySet().iterator(); iterator.hasNext(); ) {
385             buffer.append( ' ' );
386             final String JavaDoc name = (String JavaDoc)iterator.next();
387             buffer.append(name);
388             buffer.append( "=\"" );
389             buffer.append(attributes_.get(name));
390             buffer.append( "\"" );
391         }
392         buffer.append( ">]" );
393
394         return buffer.toString();
395     }
396
397
398     /**
399      * Throw an exception. This is a convenience during development only - it
400      * will likely be removed in the future.
401      */

402     protected final void notImplemented() {
403         throw new RuntimeException JavaDoc( "Not implemented yet" );
404     }
405
406
407     /**
408      * Assert that the specified string is not empty. Throw an exception if it
409      * is.
410      *
411      * @param description The description to pass into the exception if this
412      * string is empty
413      * @param string The string to check
414      * @throws IllegalArgumentException If the string is empty
415      */

416     protected final void assertNotEmpty( final String JavaDoc description, final String JavaDoc string )
417         throws IllegalArgumentException JavaDoc {
418
419         if( string.length() == 0 ) {
420             throw new IllegalArgumentException JavaDoc( "String may not be empty: " + description );
421         }
422     }
423
424
425     /**
426      * Search by the specified criteria and return the first HtmlElement that
427      * is found
428      *
429      * @param elementName The name of the element
430      * @param attributeName The name of the attribute
431      * @param attributeValue The value of the attribute
432      * @return The HtmlElement
433      * @exception ElementNotFoundException If a particular xml element could
434      * not be found in the dom model
435      */

436     public final HtmlElement getOneHtmlElementByAttribute(
437             final String JavaDoc elementName,
438             final String JavaDoc attributeName,
439             final String JavaDoc attributeValue )
440         throws
441             ElementNotFoundException {
442
443         Assert.notNull( "elementName", elementName );
444         Assert.notNull( "attributeName", attributeName );
445         Assert.notNull( "attributeValue", attributeValue );
446
447         final List JavaDoc list = getHtmlElementsByAttribute( elementName, attributeName, attributeValue );
448         final int listSize = list.size();
449         if( listSize == 0 ) {
450             throw new ElementNotFoundException( elementName, attributeName, attributeValue );
451         }
452
453         return ( HtmlElement )list.get( 0 );
454     }
455
456
457     /**
458      * Return the html element with the specified id. If more than one element
459      * has this id (not allowed by the html spec) then return the first one.
460      *
461      * @param id The id value to search by
462      * @return The html element found
463      * @exception ElementNotFoundException If no element was found that matches
464      * the id
465      */

466     public HtmlElement getHtmlElementById( final String JavaDoc id )
467         throws ElementNotFoundException {
468
469         return getPage().getHtmlElementById(id);
470     }
471
472
473     /**
474      * Return true if there is a element with the specified id. This method
475      * is intended for situations where it is enough to know whether a specific
476      * element is present in the document.<p>
477      *
478      * Implementation note: This method calls getHtmlElementById() internally
479      * so writing code like the following would be extremely inefficient.
480      * <pre>
481      * if( hasHtmlElementWithId(id) ) {
482      * HtmlElement element = getHtmlElementWithId(id)
483      * ...
484      * }
485      * </pre>
486      *
487      * @param id The id to search by
488      * @return true if an element was found with the specified id.
489      */

490     public boolean hasHtmlElementWithId( final String JavaDoc id ) {
491         try {
492             getHtmlElementById(id);
493             return true;
494         }
495         catch( final ElementNotFoundException e ) {
496             return false;
497         }
498     }
499
500
501     /**
502      * Search by the specified criteria and return all the HtmlElement that
503      * are found
504      *
505      * @param elementName The name of the element
506      * @param attributeName The name of the attribute
507      * @param attributeValue The value of the attribute
508      * @return A list of HtmlElements
509      */

510     public final List JavaDoc getHtmlElementsByAttribute(
511             final String JavaDoc elementName,
512             final String JavaDoc attributeName,
513             final String JavaDoc attributeValue ) {
514
515         final List JavaDoc list = new ArrayList JavaDoc();
516         final DescendantElementsIterator iterator = new DescendantElementsIterator();
517         final String JavaDoc lowerCaseTagName = elementName.toLowerCase();
518         
519         while(iterator.hasNext()) {
520             final HtmlElement next = iterator.nextElement();
521             if(next.getTagName().equals(lowerCaseTagName)) {
522                 final String JavaDoc attValue = next.getAttributeValue(attributeName);
523                 if(attValue != null && attValue.equals(attributeValue)) {
524                     list.add(next);
525                 }
526             }
527         }
528         return list;
529     }
530
531     /**
532      * Given a list of tag names, return the html elements that correspond to
533      * any matching element
534      *
535      * @param acceptableTagNames The list of tag names to search by.
536      * @return The list of tag names
537      */

538     public final List JavaDoc getHtmlElementsByTagNames( final List JavaDoc acceptableTagNames ) {
539
540         final List JavaDoc list = new ArrayList JavaDoc();
541         final Iterator JavaDoc iterator = acceptableTagNames.iterator();
542         
543         while(iterator.hasNext()) {
544             final String JavaDoc next = iterator.next().toString().toLowerCase();
545             list.addAll(getHtmlElementsByTagName(next));
546         }
547         return list;
548     }
549
550     /**
551      * Given a list of tag names, return the html elements that correspond to
552      * any matching element
553      *
554      * @param tagName the tag name to match
555      * @return The list of tag names
556      */

557     public final List JavaDoc getHtmlElementsByTagName( final String JavaDoc tagName ) {
558
559         final List JavaDoc list = new ArrayList JavaDoc();
560         final DescendantElementsIterator iterator = new DescendantElementsIterator();
561         final String JavaDoc lowerCaseTagName = tagName.toLowerCase();
562         
563         while(iterator.hasNext()) {
564             final HtmlElement next = iterator.nextElement();
565             if(lowerCaseTagName.equals(next.getTagName())) {
566                 list.add(next);
567             }
568         }
569         return list;
570     }
571
572     /**
573      * Appends a child element to this HTML element with the specified tag name
574      * if this HTML element does not already have a child with that tag name.
575      * Returns the appended child element, or the first existent child element
576      * with the specified tag name if none was appended.
577      * @param tagName the tag name of the child to append
578      * @return the added child, or the first existing child if none was added
579      */

580     public final HtmlElement appendChildIfNoneExists(final String JavaDoc tagName) {
581         final HtmlElement child;
582         final List JavaDoc children = getHtmlElementsByTagName(tagName);
583         if(children.isEmpty()) {
584             // Add a new child and return it.
585
child = getPage().createElement(tagName);
586             appendChild(child);
587         }
588         else {
589             // Return the first existing child.
590
child = (HtmlElement) children.get(0);
591         }
592         return child;
593     }
594
595     /**
596      * Removes the <tt>i</tt>th child element with the specified tag name
597      * from all relationships, if possible.
598      * @param tagName the tag name of the child to remove
599      * @param i the index of the child to remove
600      */

601     public final void removeChild(final String JavaDoc tagName, final int i) {
602         final List JavaDoc children = getHtmlElementsByTagName(tagName);
603         if(i >= 0 && i < children.size()) {
604             final HtmlElement child = (HtmlElement) children.get(i);
605             child.remove();
606         }
607     }
608
609     /**
610      * Create a URL object from the specified href
611      *
612      * @param href The href
613      * @return A URL
614      * @exception MalformedURLException If a URL cannot be created from this
615      * href
616      * @deprecated Use {@link HtmlPage#getFullyQualifiedUrl(String)} instead.
617      */

618     public final URL JavaDoc makeUrlFromHref( final String JavaDoc href )
619         throws MalformedURLException JavaDoc {
620
621             return getPage().getFullyQualifiedUrl(href);
622     }
623
624     /**
625      * @return an iterator over the HtmlElement children of this object, i.e. excluding the
626      * non-element nodes
627      */

628     public final ChildElementsIterator getChildElementsIterator() {
629         return new ChildElementsIterator();
630     }
631
632     /**
633     * Convert javascript snippets defined in the attribute map to executable event handlers.
634     * Should be called only on construction.
635     */

636     private void attributesToEventHandlers() {
637         for (final Iterator JavaDoc iter=attributes_.entrySet().iterator(); iter.hasNext();) {
638             final Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iter.next();
639             final String JavaDoc eventName = (String JavaDoc) entry.getKey();
640
641             if (eventName.startsWith("on")) {
642                 setEventHandler(eventName, (String JavaDoc) entry.getValue());
643             }
644         }
645
646     }
647
648    /**
649      * an iterator over the HtmlElement children
650      */

651     protected class ChildElementsIterator implements Iterator JavaDoc {
652
653         private HtmlElement nextElement_;
654
655         /** constructor */
656         public ChildElementsIterator() {
657             if(getFirstChild() != null) {
658                 if(getFirstChild() instanceof HtmlElement) {
659                     nextElement_ = (HtmlElement)getFirstChild();
660                 }
661                 else {
662                     setNextElement(getFirstChild());
663                 }
664             }
665         }
666
667         /** @return is there a next one ? */
668         public boolean hasNext() {
669             return nextElement_ != null;
670         }
671
672         /** @return the next one */
673         public Object JavaDoc next() {
674             return nextElement();
675         }
676
677         /** remove the current one */
678         public void remove() {
679             if(nextElement_ == null) {
680                 throw new IllegalStateException JavaDoc();
681             }
682             if(nextElement_.getPreviousSibling() != null) {
683                 nextElement_.getPreviousSibling().remove();
684             }
685         }
686
687         /** @return the next element */
688         public HtmlElement nextElement() {
689             if(nextElement_ != null) {
690                 final HtmlElement result = nextElement_;
691                 setNextElement(nextElement_);
692                 return result;
693             }
694             else {
695                 throw new NoSuchElementException JavaDoc();
696             }
697         }
698
699         private void setNextElement(final DomNode node) {
700             DomNode next = node.getNextSibling();
701             while(next != null && !(next instanceof HtmlElement)) {
702                 next = next.getNextSibling();
703             }
704             nextElement_ = (HtmlElement)next;
705         }
706     }
707 }
708
Popular Tags