KickJava   Java API By Example, From Geeks To Geeks.

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


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.beans.PropertyChangeListener JavaDoc;
41 import java.beans.PropertyChangeSupport JavaDoc;
42 import java.io.PrintWriter JavaDoc;
43 import java.io.StringWriter JavaDoc;
44 import java.util.Iterator JavaDoc;
45 import java.util.NoSuchElementException JavaDoc;
46
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49
50 import com.gargoylesoftware.htmlunit.Assert;
51 import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
52
53 /**
54  * Base class for nodes in the Html DOM tree. This class is modelled after the
55  * W3c DOM specification, but does not implement it.
56  *
57  * @version $Revision: 100 $
58  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
59  * @author <a HREF="mailto:gudujarlson@sf.net">Mike J. Bresnahan</a>
60  * @author David K. Taylor
61  * @author <a HREF="mailto:cse@dynabean.de">Christian Sell</a>
62  * @author Chris Erskine
63  * @author Mike Williams
64  * @author Marc Guillemot
65  */

66 public abstract class DomNode implements Cloneable JavaDoc {
67     /**
68      * node type constant for the <code>Document</code> node.
69      */

70     public static final short DOCUMENT_NODE = 0;
71     /**
72      * node type constant for <code>Element</code> nodes.
73      */

74     public static final short ELEMENT_NODE = 1;
75     /**
76      * node type constant for <code>Text</code> nodes.
77      */

78     public static final short TEXT_NODE = 3;
79
80     /**
81      * readyState constant for IE.
82      */

83     public static final String JavaDoc READY_STATE_UNINITIALIZED = "uninitialized";
84
85     /**
86      * readyState constant for IE.
87      */

88     public static final String JavaDoc READY_STATE_LOADING = "loading";
89
90     /**
91      * readyState constant for IE.
92      */

93     public static final String JavaDoc READY_STATE_COMPLETE = "complete";
94
95
96     /** the owning page of this node */
97     private final HtmlPage htmlPage_;
98
99     /** the parent node */
100     private DomNode parent_;
101
102     /**
103      * the previous sibling. The first child's <code>previousSibling</code> points
104      * to the end of the list
105      */

106     private DomNode previousSibling_;
107
108     /**
109      * The next sibling. The last child's <code>nextSibling</code> is <code>null</code>
110      */

111     private DomNode nextSibling_;
112
113     /** Start of the child list */
114     private DomNode firstChild_;
115
116     /**
117      * This is the javascript object corresponding to this DOM node. It is
118      * declared as Object so that we don't have a dependancy on the rhino jar
119      * file.<p>
120      *
121      * It may be null if there isn't a corresponding javascript object.
122      */

123     private Object JavaDoc scriptObject_;
124
125     /**
126      * Ready state is is an IE only value that is available to a large number of elements.
127      *
128      */

129     // TODO - Add processing for the other elements - Currently only body.
130
private String JavaDoc readyState_ = READY_STATE_UNINITIALIZED;
131
132     // We do lazy initialization on this since the vast majority of
133
// HtmlElement instances won't need it.
134
private PropertyChangeSupport JavaDoc propertyChangeSupport_ = null;
135
136     /** The name of the "element" property. Used when watching property change events. */
137     public static final String JavaDoc PROPERTY_ELEMENT = "element";
138
139     /**
140      * Create an instance
141      *
142      * @param htmlPage The page that contains this node
143      */

144     protected DomNode(final HtmlPage htmlPage) {
145         readyState_ = READY_STATE_LOADING; // At this time, I should be loading the object.
146
htmlPage_ = htmlPage;
147     }
148
149
150     /**
151      * Return the HtmlPage that contains this node
152      *
153      * @return See above
154      */

155     public HtmlPage getPage() {
156         return htmlPage_;
157     }
158
159     /**
160      * Internal use only - subject to change without notice.<p>
161      * Set the javascript object that corresponds to this node. This is not
162      * guarenteed to be set even if there is a javascript object for this
163      * DOM node.
164      * @param scriptObject The javascript object.
165      */

166     public void setScriptObject( final Object JavaDoc scriptObject ) {
167         scriptObject_ = scriptObject;
168     }
169
170     /**
171      * Get the last child node.
172      * @return The last child node or null if the current node has
173      * no children.
174      */

175     public DomNode getLastChild() {
176         if(firstChild_ != null) {
177             // last child is stored as the previous sibling of first child
178
return firstChild_.previousSibling_;
179         }
180         else {
181             return null;
182         }
183     }
184
185     /**
186      * @return the parent of this node, which may be <code>null</code> if this
187      * is the root node
188      */

189     public DomNode getParentNode() {
190         return parent_;
191     }
192
193     /**
194      * set the aprent node
195      * @param parent the parent node
196      */

197     protected void setParentNode(DomNode parent) {
198         parent_ = parent;
199     }
200
201
202     /**
203      * @return the previous sibling of this node, or <code>null</code> if this is
204      * the first node
205      */

206     public DomNode getPreviousSibling() {
207         if(parent_ == null || this == parent_.firstChild_) {
208             // previous sibling of first child points to last child
209
return null;
210         }
211         else {
212             return previousSibling_;
213         }
214     }
215
216     /**
217      * @return the next sibling
218      */

219     public DomNode getNextSibling() {
220         return nextSibling_;
221     }
222
223     /**
224      * @return the previous sibling
225      */

226     public DomNode getFirstChild() {
227         return firstChild_;
228     }
229
230     /** @param previous set the previousSibling field value */
231     protected void setPreviousSibling(DomNode previous) {
232         previousSibling_ = previous;
233     }
234
235     /** @param next set the nextSibling field value */
236     protected void setNextSibling(DomNode next) {
237         nextSibling_ = next;
238     }
239
240     /**
241      * Get the type of the current node.
242      * @return The node type
243      */

244     public abstract short getNodeType();
245
246     /**
247      * Get the name for the current node.
248      * @return The node name
249      */

250     public abstract String JavaDoc getNodeName();
251
252
253     /**
254      * Return a text representation of this element that represents what would
255      * be visible to the user if this page was shown in a web browser. For
256      * example, a select element would return the currently selected value as
257      * text
258      *
259      * @return The element as text
260      */

261     public String JavaDoc asText() {
262         String JavaDoc text = getChildrenAsText();
263
264         // Translate non-breaking spaces to regular spaces.
265
text = text.replace((char)160,' ');
266
267         // Remove extra whitespace
268
text = reduceWhitespace(text);
269         return text;
270     }
271
272
273     /**
274      * Return a text string that represents all the child elements as they
275      * would be visible in a web browser
276      *
277      * @return See above
278      * @see #asText()
279      */

280     protected final String JavaDoc getChildrenAsText() {
281         final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
282         final Iterator JavaDoc childIterator = getChildIterator();
283
284         if(!childIterator.hasNext()) {
285             return "";
286         }
287         while(childIterator.hasNext()) {
288             final DomNode node = (DomNode)childIterator.next();
289             buffer.append(node.asText());
290         }
291
292         return buffer.toString();
293     }
294
295
296     /**
297      * Removes extra whitespace from a string similar to what a browser does
298      * when it displays text.
299      * @param text The text to clean up.
300      * @return The cleaned up text.
301      */

302     private static String JavaDoc reduceWhitespace( final String JavaDoc text ) {
303         final StringBuffer JavaDoc buffer = new StringBuffer JavaDoc( text.length() );
304         final int length = text.length();
305         boolean whitespace = false;
306         for( int i = 0; i < length; ++i) {
307             final char ch = text.charAt(i);
308             if( whitespace ) {
309                 if( Character.isWhitespace(ch) == false ) {
310                     buffer.append(ch);
311                     whitespace = false;
312                 }
313             }
314             else {
315                 if( Character.isWhitespace(ch) ) {
316                     whitespace = true;
317                     buffer.append(' ');
318                 }
319                 else {
320                     buffer.append(ch);
321                 }
322             }
323         }
324         return buffer.toString().trim();
325     }
326
327     /**
328      * Return the log object for this element.
329      * @return The log object for this element.
330      */

331     protected final Log getLog() {
332         return LogFactory.getLog(getClass());
333     }
334
335     /**
336      * Return a string representation of the xml document from this element and all
337      * it's children (recursively).
338      *
339      * @return The xml string.
340      */

341     public String JavaDoc asXml() {
342
343         final StringWriter JavaDoc stringWriter = new StringWriter JavaDoc();
344         final PrintWriter JavaDoc printWriter = new PrintWriter JavaDoc(stringWriter);
345         printXml("", printWriter);
346         printWriter.close();
347         return stringWriter.toString();
348     }
349
350     /**
351      * recursively write the XML data for the node tree starting at <code>node</code>
352      *
353      * @param indent white space to indent child nodes
354      * @param printWriter writer where child nodes are written
355      */

356     protected void printXml( final String JavaDoc indent, final PrintWriter JavaDoc printWriter ) {
357
358         printWriter.println(indent+this);
359         printChildrenAsXml( indent, printWriter );
360     }
361
362     /**
363      * recursively write the XML data for the node tree starting at <code>node</code>
364      *
365      * @param indent white space to indent child nodes
366      * @param printWriter writer where child nodes are written
367      */

368     protected void printChildrenAsXml( final String JavaDoc indent, final PrintWriter JavaDoc printWriter ) {
369         DomNode child = getFirstChild();
370         while (child != null) {
371             child.printXml(indent+" ", printWriter);
372             child = child.getNextSibling();
373         }
374     }
375
376     /**
377      * Get the value for the current node.
378      * @return The node value
379      */

380     public String JavaDoc getNodeValue() {
381         return null;
382     }
383
384     /**
385      * @param x The new value
386      */

387     public void setNodeValue(final String JavaDoc x) {
388         // Default behavior is to do nothing, overridden in some subclasses
389
}
390
391     /**
392      * make a clone of this node
393      *
394      * @param deep if <code>true</code>, the clone will be propagated to the whole subtree
395      * below this one. Otherwise, the new node will not have any children. The page reference
396      * will always be the same as this node's.
397      * @return a new node
398      */

399     public DomNode cloneNode(final boolean deep) {
400
401         final DomNode newnode;
402         try {
403             newnode = (DomNode) clone();
404         }
405         catch( final CloneNotSupportedException JavaDoc e ) {
406             throw new IllegalStateException JavaDoc("Clone not supported for node ["+this+"]");
407         }
408
409         newnode.parent_ = null;
410         newnode.nextSibling_ = null;
411         newnode.previousSibling_ = null;
412         newnode.firstChild_ = null;
413         newnode.scriptObject_ = null;
414
415         // if deep, clone the kids too.
416
if (deep) {
417             for (DomNode child = firstChild_; child != null; child = child.nextSibling_) {
418                 newnode.appendChild(child.cloneNode(true));
419             }
420         }
421         return newnode;
422     }
423
424     /**
425      * Internal use only - subject to change without notice.<p>
426      * The logic of when and where the js object is created needs a clean up: functions using
427      * a js object of a dom node should not have to look if they should create it first
428      * Return the javascript object that corresponds to this node.
429      * @return The javascript object that corresponds to this node building it if necessary.
430      */

431     public Object JavaDoc getScriptObject() {
432         if (scriptObject_ == null) {
433             scriptObject_ = ((SimpleScriptable) getPage().getScriptObject()).makeScriptableFor(this);
434         }
435         return scriptObject_;
436     }
437     
438     /**
439      * append a child node to the end of the current list
440      * @param node the node to append
441      * @return the node added
442      */

443     public DomNode appendChild(final DomNode node) {
444
445         //clean up the new node, in case it is being moved
446
if(node != this) {
447             node.basicRemove();
448         }
449         if(firstChild_ == null) {
450             firstChild_ = node;
451             firstChild_.previousSibling_ = node;
452         }
453         else {
454             DomNode last = getLastChild();
455
456             last.nextSibling_ = node;
457             node.previousSibling_ = last;
458             node.nextSibling_ = null; //safety first
459
firstChild_.previousSibling_ = node; //new last node
460
}
461         node.parent_ = this;
462
463         getHtmlPage().notifyNodeAdded(node);
464
465         return node;
466     }
467
468     /**
469      * Gets the html page to which this objects belongs
470      * @return the html page
471      */

472     private HtmlPage getHtmlPage() {
473         if (this instanceof HtmlPage) {
474             return (HtmlPage) this;
475         }
476         else {
477             return htmlPage_;
478         }
479     }
480
481     /**
482      * insert a new child node before this node into the child relationship this node is a
483      * part of.
484      *
485      * @param newNode the new node to insert
486      * @throws IllegalStateException if this node is not a child of any other node
487      */

488     public void insertBefore(final DomNode newNode) throws IllegalStateException JavaDoc {
489
490         if(previousSibling_ == null) {
491             throw new IllegalStateException JavaDoc();
492         }
493
494         //clean up the new node, in case it is being moved
495
if(newNode != this) {
496             newNode.basicRemove();
497         }
498
499         if(parent_.firstChild_ == this) {
500             parent_.firstChild_ = newNode;
501         }
502         else {
503             previousSibling_.nextSibling_ = newNode;
504         }
505         newNode.previousSibling_ = previousSibling_;
506         newNode.nextSibling_ = this;
507         previousSibling_ = newNode;
508         newNode.parent_ = parent_;
509         
510         getHtmlPage().notifyNodeAdded(newNode);
511     }
512
513     /**
514      * remove this node from all relationships this node has with siblings an parents
515      * @throws IllegalStateException if this node is not a child of any other node
516      */

517     public void remove() throws IllegalStateException JavaDoc {
518         if(previousSibling_ == null) {
519             throw new IllegalStateException JavaDoc();
520         }
521         basicRemove();
522
523         getHtmlPage().notifyNodeRemoved(this);
524     }
525
526     /**
527      * cut off all relationships this node has with siblings an parents
528      */

529     private void basicRemove() {
530         if(parent_ != null && parent_.firstChild_ == this) {
531             parent_.firstChild_ = nextSibling_;
532         }
533         else if(previousSibling_ != null && previousSibling_.nextSibling_ == this) {
534             previousSibling_.nextSibling_ = nextSibling_;
535         }
536         if(nextSibling_ != null && nextSibling_.previousSibling_ == this) {
537             nextSibling_.previousSibling_ = previousSibling_;
538         }
539         if(parent_ != null && this == parent_.getLastChild()) {
540             parent_.firstChild_.previousSibling_ = previousSibling_;
541         }
542
543         nextSibling_ = null;
544         previousSibling_ = null;
545         parent_ = null;
546     }
547
548     /**
549      * replace this node with another node in the child relationship is part of
550      *
551      * @param newNode the node to replace this one
552      * @throws IllegalStateException if this node is not a child of any other node
553      */

554     public void replace(final DomNode newNode) throws IllegalStateException JavaDoc {
555         insertBefore(newNode);
556         remove();
557     }
558
559     /**
560      * @return an iterator over the children of this node
561      */

562     public Iterator JavaDoc getChildIterator() {
563         return new ChildIterator();
564     }
565
566     /**
567      * Add a property change listener to this node.
568      * @param listener The new listener.
569      */

570     public final synchronized void addPropertyChangeListener(
571         final PropertyChangeListener JavaDoc listener ) {
572
573         Assert.notNull("listener", listener);
574         if( propertyChangeSupport_ == null ) {
575             propertyChangeSupport_ = new PropertyChangeSupport JavaDoc(this);
576         }
577         propertyChangeSupport_.addPropertyChangeListener(listener);
578     }
579
580     /**
581      * Remove a property change listener from this node.
582      * @param listener The istener.
583      */

584     public final synchronized void removePropertyChangeListener(
585         final PropertyChangeListener JavaDoc listener ) {
586
587         Assert.notNull("listener", listener);
588         if( propertyChangeSupport_ != null ) {
589             propertyChangeSupport_.removePropertyChangeListener(listener);
590         }
591     }
592
593     /**
594      * Fire a property change event
595      * @param propertyName The name of the property.
596      * @param oldValue The old value.
597      * @param newValue The new value.
598      */

599     protected final synchronized void firePropertyChange(
600         final String JavaDoc propertyName, final Object JavaDoc oldValue, final Object JavaDoc newValue ) {
601
602         if( propertyChangeSupport_ != null ) {
603             propertyChangeSupport_.firePropertyChange(propertyName, oldValue, newValue);
604         }
605     }
606
607     /**
608      * an iterator over all children of this node
609      */

610     protected class ChildIterator implements Iterator JavaDoc {
611
612         private DomNode nextNode_ = firstChild_;
613         private DomNode currentNode_ = null;
614
615         /** @return whether there is a next object */
616         public boolean hasNext() {
617             return nextNode_ != null;
618         }
619
620         /** @return the next object */
621         public Object JavaDoc next() {
622             if(nextNode_ != null) {
623                 currentNode_ = nextNode_;
624                 nextNode_ = nextNode_.nextSibling_;
625                 return currentNode_;
626             }
627             else {
628                 throw new NoSuchElementException JavaDoc();
629             }
630         }
631
632         /** remove the current object */
633         public void remove() {
634             if(currentNode_ == null) {
635                 throw new IllegalStateException JavaDoc();
636             }
637             currentNode_.remove();
638         }
639     }
640
641
642     /**
643      * Return an iterator that will recursively iterate over every child element
644      * below this one.
645      * @return The iterator.
646      */

647     public DescendantElementsIterator getAllHtmlChildElements() {
648         return new DescendantElementsIterator();
649     }
650
651     /**
652      * an iterator over all HtmlElement descendants in document order
653      */

654     protected class DescendantElementsIterator implements Iterator JavaDoc {
655
656         private HtmlElement nextElement_ = getFirstChildElement(DomNode.this);
657
658         /** @return is there a next one? */
659         public boolean hasNext() {
660             return nextElement_ != null;
661         }
662
663         /** @return the next one */
664         public Object JavaDoc next() {
665             return nextElement();
666         }
667
668         /** remove the current object */
669         public void remove() {
670             if(nextElement_ == null) {
671                 throw new IllegalStateException JavaDoc();
672             }
673             if(nextElement_.getPreviousSibling() != null) {
674                 nextElement_.getPreviousSibling().remove();
675             }
676         }
677
678         /** @return is there a next one? */
679         public HtmlElement nextElement() {
680             final HtmlElement result = nextElement_;
681             setNextElement();
682             return result;
683         }
684
685         private void setNextElement() {
686             HtmlElement next = getFirstChildElement(nextElement_);
687             if( next == null ) {
688                 next = getNextSibling(nextElement_);
689             }
690             if( next == null ) {
691                 next = getNextElementUpwards(nextElement_);
692             }
693             nextElement_ = next;
694         }
695
696         private HtmlElement getNextElementUpwards( final DomNode startingNode ) {
697             if( startingNode == DomNode.this) {
698                 return null;
699             }
700
701             final DomNode parent = startingNode.getParentNode();
702             if( parent == DomNode.this ) {
703                 return null;
704             }
705
706             DomNode next = parent.getNextSibling();
707             while( next != null && next instanceof HtmlElement == false ) {
708                 next = next.getNextSibling();
709             }
710
711             if( next == null ) {
712                 return getNextElementUpwards(parent);
713             }
714             else {
715                 return (HtmlElement)next;
716             }
717         }
718
719         private HtmlElement getFirstChildElement(final DomNode parent) {
720             DomNode node = parent.getFirstChild();
721             while( node != null && node instanceof HtmlElement == false ) {
722                 node = node.getNextSibling();
723             }
724             return (HtmlElement)node;
725         }
726
727         private HtmlElement getNextSibling( final HtmlElement element) {
728             DomNode node = element.getNextSibling();
729             while( node != null && node instanceof HtmlElement == false ) {
730                 node = node.getNextSibling();
731             }
732             return (HtmlElement)node;
733         }
734     }
735
736     /**
737      * Return the readyState of this element. IE only.
738      * @return String - The current readyState for this element.
739      */

740     public String JavaDoc getReadyState() {
741         return readyState_;
742     }
743
744     /**
745      * Set the readyState for this element. IE only.
746      * @param state - The value to set the current state to.
747      */

748     public void setReadyState(final String JavaDoc state) {
749         readyState_ = state;
750     }
751     
752     /**
753      * Remove all the children of this node.
754      *
755      */

756     public void removeAllChildren() {
757         if (getFirstChild() == null) {
758             return;
759         }
760         final Iterator JavaDoc it = getChildIterator();
761         DomNode child;
762         while (it.hasNext()) {
763             child = (DomNode) it.next();
764             child.removeAllChildren();
765             it.remove();
766         }
767     }
768 }
769
Popular Tags