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