misc changes/refactorings
[phpeclipse.git] / archive / net.sourceforge.phpeclipse.jtidy / src / net / sourceforge / phpdt / tidy / w3c / Node.java
1 /*
2  * @(#)Node.java   1.11 2000/08/16
3  *
4  */
5
6 package net.sourceforge.phpdt.tidy.w3c;
7
8
9 /**
10  *
11  * Node
12  *
13  * (c) 1998-2000 (W3C) MIT, INRIA, Keio University
14  * See Tidy.java for the copyright notice.
15  * Derived from <a href="http://www.w3.org/People/Raggett/tidy">
16  * HTML Tidy Release 4 Aug 2000</a>
17  *
18  * @author  Dave Raggett <dsr@w3.org>
19  * @author  Andy Quick <ac.quick@sympatico.ca> (translation to Java)
20  * @version 1.0, 1999/05/22
21  * @version 1.0.1, 1999/05/29
22  * @version 1.1, 1999/06/18 Java Bean
23  * @version 1.2, 1999/07/10 Tidy Release 7 Jul 1999
24  * @version 1.3, 1999/07/30 Tidy Release 26 Jul 1999
25  * @version 1.4, 1999/09/04 DOM support
26  * @version 1.5, 1999/10/23 Tidy Release 27 Sep 1999
27  * @version 1.6, 1999/11/01 Tidy Release 22 Oct 1999
28  * @version 1.7, 1999/12/06 Tidy Release 30 Nov 1999
29  * @version 1.8, 2000/01/22 Tidy Release 13 Jan 2000
30  * @version 1.9, 2000/06/03 Tidy Release 30 Apr 2000
31  * @version 1.10, 2000/07/22 Tidy Release 8 Jul 2000
32  * @version 1.11, 2000/08/16 Tidy Release 4 Aug 2000
33  */
34
35 /*
36   Used for elements and text nodes
37   element name is null for text nodes
38   start and end are offsets into lexbuf
39   which contains the textual content of
40   all elements in the parse tree.
41
42   parent and content allow traversal
43   of the parse tree in any direction.
44   attributes are represented as a linked
45   list of AttVal nodes which hold the
46   strings for attribute/value pairs.
47 */
48
49 public class Node {
50
51     public static final short RootNode        = 0;
52     public static final short DocTypeTag      = 1;
53     public static final short CommentTag      = 2;
54     public static final short ProcInsTag      = 3;
55     public static final short TextNode        = 4;
56     public static final short StartTag        = 5;
57     public static final short EndTag          = 6;
58     public static final short StartEndTag     = 7;
59     public static final short CDATATag        = 8;
60     public static final short SectionTag      = 9;
61     public static final short AspTag          = 10;
62     public static final short JsteTag         = 11;
63     public static final short PhpTag          = 12;
64
65     protected Node parent;
66     protected Node prev;
67     protected Node next;
68     protected Node last;
69     protected int start;             /* start of span onto text array */
70     protected int end;               /* end of span onto text array */
71     protected byte[] textarray;      /* the text array */
72     protected short type;              /* TextNode, StartTag, EndTag etc. */
73     protected boolean closed;            /* true if closed by explicit end tag */
74     protected boolean implicit;          /* true if inferred */
75     protected boolean linebreak;         /* true if followed by a line break */
76     protected Dict was;   /* old tag when it was changed */
77     protected Dict tag;   /* tag's dictionary definition */
78     protected String element;          /* name (null for text nodes) */
79     protected AttVal attributes;
80     protected Node content;
81
82     public Node()
83     {
84         this(TextNode, null, 0, 0);
85     }
86
87     public Node(short type, byte[] textarray, int start, int end)
88     {
89         this.parent = null;
90         this.prev = null;
91         this.next = null;
92         this.last = null;
93         this.start = start;
94         this.end = end;
95         this.textarray = textarray;
96         this.type = type;
97         this.closed = false;
98         this.implicit = false;
99         this.linebreak = false;
100         this.was = null;
101         this.tag = null;
102         this.element = null;
103         this.attributes = null;
104         this.content = null;
105     }
106
107     public Node(short type, byte[] textarray, int start, int end, String element, TagTable tt)
108     {
109         this.parent = null;
110         this.prev = null;
111         this.next = null;
112         this.last = null;
113         this.start = start;
114         this.end = end;
115         this.textarray = textarray;
116         this.type = type;
117         this.closed = false;
118         this.implicit = false;
119         this.linebreak = false;
120         this.was = null;
121         this.tag = null;
122         this.element = element;
123         this.attributes = null;
124         this.content = null;
125         if (type == StartTag || type == StartEndTag || type == EndTag)
126             tt.findTag(this);
127     }
128
129     /* used to clone heading nodes when split by an <HR> */
130     protected Object clone()
131     {
132         Node node = new Node();
133
134         node.parent = this.parent;
135         if (this.textarray != null)
136         {
137             node.textarray = new byte[this.end - this.start];
138             node.start = 0;
139             node.end = this.end - this.start;
140             if (node.end > 0)
141                 System.arraycopy(this.textarray, this.start,
142                                  node.textarray, node.start, node.end);
143         }
144         node.type = this.type;
145         node.closed = this.closed;
146         node.implicit = this.implicit;
147         node.linebreak = this.linebreak;
148         node.was = this.was;
149         node.tag = this.tag;
150         if (this.element != null)
151             node.element = this.element;
152         if (this.attributes != null)
153             node.attributes = (AttVal)this.attributes.clone();
154         return node;
155     }
156
157     public AttVal getAttrByName(String name)
158     {
159         AttVal attr;
160
161         for (attr = this.attributes; attr != null; attr = attr.next)
162         {
163             if (name != null &&
164                 attr.attribute != null &&
165                 attr.attribute.equals(name))
166                 break;
167         }
168
169         return attr;
170     }
171
172     /* default method for checking an element's attributes */
173     public void checkAttributes( Lexer lexer )
174     {
175         AttVal attval;
176
177         for (attval = this.attributes; attval != null; attval = attval.next)
178             attval.checkAttribute( lexer, this );
179     }
180
181     public void checkUniqueAttributes(Lexer lexer)
182     {
183         AttVal attval;
184
185         for (attval = this.attributes; attval != null; attval = attval.next) {
186             if (attval.asp == null && attval.php == null)
187                 attval.checkUniqueAttribute(lexer, this);
188         }
189     }
190
191     public void addAttribute(String name, String value)
192     {
193         AttVal av = new AttVal(null, null, null, null,
194                                '"', name, value);
195         av.dict =
196           AttributeTable.getDefaultAttributeTable().findAttribute(av);
197
198         if (this.attributes == null)
199             this.attributes = av;
200         else /* append to end of attributes */
201         {
202             AttVal here = this.attributes;
203
204             while (here.next != null)
205                 here = here.next;
206
207             here.next = av;
208         }
209     }
210
211     /* remove attribute from node then free it */
212     public void removeAttribute(AttVal attr)
213     {
214         AttVal av;
215         AttVal prev = null;
216         AttVal next;
217
218         for (av = this.attributes; av != null; av = next)
219         {
220             next = av.next;
221
222             if (av == attr)
223             {
224                 if (prev != null)
225                     prev.next = next;
226                 else
227                     this.attributes = next;
228             }
229             else
230                 prev = av;
231         }
232     }
233
234     /* find doctype element */
235     public Node findDocType()
236     {
237         Node node;
238
239         for (node = this.content; 
240             node != null && node.type != DocTypeTag; node = node.next);
241
242         return node;
243     }
244
245     public void discardDocType()
246     {
247         Node node;
248
249         node = findDocType();
250         if (node != null)
251         {
252             if (node.prev != null)
253                 node.prev.next = node.next;
254             else
255                 node.parent.content = node.next;
256
257             if (node.next != null)
258                 node.next.prev = node.prev;
259
260             node.next = null;
261         }
262     }
263
264     /* remove node from markup tree and discard it */
265     public static Node discardElement(Node element)
266     {
267         Node next = null;
268
269         if (element != null)
270         {
271             next = element.next;
272             removeNode(element);
273         }
274
275         return next;
276     }
277
278     /* insert node into markup tree */
279     public static void insertNodeAtStart(Node element, Node node)
280     {
281         node.parent = element;
282
283         if (element.content == null)
284             element.last = node;
285         else
286             element.content.prev = node; // AQ added 13 Apr 2000
287
288         node.next = element.content;
289         node.prev = null;
290         element.content = node;
291     }
292
293     /* insert node into markup tree */
294     public static void insertNodeAtEnd(Node element, Node node)
295     {
296         node.parent = element;
297         node.prev = element.last;
298
299         if (element.last != null)
300             element.last.next = node;
301         else
302             element.content = node;
303
304         element.last = node;
305     }
306
307     /*
308      insert node into markup tree in pace of element
309      which is moved to become the child of the node
310     */
311     public static void insertNodeAsParent(Node element, Node node)
312     {
313         node.content = element;
314         node.last = element;
315         node.parent = element.parent;
316         element.parent = node;
317     
318         if (node.parent.content == element)
319             node.parent.content = node;
320
321         if (node.parent.last == element)
322             node.parent.last = node;
323
324         node.prev = element.prev;
325         element.prev = null;
326
327         if (node.prev != null)
328             node.prev.next = node;
329
330         node.next = element.next;
331         element.next = null;
332
333         if (node.next != null)
334             node.next.prev = node;
335     }
336
337     /* insert node into markup tree before element */
338     public static void insertNodeBeforeElement(Node element, Node node)
339     {
340         Node parent;
341
342         parent = element.parent;
343         node.parent = parent;
344         node.next = element;
345         node.prev = element.prev;
346         element.prev = node;
347
348         if (node.prev != null)
349             node.prev.next = node;
350
351         if (parent.content == element)
352             parent.content = node;
353     }
354
355     /* insert node into markup tree after element */
356     public static void insertNodeAfterElement(Node element, Node node)
357     {
358         Node parent;
359
360         parent = element.parent;
361         node.parent = parent;
362
363         // AQ - 13Jan2000 fix for parent == null
364         if (parent != null && parent.last == element)
365             parent.last = node;
366         else
367         {
368             node.next = element.next;
369             // AQ - 13Jan2000 fix for node.next == null
370             if (node.next != null)
371                 node.next.prev = node;
372         }
373
374         element.next = node;
375         node.prev = element;
376     }
377
378     public static void trimEmptyElement(Lexer lexer, Node element)
379     {
380         TagTable tt = lexer.configuration.tt;
381
382         if (lexer.canPrune(element))
383         {
384             if (element.type != TextNode)
385                 Report.warning(lexer, element, null, Report.TRIM_EMPTY_ELEMENT);
386
387             discardElement(element);
388         }
389         else if (element.tag == tt.tagP && element.content == null)
390         {
391             /* replace <p></p> by <br><br> to preserve formatting */
392             Node node = lexer.inferredTag("br");
393             Node.coerceNode(lexer, element, tt.tagBr);
394             Node.insertNodeAfterElement(element, node);
395         }
396     }
397
398     /*
399       This maps 
400            <em>hello </em><strong>world</strong>
401       to
402            <em>hello</em> <strong>world</strong>
403
404       If last child of element is a text node
405       then trim trailing white space character
406       moving it to after element's end tag.
407     */
408     public static void trimTrailingSpace(Lexer lexer, Node element, Node last)
409     {
410         byte c;
411         TagTable tt = lexer.configuration.tt;
412
413         if (last != null && last.type == Node.TextNode &&
414             last.end > last.start)
415         {
416             c = lexer.lexbuf[last.end - 1];
417
418             if (c == 160 || c == (byte)' ')
419             {
420                 /* take care with <td>&nbsp;</td> */
421                 if (element.tag == tt.tagTd ||
422                     element.tag == tt.tagTh)
423                 {
424                     if (last.end > last.start + 1)
425                         last.end -= 1;
426                 }
427                 else
428                 {
429                     last.end -= 1;
430
431                     if (((element.tag.model & Dict.CM_INLINE) != 0) &&
432                             !((element.tag.model & Dict.CM_FIELD) != 0))
433                         lexer.insertspace = true;
434
435                     /* if empty string then delete from parse tree */
436                     if (last.start == last.end)
437                         trimEmptyElement(lexer, last);
438                 }
439             }
440         }
441     }
442
443     /*
444       This maps 
445            <p>hello<em> world</em>
446       to
447            <p>hello <em>world</em>
448
449       Trims initial space, by moving it before the
450       start tag, or if this element is the first in
451       parent's content, then by discarding the space
452     */
453     public static void trimInitialSpace(Lexer lexer, Node element, Node text)
454     {
455         Node prev, node;
456
457         // GLP: Local fix to Bug 119789. Remove this comment when parser.c is updated.
458         //      31-Oct-00. 
459         if (text.type == TextNode && text.textarray[text.start] == (byte)' ' 
460                            && (text.start < text.end))
461         {
462             if (((element.tag.model & Dict.CM_INLINE) != 0) &&
463                 !((element.tag.model & Dict.CM_FIELD) != 0) &&
464                 element.parent.content != element)
465             {
466                 prev = element.prev;
467
468                 if (prev != null && prev.type == TextNode)
469                 {
470                     if (prev.textarray[prev.end - 1] != (byte)' ')
471                         prev.textarray[prev.end++] = (byte)' ';
472
473                     ++element.start;
474                 }
475                 else /* create new node */
476                 {
477                     node = lexer.newNode();
478                     // Local fix for bug 228486 (GLP).  This handles the case
479                     // where we need to create a preceeding text node but there are
480                     // no "slots" in textarray that we can steal from the current
481                     // element.  Therefore, we create a new textarray containing
482                     // just the blank.  When Tidy is fixed, this should be removed.
483                     if (element.start >= element.end)
484                     {
485                         node.start = 0;
486                         node.end = 1;
487                         node.textarray = new byte[1];
488                     }
489                     else
490                     {
491                         node.start = element.start++;
492                         node.end = element.start;
493                         node.textarray = element.textarray;
494                     }
495                     node.textarray[node.start] = (byte)' ';
496                     node.prev = prev;
497                     if (prev != null)
498                         prev.next = node;
499                     node.next = element;
500                     element.prev = node;
501                     node.parent = element.parent;
502                 }
503             }
504
505             /* discard the space  in current node */
506             ++text.start;
507         }
508     }
509
510     /* 
511       Move initial and trailing space out.
512       This routine maps:
513
514            hello<em> world</em>
515       to
516            hello <em>world</em>
517       and
518            <em>hello </em><strong>world</strong>
519       to
520            <em>hello</em> <strong>world</strong>
521     */
522     public static void trimSpaces(Lexer lexer, Node element)
523     {
524         Node text = element.content;
525         TagTable tt = lexer.configuration.tt;
526
527         if (text != null && text.type == Node.TextNode &&
528             element.tag != tt.tagPre)
529             trimInitialSpace(lexer, element, text);
530
531         text = element.last;
532
533         if (text != null && text.type == Node.TextNode)
534             trimTrailingSpace(lexer, element, text);
535     }
536
537     public boolean isDescendantOf(Dict tag)
538     {
539         Node parent;
540
541         for (parent = this.parent;
542                 parent != null; parent = parent.parent)
543         {
544             if (parent.tag == tag)
545                 return true;
546         }
547
548         return false;
549     }
550
551     /*
552      the doctype has been found after other tags,
553      and needs moving to before the html element
554     */
555     public static void insertDocType(Lexer lexer, Node element, Node doctype)
556     {
557         TagTable tt = lexer.configuration.tt;
558       
559         Report.warning(lexer, element, doctype, Report.DOCTYPE_AFTER_TAGS);
560
561         while (element.tag != tt.tagHtml)
562             element = element.parent;
563
564         insertNodeBeforeElement(element, doctype);
565     }
566
567     public Node findBody(TagTable tt)
568     {
569         Node node;
570
571         node = this.content;
572
573         while (node != null && node.tag != tt.tagHtml)
574             node = node.next;
575
576         if (node == null)
577             return null;
578
579         node = node.content;
580
581         while (node != null && node.tag != tt.tagBody)
582             node = node.next;
583
584         return node;
585     }
586
587     public boolean isElement()
588     {
589         return (this.type == StartTag || this.type == StartEndTag ? true : false);
590     }
591
592     /*
593      unexpected content in table row is moved to just before
594      the table in accordance with Netscape and IE. This code
595      assumes that node hasn't been inserted into the row.
596     */
597     public static void moveBeforeTable(Node row, Node node, TagTable tt)
598     {
599         Node table;
600
601         /* first find the table element */
602         for (table = row.parent; table != null; table = table.parent)
603         {
604             if (table.tag == tt.tagTable)
605             {
606                 if (table.parent.content == table)
607                     table.parent.content = node;
608
609                 node.prev = table.prev;
610                 node.next = table;
611                 table.prev = node;
612                 node.parent = table.parent;
613         
614                 if (node.prev != null)
615                     node.prev.next = node;
616
617                 break;
618             }
619         }
620     }
621
622     /*
623      if a table row is empty then insert an empty cell
624      this practice is consistent with browser behavior
625      and avoids potential problems with row spanning cells
626     */
627     public static void fixEmptyRow(Lexer lexer, Node row)
628     {
629         Node cell;
630
631         if (row.content == null)
632         {
633             cell = lexer.inferredTag("td");
634             insertNodeAtEnd(row, cell);
635             Report.warning(lexer, row, cell, Report.MISSING_STARTTAG);
636         }
637     }
638
639     public static void coerceNode(Lexer lexer, Node node, Dict tag)
640     {
641         Node tmp = lexer.inferredTag(tag.name);
642         Report.warning(lexer, node, tmp, Report.OBSOLETE_ELEMENT);
643         node.was = node.tag;
644         node.tag = tag;
645         node.type = StartTag;
646         node.implicit = true;
647         node.element = tag.name;
648     }
649
650     /* extract a node and its children from a markup tree */
651     public static void removeNode(Node node)
652     {
653         if (node.prev != null)
654             node.prev.next = node.next;
655
656         if (node.next != null)
657             node.next.prev = node.prev;
658
659         if (node.parent != null)
660         {
661             if (node.parent.content == node)
662                 node.parent.content = node.next;
663
664             if (node.parent.last == node)
665                 node.parent.last = node.prev;
666         }
667
668         node.parent = node.prev = node.next = null;
669     }
670
671     public static boolean insertMisc(Node element, Node node)
672     {
673         if (node.type == CommentTag ||
674             node.type == ProcInsTag ||
675             node.type == CDATATag ||
676             node.type == SectionTag ||
677             node.type == AspTag ||
678             node.type == JsteTag ||
679             node.type == PhpTag)
680         {
681             insertNodeAtEnd(element, node);
682             return true;
683         }
684
685         return false;
686     }
687
688     /*
689      used to determine how attributes
690      without values should be printed
691      this was introduced to deal with
692      user defined tags e.g. Cold Fusion
693     */
694     public static boolean isNewNode(Node node)
695     {
696         if (node != null && node.tag != null)
697         {
698             return ((node.tag.model & Dict.CM_NEW) != 0);
699         }
700
701         return true;
702     }
703
704     public boolean hasOneChild()
705     {
706         return (this.content != null && this.content.next == null);
707     }
708
709     /* find html element */
710     public Node findHTML(TagTable tt)
711     {
712         Node node;
713
714         for (node = this.content;
715                 node != null && node.tag != tt.tagHtml; node = node.next);
716
717         return node;
718     }
719
720     public Node findHEAD(TagTable tt)
721     {
722         Node node;
723
724         node = this.findHTML(tt);
725
726         if (node != null)
727         {
728             for (node = node.content;
729                 node != null && node.tag != tt.tagHead;
730                 node = node.next);
731         }
732
733         return node;
734     }
735
736     public boolean checkNodeIntegrity()
737     {
738         Node child;
739         boolean found = false;
740
741         if (this.prev != null)
742         {
743             if (this.prev.next != this)
744                 return false;
745         }
746
747         if (this.next != null)
748         {
749             if (this.next.prev != this)
750                 return false;
751         }
752
753         if (this.parent != null)
754         {
755             if (this.prev == null && this.parent.content != this)
756                 return false;
757
758             if (this.next == null && this.parent.last != this)
759                 return false;
760
761             for (child = this.parent.content; child != null; child = child.next)
762                 if (child == this)
763                 {
764                     found = true;
765                     break;
766                 }
767
768             if (!found)
769                 return false;
770         }
771
772         for (child = this.content; child != null; child = child.next)
773             if (!child.checkNodeIntegrity())
774                 return false;
775
776         return true;
777     }
778
779     /*
780      Add class="foo" to node
781     */
782     public static void addClass(Node node, String classname)
783     {
784         AttVal classattr = node.getAttrByName("class");
785
786             /*
787              if there already is a class attribute
788              then append class name after a space
789             */
790             if (classattr != null)
791             {
792                 classattr.value = classattr.value + " " + classname;
793             }
794             else /* create new class attribute */
795                 node.addAttribute("class", classname);
796     }
797
798     /* --------------------- DEBUG -------------------------- */
799
800     private static final String[] nodeTypeString =
801     {
802         "RootNode",
803         "DocTypeTag",
804         "CommentTag",
805         "ProcInsTag",
806         "TextNode",
807         "StartTag",
808         "EndTag",
809         "StartEndTag",
810         "SectionTag",
811         "AspTag",
812         "PhpTag"
813     };
814
815     public String toString()
816     {
817         String s = "";
818         Node n = this;
819
820         while (n != null) {
821             s += "[Node type=";
822             s += nodeTypeString[n.type];
823             s += ",element=";
824             if (n.element != null)
825                 s += n.element;
826             else
827                 s += "null";
828             if (n.type == TextNode ||
829                 n.type == CommentTag ||
830                 n.type == ProcInsTag) {
831                 s += ",text=";
832                 if (n.textarray != null && n.start <= n.end) {
833                     s += "\"";
834                     s += Lexer.getString(n.textarray, n.start, n.end - n.start);
835                     s += "\"";
836                 } else {
837                     s += "null";
838                 }
839             }
840             s += ",content=";
841             if (n.content != null)
842                 s += n.content.toString();
843             else
844                 s += "null";
845             s += "]";
846             if (n.next != null)
847                 s += ",";
848             n = n.next;
849         }
850         return s;
851     }
852     /* --------------------- END DEBUG ---------------------- */
853
854
855     /* --------------------- DOM ---------------------------- */
856
857     protected org.w3c.dom.Node adapter = null;
858
859     protected org.w3c.dom.Node getAdapter()
860     {
861         if (adapter == null)
862         {
863             switch (this.type)
864             {
865                 case RootNode:
866                     adapter = new DOMDocumentImpl(this);
867                     break;
868                 case StartTag:
869                 case StartEndTag:
870                     adapter = new DOMElementImpl(this);
871                     break;
872                 case DocTypeTag:
873                     adapter = new DOMDocumentTypeImpl(this);
874                     break;
875                 case CommentTag:
876                     adapter = new DOMCommentImpl(this);
877                     break;
878                 case TextNode:
879                     adapter = new DOMTextImpl(this);
880                     break;
881                 case CDATATag:
882                     adapter = new DOMCDATASectionImpl(this);
883                     break;
884                 case ProcInsTag:
885                     adapter = new DOMProcessingInstructionImpl(this);
886                     break;
887                 default:
888                     adapter = new DOMNodeImpl(this);
889             }
890         }
891         return adapter;
892     }
893
894     protected Node cloneNode(boolean deep)
895     {
896         Node node = (Node)this.clone();
897         if (deep)
898         {
899             Node child;
900             Node newChild;
901             for (child = this.content; child != null; child = child.next)
902             {
903                 newChild = child.cloneNode(deep);
904                 insertNodeAtEnd(node, newChild);
905             }
906         }
907         return node;
908     }
909
910
911     protected void setType(short newType)
912     {
913         this.type = newType;
914     }
915
916     /* --------------------- END DOM ------------------------ */
917
918 }