View Javadoc

1   /*
2    *  Java HTML Tidy - JTidy
3    *  HTML parser and pretty printer
4    *
5    *  Copyright (c) 1998-2000 World Wide Web Consortium (Massachusetts
6    *  Institute of Technology, Institut National de Recherche en
7    *  Informatique et en Automatique, Keio University). All Rights
8    *  Reserved.
9    *
10   *  Contributing Author(s):
11   *
12   *     Dave Raggett <dsr@w3.org>
13   *     Andy Quick <ac.quick@sympatico.ca> (translation to Java)
14   *     Gary L Peskin <garyp@firstech.com> (Java development)
15   *     Sami Lempinen <sami@lempinen.net> (release management)
16   *     Fabrizio Giustina <fgiust at users.sourceforge.net>
17   *
18   *  The contributing author(s) would like to thank all those who
19   *  helped with testing, bug fixes, and patience.  This wouldn't
20   *  have been possible without all of you.
21   *
22   *  COPYRIGHT NOTICE:
23   * 
24   *  This software and documentation is provided "as is," and
25   *  the copyright holders and contributing author(s) make no
26   *  representations or warranties, express or implied, including
27   *  but not limited to, warranties of merchantability or fitness
28   *  for any particular purpose or that the use of the software or
29   *  documentation will not infringe any third party patents,
30   *  copyrights, trademarks or other rights. 
31   *
32   *  The copyright holders and contributing author(s) will not be
33   *  liable for any direct, indirect, special or consequential damages
34   *  arising out of any use of the software or documentation, even if
35   *  advised of the possibility of such damage.
36   *
37   *  Permission is hereby granted to use, copy, modify, and distribute
38   *  this source code, or portions hereof, documentation and executables,
39   *  for any purpose, without fee, subject to the following restrictions:
40   *
41   *  1. The origin of this source code must not be misrepresented.
42   *  2. Altered versions must be plainly marked as such and must
43   *     not be misrepresented as being the original source.
44   *  3. This Copyright notice may not be removed or altered from any
45   *     source or altered source distribution.
46   * 
47   *  The copyright holders and contributing author(s) specifically
48   *  permit, without fee, and encourage the use of this source code
49   *  as a component for supporting the Hypertext Markup Language in
50   *  commercial products. If you use this source code in a product,
51   *  acknowledgment is not required but would be appreciated.
52   *
53   */
54  package org.w3c.tidy;
55  
56  import java.io.File;
57  import java.io.FileOutputStream;
58  import java.io.IOException;
59  import java.text.NumberFormat;
60  
61  
62  /***
63   * Pretty print parse tree. Block-level and unknown elements are printed on new lines and their contents indented 2
64   * spaces Inline elements are printed inline. Inline content is wrapped on spaces (except in attribute values or
65   * preformatted text, after start tags and before end tags.
66   * @author Dave Raggett <a href="mailto:dsr@w3.org">dsr@w3.org </a>
67   * @author Andy Quick <a href="mailto:ac.quick@sympatico.ca">ac.quick@sympatico.ca </a> (translation to Java)
68   * @author Fabrizio Giustina
69   * @version $Revision: 1.60 $ ($Author: fgiust $)
70   */
71  public class PPrint
72  {
73  
74      /***
75       * position: normal.
76       */
77      private static final short NORMAL = 0;
78  
79      /***
80       * position: preformatted text.
81       */
82      private static final short PREFORMATTED = 1;
83  
84      /***
85       * position: comment.
86       */
87      private static final short COMMENT = 2;
88  
89      /***
90       * position: attribute value.
91       */
92      private static final short ATTRIBVALUE = 4;
93  
94      /***
95       * position: nowrap.
96       */
97      private static final short NOWRAP = 8;
98  
99      /***
100      * position: cdata.
101      */
102     private static final short CDATA = 16;
103 
104     /***
105      * Start cdata token.
106      */
107     private static final String CDATA_START = "<![CDATA[";
108 
109     /***
110      * End cdata token.
111      */
112     private static final String CDATA_END = "]]>";
113 
114     /***
115      * Javascript comment start.
116      */
117     private static final String JS_COMMENT_START = "//";
118 
119     /***
120      * Javascript comment end.
121      */
122     private static final String JS_COMMENT_END = "";
123 
124     /***
125      * VB comment start.
126      */
127     private static final String VB_COMMENT_START = "\'";
128 
129     /***
130      * VB comment end.
131      */
132     private static final String VB_COMMENT_END = "";
133 
134     /***
135      * CSS comment start.
136      */
137     private static final String CSS_COMMENT_START = "/*";
138 
139     /***
140      * CSS comment end.
141      */
142     private static final String CSS_COMMENT_END = "*/";
143 
144     /***
145      * Default comment start.
146      */
147     private static final String DEFAULT_COMMENT_START = "";
148 
149     /***
150      * Default comment end.
151      */
152     private static final String DEFAULT_COMMENT_END = "";
153 
154     private int[] linebuf;
155 
156     private int lbufsize;
157 
158     private int linelen;
159 
160     private int wraphere;
161 
162     private boolean inAttVal;
163 
164     private boolean inString;
165 
166     /***
167      * Current slide number.
168      */
169     private int slide;
170 
171     /***
172      * Total slides count.
173      */
174     private int count;
175 
176     private Node slidecontent;
177 
178     /***
179      * current configuration.
180      */
181     private Configuration configuration;
182 
183     /***
184      * Instantiates a new PPrint.
185      * @param configuration configuration
186      */
187     public PPrint(Configuration configuration)
188     {
189         this.configuration = configuration;
190     }
191 
192     /***
193      * @param ind
194      * @return
195      */
196     int cWrapLen(int ind)
197     {
198         /* #431953 - start RJ Wraplen adjusted for smooth international ride */
199         if ("zh".equals(this.configuration.language))
200         {
201             // Chinese characters take two positions on a fixed-width screen
202             // It would be more accurate to keep a parallel linelen and wraphere incremented by 2 for Chinese characters
203             // and 1 otherwise, but this is way simpler.
204             return (ind + ((this.configuration.wraplen - ind) / 2));
205         }
206         if ("ja".equals(this.configuration.language))
207         {
208             /* average Japanese text is 30% kanji */
209             return (ind + (((this.configuration.wraplen - ind) * 7) / 10));
210         }
211         return (this.configuration.wraplen);
212         /* #431953 - end RJ */
213     }
214 
215     /***
216      * return one less than the number of bytes used by the UTF-8 byte sequence. The Unicode char is returned in ch.
217      * @param str points to the UTF-8 byte sequence
218      * @param start starting offset in str
219      * @param ch initialized to 1st byte, passed as an array to allow modification
220      * @return one less that the number of bytes used by UTF-8 char
221      */
222     public static int getUTF8(byte[] str, int start, int[] ch)
223     {
224 
225         int[] n = new int[1];
226 
227         int[] bytes = new int[]{0};
228 
229         // first byte "str[0]" is passed in separately from the
230         // rest of the UTF-8 byte sequence starting at "str[1]"
231         byte[] successorBytes = str;
232 
233         boolean err = EncodingUtils.decodeUTF8BytesToChar(
234             n,
235             TidyUtils.toUnsigned(str[start]),
236             successorBytes,
237             null,
238             bytes,
239             start + 1);
240 
241         if (err)
242         {
243             n[0] = 0xFFFD; // replacement char
244         }
245         ch[0] = n[0];
246         return bytes[0] - 1;
247 
248     }
249 
250     /***
251      * store char c as UTF-8 encoded byte stream.
252      * @param buf
253      * @param start
254      * @param c
255      * @return
256      */
257     public static int putUTF8(byte[] buf, int start, int c)
258     {
259         int[] count = new int[]{0};
260 
261         boolean err = EncodingUtils.encodeCharToUTF8Bytes(c, buf, null, count);
262         if (err)
263         {
264             // replacement char 0xFFFD encoded as UTF-8
265             buf[0] = (byte) 0xEF;
266             buf[1] = (byte) 0xBF;
267             buf[2] = (byte) 0xBD;
268             count[0] = 3;
269         }
270 
271         start += count[0];
272 
273         return start;
274     }
275 
276     private void addC(int c, int index)
277     {
278         if (index + 1 >= lbufsize)
279         {
280             while (index + 1 >= lbufsize)
281             {
282                 if (lbufsize == 0)
283                 {
284                     lbufsize = 256;
285                 }
286                 else
287                 {
288                     lbufsize = lbufsize * 2;
289                 }
290             }
291 
292             int[] temp = new int[lbufsize];
293             if (linebuf != null)
294             {
295                 System.arraycopy(linebuf, 0, temp, 0, index);
296             }
297             linebuf = temp;
298         }
299 
300         linebuf[index] = c;
301     }
302 
303     /***
304      * Adds an ascii String.
305      * @param str String to be added
306      * @param index actual line lenght
307      * @return final line length
308      */
309     private int addAsciiString(String str, int index)
310     {
311 
312         int len = str.length();
313         if (index + len >= lbufsize)
314         {
315             while (index + len >= lbufsize)
316             {
317                 if (lbufsize == 0)
318                 {
319                     lbufsize = 256;
320                 }
321                 else
322                 {
323                     lbufsize = lbufsize * 2;
324                 }
325             }
326 
327             int[] temp = new int[lbufsize];
328             if (linebuf != null)
329             {
330                 System.arraycopy(linebuf, 0, temp, 0, index);
331             }
332             linebuf = temp;
333         }
334 
335         for (int ix = 0; ix < len; ++ix)
336         {
337             linebuf[index + ix] = str.charAt(ix);
338         }
339         return index + len;
340     }
341 
342     /***
343      * @param fout
344      * @param indent
345      */
346     private void wrapLine(Out fout, int indent)
347     {
348         int i, p, q;
349 
350         if (wraphere == 0)
351         {
352             return;
353         }
354 
355         for (i = 0; i < indent; ++i)
356         {
357             fout.outc(' ');
358         }
359 
360         for (i = 0; i < wraphere; ++i)
361         {
362             fout.outc(linebuf[i]);
363         }
364 
365         if (inString)
366         {
367             fout.outc(' ');
368             fout.outc('//');
369         }
370 
371         fout.newline();
372 
373         if (linelen > wraphere)
374         {
375             p = 0;
376 
377             if (linebuf[wraphere] == ' ')
378             {
379                 ++wraphere;
380             }
381 
382             q = wraphere;
383             addC('\0', linelen);
384 
385             while (true)
386             {
387                 linebuf[p] = linebuf[q];
388                 if (linebuf[q] == 0)
389                 {
390                     break;
391                 }
392                 p++;
393                 q++;
394             }
395             linelen -= wraphere;
396         }
397         else
398         {
399             linelen = 0;
400         }
401 
402         wraphere = 0;
403     }
404 
405     /***
406      * @param fout
407      * @param indent
408      * @param inString
409      */
410     private void wrapAttrVal(Out fout, int indent, boolean inString)
411     {
412         int i, p, q;
413 
414         for (i = 0; i < indent; ++i)
415         {
416             fout.outc(' ');
417         }
418 
419         for (i = 0; i < wraphere; ++i)
420         {
421             fout.outc(linebuf[i]);
422         }
423 
424         fout.outc(' ');
425 
426         if (inString)
427         {
428             fout.outc('//');
429         }
430 
431         fout.newline();
432 
433         if (linelen > wraphere)
434         {
435             p = 0;
436 
437             if (linebuf[wraphere] == ' ')
438             {
439                 ++wraphere;
440             }
441 
442             q = wraphere;
443             addC('\0', linelen);
444 
445             while (true)
446             {
447                 linebuf[p] = linebuf[q];
448                 if (linebuf[q] == 0)
449                 {
450                     break;
451                 }
452                 p++;
453                 q++;
454             }
455             linelen -= wraphere;
456         }
457         else
458         {
459             linelen = 0;
460         }
461 
462         wraphere = 0;
463     }
464 
465     /***
466      * @param fout
467      * @param indent
468      */
469     public void flushLine(Out fout, int indent)
470     {
471         int i;
472 
473         if (linelen > 0)
474         {
475             if (indent + linelen >= this.configuration.wraplen)
476             {
477                 wrapLine(fout, indent);
478             }
479 
480             if (!inAttVal || this.configuration.indentAttributes)
481             {
482                 for (i = 0; i < indent; ++i)
483                 {
484                     fout.outc(' ');
485                 }
486             }
487 
488             for (i = 0; i < linelen; ++i)
489             {
490                 fout.outc(linebuf[i]);
491             }
492         }
493 
494         fout.newline();
495         linelen = 0;
496         wraphere = 0;
497         inAttVal = false;
498     }
499 
500     /***
501      * @param fout
502      * @param indent
503      */
504     public void condFlushLine(Out fout, int indent)
505     {
506         int i;
507 
508         if (linelen > 0)
509         {
510             if (indent + linelen >= this.configuration.wraplen)
511             {
512                 wrapLine(fout, indent);
513             }
514 
515             if (!inAttVal || this.configuration.indentAttributes)
516             {
517                 for (i = 0; i < indent; ++i)
518                 {
519                     fout.outc(' ');
520                 }
521             }
522 
523             for (i = 0; i < linelen; ++i)
524             {
525                 fout.outc(linebuf[i]);
526             }
527 
528             fout.newline();
529             linelen = 0;
530             wraphere = 0;
531             inAttVal = false;
532         }
533     }
534 
535     /***
536      * @param c
537      * @param mode
538      */
539     private void printChar(int c, short mode)
540     {
541         String entity;
542         boolean breakable = false; // #431953 - RJ
543 
544         if (c == ' ' && !TidyUtils.toBoolean(mode & (PREFORMATTED | COMMENT | ATTRIBVALUE | CDATA)))
545         {
546             // coerce a space character to a non-breaking space
547             if (TidyUtils.toBoolean(mode & NOWRAP))
548             {
549                 // by default XML doesn't define &nbsp;
550                 if (this.configuration.numEntities || this.configuration.xmlTags)
551                 {
552                     addC('&', linelen++);
553                     addC('#', linelen++);
554                     addC('1', linelen++);
555                     addC('6', linelen++);
556                     addC('0', linelen++);
557                     addC(';', linelen++);
558                 }
559                 else
560                 {
561                     // otherwise use named entity
562                     addC('&', linelen++);
563                     addC('n', linelen++);
564                     addC('b', linelen++);
565                     addC('s', linelen++);
566                     addC('p', linelen++);
567                     addC(';', linelen++);
568                 }
569                 return;
570             }
571             wraphere = linelen;
572         }
573 
574         // comment characters are passed raw
575         if (TidyUtils.toBoolean(mode & (COMMENT | CDATA)))
576         {
577             addC(c, linelen++);
578             return;
579         }
580 
581         // except in CDATA map < to &lt; etc.
582         if (!TidyUtils.toBoolean(mode & CDATA))
583         {
584             if (c == '<')
585             {
586                 addC('&', linelen++);
587                 addC('l', linelen++);
588                 addC('t', linelen++);
589                 addC(';', linelen++);
590                 return;
591             }
592 
593             if (c == '>')
594             {
595                 addC('&', linelen++);
596                 addC('g', linelen++);
597                 addC('t', linelen++);
598                 addC(';', linelen++);
599                 return;
600             }
601 
602             // naked '&' chars can be left alone or quoted as &amp;
603             // The latter is required for XML where naked '&' are illegal.
604             if (c == '&' && this.configuration.quoteAmpersand)
605             {
606                 addC('&', linelen++);
607                 addC('a', linelen++);
608                 addC('m', linelen++);
609                 addC('p', linelen++);
610                 addC(';', linelen++);
611                 return;
612             }
613 
614             if (c == '"' && this.configuration.quoteMarks)
615             {
616                 addC('&', linelen++);
617                 addC('q', linelen++);
618                 addC('u', linelen++);
619                 addC('o', linelen++);
620                 addC('t', linelen++);
621                 addC(';', linelen++);
622                 return;
623             }
624 
625             if (c == '\'' && this.configuration.quoteMarks)
626             {
627                 addC('&', linelen++);
628                 addC('#', linelen++);
629                 addC('3', linelen++);
630                 addC('9', linelen++);
631                 addC(';', linelen++);
632                 return;
633             }
634 
635             if (c == 160 && !this.configuration.rawOut)
636             {
637                 if (this.configuration.makeBare)
638                 {
639                     addC(' ', linelen++);
640                 }
641                 else if (this.configuration.quoteNbsp)
642                 {
643                     addC('&', linelen++);
644 
645                     if (this.configuration.numEntities || this.configuration.xmlTags)
646                     {
647                         addC('#', linelen++);
648                         addC('1', linelen++);
649                         addC('6', linelen++);
650                         addC('0', linelen++);
651                     }
652                     else
653                     {
654                         addC('n', linelen++);
655                         addC('b', linelen++);
656                         addC('s', linelen++);
657                         addC('p', linelen++);
658                     }
659 
660                     addC(';', linelen++);
661                 }
662                 else
663                 {
664                     addC(c, linelen++);
665                 }
666 
667                 return;
668             }
669         }
670 
671         // #431953 - start RJ
672         // Handle encoding-specific issues
673 
674         switch (this.configuration.getOutCharEncoding())
675         {
676             case Configuration.UTF8 :
677                 // Chinese doesn't have spaces, so it needs other kinds of breaks
678                 // This will also help documents using nice Unicode punctuation
679                 // But we leave the ASCII range punctuation untouched
680 
681                 // Break after any punctuation or spaces characters
682                 if ((c >= 0x2000) && !TidyUtils.toBoolean(mode & PREFORMATTED))
683                 {
684                     if (((c >= 0x2000) && (c <= 0x2006))
685                         || ((c >= 0x2008) && (c <= 0x2010))
686                         || ((c >= 0x2011) && (c <= 0x2046))
687                         || ((c >= 0x207D) && (c <= 0x207E))
688                         || ((c >= 0x208D) && (c <= 0x208E))
689                         || ((c >= 0x2329) && (c <= 0x232A))
690                         || ((c >= 0x3001) && (c <= 0x3003))
691                         || ((c >= 0x3008) && (c <= 0x3011))
692                         || ((c >= 0x3014) && (c <= 0x301F))
693                         || ((c >= 0xFD3E) && (c <= 0xFD3F))
694                         || ((c >= 0xFE30) && (c <= 0xFE44))
695                         || ((c >= 0xFE49) && (c <= 0xFE52))
696                         || ((c >= 0xFE54) && (c <= 0xFE61))
697                         || ((c >= 0xFE6A) && (c <= 0xFE6B))
698                         || ((c >= 0xFF01) && (c <= 0xFF03))
699                         || ((c >= 0xFF05) && (c <= 0xFF0A))
700                         || ((c >= 0xFF0C) && (c <= 0xFF0F))
701                         || ((c >= 0xFF1A) && (c <= 0xFF1B))
702                         || ((c >= 0xFF1F) && (c <= 0xFF20))
703                         || ((c >= 0xFF3B) && (c <= 0xFF3D))
704                         || ((c >= 0xFF61) && (c <= 0xFF65)))
705                     {
706                         wraphere = linelen + 2; // 2, because AddChar is not till later
707                         breakable = true;
708                     }
709                     else
710                     {
711                         switch (c)
712                         {
713                             case 0xFE63 :
714                             case 0xFE68 :
715                             case 0x3030 :
716                             case 0x30FB :
717                             case 0xFF3F :
718                             case 0xFF5B :
719                             case 0xFF5D :
720                                 wraphere = linelen + 2;
721                                 breakable = true;
722                         }
723                     }
724                     // but break before a left punctuation
725                     if (breakable)
726                     {
727                         if (((c >= 0x201A) && (c <= 0x201C)) || ((c >= 0x201E) && (c <= 0x201F)))
728                         {
729                             wraphere--;
730                         }
731                         else
732                         {
733                             switch (c)
734                             {
735                                 case 0x2018 :
736                                 case 0x2039 :
737                                 case 0x2045 :
738                                 case 0x207D :
739                                 case 0x208D :
740                                 case 0x2329 :
741                                 case 0x3008 :
742                                 case 0x300A :
743                                 case 0x300C :
744                                 case 0x300E :
745                                 case 0x3010 :
746                                 case 0x3014 :
747                                 case 0x3016 :
748                                 case 0x3018 :
749                                 case 0x301A :
750                                 case 0x301D :
751                                 case 0xFD3E :
752                                 case 0xFE35 :
753                                 case 0xFE37 :
754                                 case 0xFE39 :
755                                 case 0xFE3B :
756                                 case 0xFE3D :
757                                 case 0xFE3F :
758                                 case 0xFE41 :
759                                 case 0xFE43 :
760                                 case 0xFE59 :
761                                 case 0xFE5B :
762                                 case 0xFE5D :
763                                 case 0xFF08 :
764                                 case 0xFF3B :
765                                 case 0xFF5B :
766                                 case 0xFF62 :
767                                     wraphere--;
768                             }
769                         }
770                     }
771                 }
772                 break;
773             case Configuration.BIG5 :
774                 // Allow linebreak at Chinese punctuation characters
775                 // There are not many spaces in Chinese
776                 addC(c, linelen++);
777                 if (((c & 0xFF00) == 0xA100) && !TidyUtils.toBoolean(mode & PREFORMATTED))
778                 {
779                     wraphere = linelen;
780                     // opening brackets have odd codes: break before them
781                     if ((c > 0x5C) && (c < 0xAD) && ((c & 1) == 1))
782                     {
783                         wraphere--;
784                     }
785                 }
786                 return;
787             case Configuration.SHIFTJIS :
788             case Configuration.ISO2022 : // ISO 2022 characters are passed raw
789                 addC(c, linelen++);
790                 return;
791             default :
792                 if (this.configuration.rawOut)
793                 {
794                     addC(c, linelen++);
795                     return;
796                 }
797         // #431953 - end RJ
798         }
799 
800         // if preformatted text, map &nbsp; to space
801         if (c == 160 && TidyUtils.toBoolean(mode & PREFORMATTED))
802         {
803             addC(' ', linelen++);
804             return;
805         }
806 
807         // Filters from Word and PowerPoint often use smart quotes resulting in character codes between 128 and 159.
808         // Unfortunately, the corresponding HTML 4.0 entities for these are not widely supported.
809         // The following converts dashes and quotation marks to the nearest ASCII equivalent.
810         // My thanks to Andrzej Novosiolov for his help with this code.
811 
812         if (this.configuration.makeClean && this.configuration.asciiChars || this.configuration.makeBare)
813         {
814             if (c >= 0x2013 && c <= 0x201E)
815             {
816                 switch (c)
817                 {
818                     case 0x2013 : // en dash
819                     case 0x2014 : // em dash
820                         c = '-';
821                         break;
822                     case 0x2018 : // left single quotation mark
823                     case 0x2019 : // right single quotation mark
824                     case 0x201A : // single low-9 quotation mark
825                         c = '\'';
826                         break;
827                     case 0x201C : // left double quotation mark
828                     case 0x201D : // right double quotation mark
829                     case 0x201E : // double low-9 quotation mark
830                         c = '"';
831                         break;
832                 }
833             }
834         }
835 
836         // don't map latin-1 chars to entities
837         if (this.configuration.getOutCharEncoding() == Configuration.LATIN1)
838         {
839             if (c > 255) /* multi byte chars */
840             {
841                 if (!this.configuration.numEntities)
842                 {
843                     entity = EntityTable.getDefaultEntityTable().entityName((short) c);
844                     if (entity != null)
845                     {
846                         entity = "&" + entity + ";";
847                     }
848                     else
849                     {
850                         entity = "&#" + c + ";";
851                     }
852                 }
853                 else
854                 {
855                     entity = "&#" + c + ";";
856                 }
857 
858                 for (int i = 0; i < entity.length(); i++)
859                 {
860                     addC(entity.charAt(i), linelen++);
861                 }
862 
863                 return;
864             }
865 
866             if (c > 126 && c < 160)
867             {
868                 entity = "&#" + c + ";";
869 
870                 for (int i = 0; i < entity.length(); i++)
871                 {
872                     addC(entity.charAt(i), linelen++);
873                 }
874 
875                 return;
876             }
877 
878             addC(c, linelen++);
879             return;
880         }
881 
882         // don't map utf8 or utf16 chars to entities
883         if (this.configuration.getOutCharEncoding() == Configuration.UTF8
884             || this.configuration.getOutCharEncoding() == Configuration.UTF16
885             || this.configuration.getOutCharEncoding() == Configuration.UTF16LE
886             || this.configuration.getOutCharEncoding() == Configuration.UTF16BE)
887         {
888             addC(c, linelen++);
889             return;
890         }
891 
892         // use numeric entities only for XML
893         if (this.configuration.xmlTags)
894         {
895             // if ASCII use numeric entities for chars > 127
896             if (c > 127 && this.configuration.getOutCharEncoding() == Configuration.ASCII)
897             {
898                 entity = "&#" + c + ";";
899 
900                 for (int i = 0; i < entity.length(); i++)
901                 {
902                     addC(entity.charAt(i), linelen++);
903                 }
904 
905                 return;
906             }
907 
908             // otherwise output char raw
909             addC(c, linelen++);
910             return;
911         }
912 
913         // default treatment for ASCII
914         if (this.configuration.getOutCharEncoding() == Configuration.ASCII && (c > 126 || (c < ' ' && c != '\t')))
915         {
916             if (!this.configuration.numEntities)
917             {
918                 entity = EntityTable.getDefaultEntityTable().entityName((short) c);
919                 if (entity != null)
920                 {
921                     entity = "&" + entity + ";";
922                 }
923                 else
924                 {
925                     entity = "&#" + c + ";";
926                 }
927             }
928             else
929             {
930                 entity = "&#" + c + ";";
931             }
932 
933             for (int i = 0; i < entity.length(); i++)
934             {
935                 addC(entity.charAt(i), linelen++);
936             }
937 
938             return;
939         }
940 
941         addC(c, linelen++);
942     }
943 
944     /***
945      * The line buffer is uint not char so we can hold Unicode values unencoded. The translation to UTF-8 is deferred to
946      * the outc routine called to flush the line buffer.
947      * @param fout
948      * @param mode
949      * @param indent
950      * @param textarray
951      * @param start
952      * @param end
953      */
954     private void printText(Out fout, short mode, int indent, byte[] textarray, int start, int end)
955     {
956         int i, c;
957         int[] ci = new int[1];
958 
959         for (i = start; i < end; ++i)
960         {
961             if (indent + linelen >= this.configuration.wraplen)
962             {
963                 wrapLine(fout, indent);
964             }
965 
966             c = (textarray[i]) & 0xFF; // Convert to unsigned.
967 
968             // look for UTF-8 multibyte character
969             if (c > 0x7F)
970             {
971                 i += getUTF8(textarray, i, ci);
972                 c = ci[0];
973             }
974 
975             if (c == '\n')
976             {
977                 flushLine(fout, indent);
978                 continue;
979             }
980 
981             printChar(c, mode);
982         }
983     }
984 
985     /***
986      * @param str
987      */
988     private void printString(String str)
989     {
990         for (int i = 0; i < str.length(); i++)
991         {
992             addC(str.charAt(i), linelen++);
993         }
994     }
995 
996     /***
997      * @param fout
998      * @param indent
999      * @param value
1000      * @param delim
1001      * @param wrappable
1002      */
1003     private void printAttrValue(Out fout, int indent, String value, int delim, boolean wrappable)
1004     {
1005         int c;
1006         int[] ci = new int[1];
1007         boolean wasinstring = false;
1008         byte[] valueChars = null;
1009         int i;
1010         short mode = (wrappable ? (short) (NORMAL | ATTRIBVALUE) : (short) (PREFORMATTED | ATTRIBVALUE));
1011 
1012         if (value != null)
1013         {
1014             valueChars = TidyUtils.getBytes(value);
1015         }
1016 
1017         // look for ASP, Tango or PHP instructions for computed attribute value
1018         if (valueChars != null && valueChars.length >= 5 && valueChars[0] == '<')
1019         {
1020             if (valueChars[1] == '%' || valueChars[1] == '@' || (new String(valueChars, 0, 5)).equals("<?php"))
1021             {
1022                 mode |= CDATA;
1023             }
1024         }
1025 
1026         if (delim == 0)
1027         {
1028             delim = '"';
1029         }
1030 
1031         addC('=', linelen++);
1032 
1033         // don't wrap after "=" for xml documents
1034         if (!this.configuration.xmlOut)
1035         {
1036 
1037             if (indent + linelen < this.configuration.wraplen)
1038             {
1039                 wraphere = linelen;
1040             }
1041 
1042             if (indent + linelen >= this.configuration.wraplen)
1043             {
1044                 wrapLine(fout, indent);
1045             }
1046 
1047             if (indent + linelen < this.configuration.wraplen)
1048             {
1049                 wraphere = linelen;
1050             }
1051             else
1052             {
1053                 condFlushLine(fout, indent);
1054             }
1055         }
1056 
1057         addC(delim, linelen++);
1058 
1059         if (value != null)
1060         {
1061             inString = false;
1062 
1063             i = 0;
1064             while (i < valueChars.length)
1065             {
1066                 c = (valueChars[i]) & 0xFF; // Convert to unsigned.
1067 
1068                 if (wrappable && c == ' ' && indent + linelen < this.configuration.wraplen)
1069                 {
1070                     wraphere = linelen;
1071                     wasinstring = inString;
1072                 }
1073 
1074                 if (wrappable && wraphere > 0 && indent + linelen >= this.configuration.wraplen)
1075                 {
1076                     wrapAttrVal(fout, indent, wasinstring);
1077                 }
1078 
1079                 if (c == delim)
1080                 {
1081                     String entity;
1082 
1083                     entity = (c == '"' ? "&quot;" : "&#39;");
1084 
1085                     for (int j = 0; j < entity.length(); j++)
1086                     {
1087                         addC(entity.charAt(j), linelen++);
1088                     }
1089 
1090                     ++i;
1091                     continue;
1092                 }
1093                 else if (c == '"')
1094                 {
1095                     if (this.configuration.quoteMarks)
1096                     {
1097                         addC('&', linelen++);
1098                         addC('q', linelen++);
1099                         addC('u', linelen++);
1100                         addC('o', linelen++);
1101                         addC('t', linelen++);
1102                         addC(';', linelen++);
1103                     }
1104                     else
1105                     {
1106                         addC('"', linelen++);
1107                     }
1108 
1109                     if (delim == '\'')
1110                     {
1111                         inString = !inString;
1112                     }
1113 
1114                     ++i;
1115                     continue;
1116                 }
1117                 else if (c == '\'')
1118                 {
1119                     if (this.configuration.quoteMarks)
1120                     {
1121                         addC('&', linelen++);
1122                         addC('#', linelen++);
1123                         addC('3', linelen++);
1124                         addC('9', linelen++);
1125                         addC(';', linelen++);
1126                     }
1127                     else
1128                     {
1129                         addC('\'', linelen++);
1130                     }
1131 
1132                     if (delim == '"')
1133                     {
1134                         inString = !inString;
1135                     }
1136 
1137                     ++i;
1138                     continue;
1139                 }
1140 
1141                 // look for UTF-8 multibyte character
1142                 if (c > 0x7F)
1143                 {
1144                     i += getUTF8(valueChars, i, ci);
1145                     c = ci[0];
1146                 }
1147 
1148                 ++i;
1149 
1150                 if (c == '\n')
1151                 {
1152                     flushLine(fout, indent);
1153                     continue;
1154                 }
1155 
1156                 printChar(c, mode);
1157             }
1158         }
1159 
1160         inString = false;
1161         addC(delim, linelen++);
1162     }
1163 
1164     /***
1165      * @param fout
1166      * @param indent
1167      * @param node
1168      * @param attr
1169      */
1170     private void printAttribute(Out fout, int indent, Node node, AttVal attr)
1171     {
1172         String name;
1173         boolean wrappable = false;
1174 
1175         if (this.configuration.indentAttributes)
1176         {
1177             flushLine(fout, indent);
1178             indent += this.configuration.spaces;
1179         }
1180 
1181         name = attr.attribute;
1182 
1183         if (indent + linelen >= this.configuration.wraplen)
1184         {
1185             wrapLine(fout, indent);
1186         }
1187 
1188         if (!this.configuration.xmlTags && !this.configuration.xmlOut && attr.dict != null)
1189         {
1190             if (AttributeTable.getDefaultAttributeTable().isScript(name))
1191             {
1192                 wrappable = this.configuration.wrapScriptlets;
1193             }
1194             else if (!attr.dict.isNowrap() && this.configuration.wrapAttVals)
1195             {
1196                 wrappable = true;
1197             }
1198         }
1199 
1200         if (indent + linelen < this.configuration.wraplen)
1201         {
1202             wraphere = linelen;
1203             addC(' ', linelen++);
1204         }
1205         else
1206         {
1207             condFlushLine(fout, indent);
1208             addC(' ', linelen++);
1209         }
1210 
1211         for (int i = 0; i < name.length(); i++)
1212         {
1213             addC(
1214                 TidyUtils.foldCase(name.charAt(i), this.configuration.upperCaseAttrs, this.configuration.xmlTags),
1215                 linelen++);
1216         }
1217 
1218         if (indent + linelen >= this.configuration.wraplen)
1219         {
1220             wrapLine(fout, indent);
1221         }
1222 
1223         if (attr.value == null)
1224         {
1225             if (this.configuration.xmlTags || this.configuration.xmlOut)
1226             {
1227                 printAttrValue(fout, indent, (attr.isBoolAttribute() ? attr.attribute : ""), attr.delim, true);
1228             }
1229             else if (!attr.isBoolAttribute() && node != null && !node.isNewNode())
1230             {
1231                 printAttrValue(fout, indent, "", attr.delim, true);
1232             }
1233             else if (indent + linelen < this.configuration.wraplen)
1234             {
1235                 wraphere = linelen;
1236             }
1237 
1238         }
1239         else
1240         {
1241             printAttrValue(fout, indent, attr.value, attr.delim, wrappable);
1242         }
1243     }
1244 
1245     /***
1246      * @param fout
1247      * @param indent
1248      * @param node
1249      * @param attr
1250      */
1251     private void printAttrs(Out fout, int indent, Node node, AttVal attr)
1252     {
1253         // add xml:space attribute to pre and other elements
1254         if (configuration.xmlOut
1255             && configuration.xmlSpace
1256             && ParserImpl.XMLPreserveWhiteSpace(node, configuration.tt)
1257             && node.getAttrByName("xml:space") == null)
1258         {
1259             node.addAttribute("xml:space", "preserve");
1260             if (attr != null)
1261             {
1262                 attr = node.attributes;
1263             }
1264         }
1265 
1266         if (attr != null)
1267         {
1268             if (attr.next != null)
1269             {
1270                 printAttrs(fout, indent, node, attr.next);
1271             }
1272 
1273             if (attr.attribute != null)
1274             {
1275                 Attribute attribute = attr.dict;
1276 
1277                 if (!this.configuration.dropProprietaryAttributes
1278                     || !(attribute == null || TidyUtils.toBoolean(attribute.getVersions() & Dict.VERS_PROPRIETARY)))
1279                 {
1280                     printAttribute(fout, indent, node, attr);
1281                 }
1282             }
1283             else if (attr.asp != null)
1284             {
1285                 addC(' ', linelen++);
1286                 printAsp(fout, indent, attr.asp);
1287             }
1288             else if (attr.php != null)
1289             {
1290                 addC(' ', linelen++);
1291                 printPhp(fout, indent, attr.php);
1292             }
1293         }
1294 
1295     }
1296 
1297     /***
1298      * Line can be wrapped immediately after inline start tag provided if follows a text node ending in a space, or it
1299      * parent is an inline element that that rule applies to. This behaviour was reverse engineered from Netscape 3.0
1300      * @param node current Node
1301      * @return <code>true</code> if the current char follows a space
1302      */
1303     private static boolean afterSpace(Node node)
1304     {
1305         Node prev;
1306         int c;
1307 
1308         if (node == null || node.tag == null || !TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
1309         {
1310             return true;
1311         }
1312 
1313         prev = node.prev;
1314 
1315         if (prev != null)
1316         {
1317             if (prev.type == Node.TEXT_NODE && prev.end > prev.start)
1318             {
1319                 c = (prev.textarray[prev.end - 1]) & 0xFF; // Convert to unsigned.
1320 
1321                 if (c == 160 || c == ' ' || c == '\n')
1322                 {
1323                     return true;
1324                 }
1325             }
1326 
1327             return false;
1328         }
1329 
1330         return afterSpace(node.parent);
1331     }
1332 
1333     /***
1334      * @param lexer
1335      * @param fout
1336      * @param mode
1337      * @param indent
1338      * @param node
1339      */
1340     private void printTag(Lexer lexer, Out fout, short mode, int indent, Node node)
1341     {
1342         String p;
1343         TagTable tt = this.configuration.tt;
1344 
1345         addC('<', linelen++);
1346 
1347         if (node.type == Node.END_TAG)
1348         {
1349             addC('/', linelen++);
1350         }
1351 
1352         p = node.element;
1353         for (int i = 0; i < p.length(); i++)
1354         {
1355             addC(
1356                 TidyUtils.foldCase(p.charAt(i), this.configuration.upperCaseTags, this.configuration.xmlTags),
1357                 linelen++);
1358         }
1359 
1360         printAttrs(fout, indent, node, node.attributes);
1361 
1362         if ((this.configuration.xmlOut || this.configuration.xHTML)
1363             && (node.type == Node.START_END_TAG || TidyUtils.toBoolean(node.tag.model & Dict.CM_EMPTY)))
1364         {
1365             addC(' ', linelen++); // Space is NS compatibility hack <br />
1366             addC('/', linelen++); // Required end tag marker
1367         }
1368 
1369         addC('>', linelen++);
1370 
1371         if ((node.type != Node.START_END_TAG || configuration.xHTML) && !TidyUtils.toBoolean(mode & PREFORMATTED))
1372         {
1373             if (indent + linelen >= this.configuration.wraplen)
1374             {
1375                 wrapLine(fout, indent);
1376             }
1377 
1378             if (indent + linelen < this.configuration.wraplen)
1379             {
1380 
1381                 // wrap after start tag if is <br/> or if it's not inline
1382                 // fix for [514348]
1383                 if (!TidyUtils.toBoolean(mode & NOWRAP)
1384                     && (!TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE) || (node.tag == tt.tagBr))
1385                     && afterSpace(node))
1386                 {
1387                     wraphere = linelen;
1388                 }
1389 
1390             }
1391         }
1392         else
1393         {
1394             condFlushLine(fout, indent);
1395         }
1396 
1397     }
1398 
1399     /***
1400      * @param mode
1401      * @param indent
1402      * @param node
1403      */
1404     private void printEndTag(short mode, int indent, Node node)
1405     {
1406         String p;
1407 
1408         // Netscape ignores SGML standard by not ignoring a line break before </A> or </U> etc.
1409         // To avoid rendering this as an underlined space, I disable line wrapping before inline end tags
1410 
1411         // if (indent + linelen < this.configuration.wraplen && !TidyUtils.toBoolean(mode & NOWRAP))
1412         // {
1413         //     wraphere = linelen;
1414         // }
1415 
1416         addC('<', linelen++);
1417         addC('/', linelen++);
1418 
1419         p = node.element;
1420         for (int i = 0; i < p.length(); i++)
1421         {
1422             addC(
1423                 TidyUtils.foldCase(p.charAt(i), this.configuration.upperCaseTags, this.configuration.xmlTags),
1424                 linelen++);
1425         }
1426 
1427         addC('>', linelen++);
1428     }
1429 
1430     /***
1431      * @param fout
1432      * @param indent
1433      * @param node
1434      */
1435     private void printComment(Out fout, int indent, Node node)
1436     {
1437         if (this.configuration.hideComments)
1438         {
1439             return;
1440         }
1441 
1442         if (indent + linelen < this.configuration.wraplen)
1443         {
1444             wraphere = linelen;
1445         }
1446 
1447         addC('<', linelen++);
1448         addC('!', linelen++);
1449         addC('-', linelen++);
1450         addC('-', linelen++);
1451 
1452         printText(fout, COMMENT, indent, node.textarray, node.start, node.end);
1453 
1454         // See Lexer.java: AQ 8Jul2000
1455         addC('-', linelen++);
1456         addC('-', linelen++);
1457         addC('>', linelen++);
1458 
1459         if (node.linebreak)
1460         {
1461             flushLine(fout, indent);
1462         }
1463     }
1464 
1465     /***
1466      * @param fout
1467      * @param indent
1468      * @param lexer
1469      * @param node
1470      */
1471     private void printDocType(Out fout, int indent, Lexer lexer, Node node)
1472     {
1473         int i, c = 0;
1474         short mode = 0;
1475         boolean q = this.configuration.quoteMarks;
1476 
1477         this.configuration.quoteMarks = false;
1478 
1479         if (indent + linelen < this.configuration.wraplen)
1480         {
1481             wraphere = linelen;
1482         }
1483 
1484         condFlushLine(fout, indent);
1485 
1486         addC('<', linelen++);
1487         addC('!', linelen++);
1488         addC('D', linelen++);
1489         addC('O', linelen++);
1490         addC('C', linelen++);
1491         addC('T', linelen++);
1492         addC('Y', linelen++);
1493         addC('P', linelen++);
1494         addC('E', linelen++);
1495         addC(' ', linelen++);
1496 
1497         if (indent + linelen < this.configuration.wraplen)
1498         {
1499             wraphere = linelen;
1500         }
1501 
1502         for (i = node.start; i < node.end; ++i)
1503         {
1504             if (indent + linelen >= this.configuration.wraplen)
1505             {
1506                 wrapLine(fout, indent);
1507             }
1508 
1509             c = node.textarray[i] & 0xFF; // Convert to unsigned.
1510 
1511             // inDTDSubset?
1512             if (TidyUtils.toBoolean(mode & CDATA))
1513             {
1514                 if (c == ']')
1515                 {
1516                     mode &= ~CDATA;
1517                 }
1518             }
1519             else if (c == '[')
1520             {
1521                 mode |= CDATA;
1522             }
1523             int[] ci = new int[1];
1524 
1525             // look for UTF-8 multibyte character
1526             if (c > 0x7F)
1527             {
1528                 i += getUTF8(node.textarray, i, ci);
1529                 c = ci[0];
1530             }
1531 
1532             if (c == '\n')
1533             {
1534                 flushLine(fout, indent);
1535                 continue;
1536             }
1537 
1538             printChar(c, mode);
1539         }
1540 
1541         if (linelen < this.configuration.wraplen)
1542         {
1543             wraphere = linelen;
1544         }
1545 
1546         addC('>', linelen++);
1547         this.configuration.quoteMarks = q;
1548         condFlushLine(fout, indent);
1549     }
1550 
1551     /***
1552      * @param fout
1553      * @param indent
1554      * @param node
1555      */
1556     private void printPI(Out fout, int indent, Node node)
1557     {
1558         if (indent + linelen < this.configuration.wraplen)
1559         {
1560             wraphere = linelen;
1561         }
1562 
1563         addC('<', linelen++);
1564         addC('?', linelen++);
1565 
1566         // set CDATA to pass < and > unescaped
1567         printText(fout, CDATA, indent, node.textarray, node.start, node.end);
1568 
1569         if (node.end <= 0 || node.textarray[node.end - 1] != '?') // #542029 - fix by Terry Teague 10 Apr 02
1570         {
1571             addC('?', linelen++);
1572         }
1573 
1574         addC('>', linelen++);
1575         condFlushLine(fout, indent);
1576     }
1577 
1578     /***
1579      * Pretty print the xml declaration.
1580      * @param fout
1581      * @param indent
1582      * @param node
1583      */
1584     private void printXmlDecl(Out fout, int indent, Node node)
1585     {
1586         if (indent + linelen < this.configuration.wraplen)
1587         {
1588             wraphere = linelen;
1589         }
1590 
1591         addC('<', linelen++);
1592         addC('?', linelen++);
1593         addC('x', linelen++);
1594         addC('m', linelen++);
1595         addC('l', linelen++);
1596 
1597         printAttrs(fout, indent, node, node.attributes);
1598 
1599         if (node.end <= 0 || node.textarray[node.end - 1] != '?') // #542029 - fix by Terry Teague 10 Apr 02
1600         {
1601             addC('?', linelen++);
1602         }
1603 
1604         addC('>', linelen++);
1605 
1606         condFlushLine(fout, indent);
1607     }
1608 
1609     /***
1610      * note ASP and JSTE share <% ... %> syntax.
1611      * @param fout
1612      * @param indent
1613      * @param node
1614      */
1615     private void printAsp(Out fout, int indent, Node node)
1616     {
1617         int savewraplen = this.configuration.wraplen;
1618 
1619         // disable wrapping if so requested
1620 
1621         if (!this.configuration.wrapAsp || !this.configuration.wrapJste)
1622         {
1623             this.configuration.wraplen = 0xFFFFFF; // a very large number
1624         }
1625 
1626         addC('<', linelen++);
1627         addC('%', linelen++);
1628 
1629         printText(fout, (this.configuration.wrapAsp ? CDATA : COMMENT), indent, node.textarray, node.start, node.end);
1630 
1631         addC('%', linelen++);
1632         addC('>', linelen++);
1633         /* condFlushLine(fout, indent); */
1634         this.configuration.wraplen = savewraplen;
1635     }
1636 
1637     /***
1638      * JSTE also supports <# ... #> syntax
1639      * @param fout
1640      * @param indent
1641      * @param node
1642      */
1643     private void printJste(Out fout, int indent, Node node)
1644     {
1645         int savewraplen = this.configuration.wraplen;
1646 
1647         // disable wrapping if so requested
1648 
1649         if (!this.configuration.wrapJste)
1650         {
1651             this.configuration.wraplen = 0xFFFFFF; // a very large number
1652         }
1653 
1654         addC('<', linelen++);
1655         addC('#', linelen++);
1656 
1657         printText(fout, (this.configuration.wrapJste ? CDATA : COMMENT), indent, node.textarray, node.start, node.end);
1658 
1659         addC('#', linelen++);
1660         addC('>', linelen++);
1661         // condFlushLine(fout, indent);
1662         this.configuration.wraplen = savewraplen;
1663     }
1664 
1665     /***
1666      * PHP is based on XML processing instructions.
1667      * @param fout
1668      * @param indent
1669      * @param node
1670      */
1671     private void printPhp(Out fout, int indent, Node node)
1672     {
1673         int savewraplen = this.configuration.wraplen;
1674 
1675         // disable wrapping if so requested
1676 
1677         if (!this.configuration.wrapPhp)
1678         {
1679             this.configuration.wraplen = 0xFFFFFF; // a very large number
1680         }
1681 
1682         addC('<', linelen++);
1683         addC('?', linelen++);
1684 
1685         printText(fout, (this.configuration.wrapPhp ? CDATA : COMMENT), indent, node.textarray, node.start, node.end);
1686 
1687         addC('?', linelen++);
1688         addC('>', linelen++);
1689         // PCondFlushLine(fout, indent);
1690         this.configuration.wraplen = savewraplen;
1691     }
1692 
1693     /***
1694      * @param fout
1695      * @param indent
1696      * @param node
1697      */
1698     private void printCDATA(Out fout, int indent, Node node)
1699     {
1700         int savewraplen = this.configuration.wraplen;
1701 
1702         if (!this.configuration.indentCdata)
1703         {
1704             indent = 0;
1705         }
1706 
1707         condFlushLine(fout, indent);
1708 
1709         // disable wrapping
1710         this.configuration.wraplen = 0xFFFFFF; // a very large number
1711 
1712         addC('<', linelen++);
1713         addC('!', linelen++);
1714         addC('[', linelen++);
1715         addC('C', linelen++);
1716         addC('D', linelen++);
1717         addC('A', linelen++);
1718         addC('T', linelen++);
1719         addC('A', linelen++);
1720         addC('[', linelen++);
1721 
1722         printText(fout, COMMENT, indent, node.textarray, node.start, node.end);
1723 
1724         addC(']', linelen++);
1725         addC(']', linelen++);
1726         addC('>', linelen++);
1727         condFlushLine(fout, indent);
1728         this.configuration.wraplen = savewraplen;
1729     }
1730 
1731     /***
1732      * @param fout
1733      * @param indent
1734      * @param node
1735      */
1736     private void printSection(Out fout, int indent, Node node)
1737     {
1738         int savewraplen = this.configuration.wraplen;
1739 
1740         // disable wrapping if so requested
1741 
1742         if (!this.configuration.wrapSection)
1743         {
1744             this.configuration.wraplen = 0xFFFFFF; // a very large number
1745         }
1746 
1747         addC('<', linelen++);
1748         addC('!', linelen++);
1749         addC('[', linelen++);
1750 
1751         printText(
1752             fout,
1753             (this.configuration.wrapSection ? CDATA : COMMENT),
1754             indent,
1755             node.textarray,
1756             node.start,
1757             node.end);
1758 
1759         addC(']', linelen++);
1760         addC('>', linelen++);
1761         // PCondFlushLine(fout, indent);
1762         this.configuration.wraplen = savewraplen;
1763     }
1764 
1765     /***
1766      * Is the current node inside HEAD?
1767      * @param node Node
1768      * @return <code>true</code> if node is inside an HEAD tag
1769      */
1770     private boolean insideHead(Node node)
1771     {
1772         if (node.tag == this.configuration.tt.tagHead)
1773         {
1774             return true;
1775         }
1776 
1777         if (node.parent != null)
1778         {
1779             return insideHead(node.parent);
1780         }
1781         return false;
1782     }
1783 
1784     /***
1785      * Is text node and already ends w/ a newline? Used to pretty print CDATA/PRE text content. If it already ends on a
1786      * newline, it is not necessary to print another before printing end tag.
1787      * @param lexer Lexer
1788      * @param node text node
1789      * @return text indent
1790      */
1791     private int textEndsWithNewline(Lexer lexer, Node node)
1792     {
1793         if (node.type == Node.TEXT_NODE && node.end > node.start)
1794         {
1795             int ch, ix = node.end - 1;
1796             // Skip non-newline whitespace
1797             while (ix >= node.start
1798                 && TidyUtils.toBoolean(ch = (node.textarray[ix] & 0xff))
1799                 && (ch == ' ' || ch == '\t' || ch == '\r'))
1800             {
1801                 --ix;
1802             }
1803 
1804             if (node.textarray[ix] == '\n')
1805             {
1806                 return node.end - ix - 1; // #543262 tidy eats all memory
1807             }
1808         }
1809         return -1;
1810     }
1811 
1812     /***
1813      * Does the current node contain a CDATA section?
1814      * @param lexer Lexer
1815      * @param node Node
1816      * @return <code>true</code> if node contains a CDATA section
1817      */
1818     static boolean hasCDATA(Lexer lexer, Node node)
1819     {
1820         // Scan forward through the textarray. Since the characters we're
1821         // looking for are < 0x7f, we don't have to do any UTF-8 decoding.
1822 
1823         if (node.type != Node.TEXT_NODE)
1824         {
1825             return false;
1826         }
1827 
1828         int len = node.end - node.start + 1;
1829         String start = TidyUtils.getString(node.textarray, node.start, len);
1830 
1831         int indexOfCData = start.indexOf(CDATA_START);
1832         return indexOfCData > -1 && indexOfCData <= len;
1833     }
1834 
1835     /***
1836      * Print script and style elements. For XHTML, wrap the content as follows:
1837      * 
1838      * <pre>
1839      *     JavaScript:
1840      *         //&lt;![CDATA[
1841      *             content
1842      *         //]]>
1843      *     VBScript:
1844      *         '&lt;![CDATA[
1845      *             content
1846      *         ']]>
1847      *     CSS:
1848      *         /*&lt;![CDATA[* /
1849      *             content
1850      *         /*]]>* /
1851      *     other:
1852      *        &lt;![CDATA[
1853      *             content
1854      *         ]]>
1855      * </pre>
1856      * 
1857      * @param fout
1858      * @param mode
1859      * @param indent
1860      * @param lexer
1861      * @param node
1862      */
1863     private void printScriptStyle(Out fout, short mode, int indent, Lexer lexer, Node node)
1864     {
1865         Node content;
1866         String commentStart = DEFAULT_COMMENT_START;
1867         String commentEnd = DEFAULT_COMMENT_END;
1868         boolean hasCData = false;
1869         int contentIndent = -1;
1870 
1871         if (insideHead(node))
1872         {
1873             // flushLine(fout, indent);
1874         }
1875 
1876         indent = 0;
1877 
1878         // start script
1879         printTag(lexer, fout, mode, indent, node);
1880         // flushLine(fout, indent); // extra newline
1881 
1882         if (lexer.configuration.xHTML && node.content != null)
1883         {
1884             AttVal type = node.getAttrByName("type");
1885             if (type != null)
1886             {
1887                 if ("text/javascript".equalsIgnoreCase(type.value))
1888                 {
1889                     commentStart = JS_COMMENT_START;
1890                     commentEnd = JS_COMMENT_END;
1891                 }
1892                 else if ("text/css".equalsIgnoreCase(type.value))
1893                 {
1894                     commentStart = CSS_COMMENT_START;
1895                     commentEnd = CSS_COMMENT_END;
1896                 }
1897                 else if ("text/vbscript".equalsIgnoreCase(type.value))
1898                 {
1899                     commentStart = VB_COMMENT_START;
1900                     commentEnd = VB_COMMENT_END;
1901                 }
1902             }
1903 
1904             hasCData = hasCDATA(lexer, node.content);
1905             if (!hasCData)
1906             {
1907                 // disable wrapping
1908                 int savewraplen = lexer.configuration.wraplen;
1909                 lexer.configuration.wraplen = 0xFFFFFF; // a very large number
1910 
1911                 linelen = addAsciiString(commentStart, linelen);
1912                 linelen = addAsciiString(CDATA_START, linelen);
1913                 linelen = addAsciiString(commentEnd, linelen);
1914                 condFlushLine(fout, indent);
1915 
1916                 // restore wrapping
1917                 lexer.configuration.wraplen = savewraplen;
1918             }
1919         }
1920 
1921         for (content = node.content; content != null; content = content.next)
1922         {
1923             printTree(fout, (short) (mode | PREFORMATTED | NOWRAP | CDATA), 0, lexer, content);
1924 
1925             if (content.next == null)
1926             {
1927                 contentIndent = textEndsWithNewline(lexer, content);
1928             }
1929 
1930         }
1931 
1932         if (contentIndent < 0)
1933         {
1934             condFlushLine(fout, indent);
1935             contentIndent = 0;
1936         }
1937 
1938         if (lexer.configuration.xHTML && node.content != null)
1939         {
1940             if (!hasCData)
1941             {
1942                 // disable wrapping
1943                 int ix, savewraplen = lexer.configuration.wraplen;
1944                 lexer.configuration.wraplen = 0xFFFFFF; // a very large number
1945 
1946                 // Add spaces to last text node to align w/ indent
1947                 if (contentIndent > 0 && linelen < contentIndent)
1948                 {
1949                     linelen = contentIndent;
1950                 }
1951                 for (ix = 0; contentIndent < indent && ix < indent - contentIndent; ++ix)
1952                 {
1953                     addC(' ', linelen++);
1954                 }
1955 
1956                 linelen = addAsciiString(commentStart, linelen);
1957                 linelen = addAsciiString(CDATA_END, linelen);
1958                 linelen = addAsciiString(commentEnd, linelen);
1959 
1960                 // restore wrapping
1961                 lexer.configuration.wraplen = savewraplen;
1962                 condFlushLine(fout, 0);
1963             }
1964         }
1965 
1966         printEndTag(mode, indent, node);
1967 
1968         if (!lexer.configuration.indentContent && node.next != null
1969 
1970         && !((node.tag != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
1971 
1972         || node.type != Node.TEXT_NODE
1973 
1974         ))
1975         {
1976             flushLine(fout, indent);
1977         }
1978 
1979         flushLine(fout, indent);
1980     }
1981 
1982     /***
1983      * Should tidy indent the give tag?
1984      * @param node actual node
1985      * @return <code>true</code> if line should be indented
1986      */
1987     private boolean shouldIndent(Node node)
1988     {
1989         TagTable tt = this.configuration.tt;
1990 
1991         if (!this.configuration.indentContent)
1992         {
1993             return false;
1994         }
1995 
1996         if (this.configuration.smartIndent)
1997         {
1998             if (node.content != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_NO_INDENT))
1999             {
2000                 for (node = node.content; node != null; node = node.next)
2001                 {
2002                     if (node.tag != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_BLOCK))
2003                     {
2004                         return true;
2005                     }
2006                 }
2007 
2008                 return false;
2009             }
2010 
2011             if (TidyUtils.toBoolean(node.tag.model & Dict.CM_HEADING))
2012             {
2013                 return false;
2014             }
2015 
2016             if (node.tag == tt.tagP)
2017             {
2018                 return false;
2019             }
2020 
2021             if (node.tag == tt.tagTitle)
2022             {
2023                 return false;
2024             }
2025         }
2026 
2027         if (TidyUtils.toBoolean(node.tag.model & (Dict.CM_FIELD | Dict.CM_OBJECT)))
2028         {
2029             return true;
2030         }
2031 
2032         if (node.tag == tt.tagMap)
2033         {
2034             return true;
2035         }
2036 
2037         return !TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE);
2038     }
2039 
2040     /***
2041      * Print just the content of the body element. Useful when you want to reuse material from other documents.
2042      * @param fout
2043      * @param lexer
2044      * @param root
2045      * @param xml
2046      */
2047     void printBody(Out fout, Lexer lexer, Node root, boolean xml)
2048     {
2049         if (root == null)
2050         {
2051             return;
2052         }
2053 
2054         // Feature request #434940 - fix by Dave Raggett/Ignacio Vazquez-Abrams 21 Jun 01
2055         // Sebastiano Vigna <vigna@dsi.unimi.it>
2056         Node body = root.findBody(lexer.configuration.tt);
2057 
2058         if (body != null)
2059         {
2060             Node content;
2061             for (content = body.content; content != null; content = content.next)
2062             {
2063                 if (xml)
2064                 {
2065                     printXMLTree(fout, (short) 0, 0, lexer, content);
2066                 }
2067                 else
2068                 {
2069                     printTree(fout, (short) 0, 0, lexer, content);
2070                 }
2071             }
2072         }
2073     }
2074 
2075     /***
2076      * @param fout
2077      * @param mode
2078      * @param indent
2079      * @param lexer
2080      * @param node
2081      */
2082     public void printTree(Out fout, short mode, int indent, Lexer lexer, Node node)
2083     {
2084         Node content, last;
2085         TagTable tt = this.configuration.tt;
2086 
2087         if (node == null)
2088         {
2089             return;
2090         }
2091 
2092         if (node.type == Node.TEXT_NODE || (node.type == Node.CDATA_TAG && lexer.configuration.escapeCdata))
2093         {
2094             printText(fout, mode, indent, node.textarray, node.start, node.end);
2095         }
2096         else if (node.type == Node.COMMENT_TAG)
2097         {
2098             printComment(fout, indent, node);
2099         }
2100         else if (node.type == Node.ROOT_NODE)
2101         {
2102             for (content = node.content; content != null; content = content.next)
2103             {
2104                 printTree(fout, mode, indent, lexer, content);
2105             }
2106         }
2107         else if (node.type == Node.DOCTYPE_TAG)
2108         {
2109             printDocType(fout, indent, lexer, node);
2110         }
2111         else if (node.type == Node.PROC_INS_TAG)
2112         {
2113             printPI(fout, indent, node);
2114         }
2115         else if (node.type == Node.XML_DECL)
2116         {
2117             printXmlDecl(fout, indent, node);
2118         }
2119         else if (node.type == Node.CDATA_TAG)
2120         {
2121             printCDATA(fout, indent, node);
2122         }
2123         else if (node.type == Node.SECTION_TAG)
2124         {
2125             printSection(fout, indent, node);
2126         }
2127         else if (node.type == Node.ASP_TAG)
2128         {
2129             printAsp(fout, indent, node);
2130         }
2131         else if (node.type == Node.JSTE_TAG)
2132         {
2133             printJste(fout, indent, node);
2134         }
2135         else if (node.type == Node.PHP_TAG)
2136         {
2137             printPhp(fout, indent, node);
2138         }
2139         else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_EMPTY)
2140             || (node.type == Node.START_END_TAG && !configuration.xHTML))
2141         {
2142             if (!TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
2143             {
2144                 condFlushLine(fout, indent);
2145             }
2146 
2147             if (node.tag == tt.tagBr
2148                 && node.prev != null
2149                 && node.prev.tag != tt.tagBr
2150                 && this.configuration.breakBeforeBR)
2151             {
2152                 flushLine(fout, indent);
2153             }
2154 
2155             if (this.configuration.makeClean && node.tag == tt.tagWbr)
2156             {
2157                 printString(" ");
2158             }
2159             else
2160             {
2161                 printTag(lexer, fout, mode, indent, node);
2162             }
2163 
2164             if (node.tag == tt.tagParam || node.tag == tt.tagArea)
2165             {
2166                 condFlushLine(fout, indent);
2167             }
2168             else if (node.tag == tt.tagBr || node.tag == tt.tagHr)
2169             {
2170                 flushLine(fout, indent);
2171             }
2172         }
2173         else
2174         {
2175             if (node.type == Node.START_END_TAG)
2176             {
2177                 node.type = Node.START_TAG;
2178             }
2179 
2180             // some kind of container element
2181             if (node.tag != null && node.tag.getParser() == ParserImpl.PRE)
2182             {
2183                 condFlushLine(fout, indent);
2184 
2185                 indent = 0;
2186                 condFlushLine(fout, indent);
2187                 printTag(lexer, fout, mode, indent, node);
2188                 flushLine(fout, indent);
2189 
2190                 for (content = node.content; content != null; content = content.next)
2191                 {
2192                     printTree(fout, (short) (mode | PREFORMATTED | NOWRAP), indent, lexer, content);
2193                 }
2194 
2195                 condFlushLine(fout, indent);
2196                 printEndTag(mode, indent, node);
2197                 flushLine(fout, indent);
2198 
2199                 if (!this.configuration.indentContent && node.next != null)
2200                 {
2201                     flushLine(fout, indent);
2202                 }
2203             }
2204             else if (node.tag == tt.tagStyle || node.tag == tt.tagScript)
2205             {
2206                 printScriptStyle(fout, (short) (mode | PREFORMATTED | NOWRAP | CDATA), indent, lexer, node);
2207             }
2208             else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
2209             {
2210                 if (this.configuration.makeClean)
2211                 {
2212                     // discards <font> and </font> tags
2213                     if (node.tag == tt.tagFont)
2214                     {
2215                         for (content = node.content; content != null; content = content.next)
2216                         {
2217                             printTree(fout, mode, indent, lexer, content);
2218                         }
2219                         return;
2220                     }
2221 
2222                     // replace <nobr> ... </nobr> by &nbsp; or &#160; etc.
2223                     if (node.tag == tt.tagNobr)
2224                     {
2225                         for (content = node.content; content != null; content = content.next)
2226                         {
2227                             printTree(fout, (short) (mode | NOWRAP), indent, lexer, content);
2228                         }
2229                         return;
2230                     }
2231                 }
2232 
2233                 // otherwise a normal inline element
2234 
2235                 printTag(lexer, fout, mode, indent, node);
2236 
2237                 // indent content for SELECT, TEXTAREA, MAP, OBJECT and APPLET
2238 
2239                 if (shouldIndent(node))
2240                 {
2241                     condFlushLine(fout, indent);
2242                     indent += this.configuration.spaces;
2243 
2244                     for (content = node.content; content != null; content = content.next)
2245                     {
2246                         printTree(fout, mode, indent, lexer, content);
2247                     }
2248 
2249                     condFlushLine(fout, indent);
2250                     indent -= this.configuration.spaces;
2251                     condFlushLine(fout, indent);
2252                 }
2253                 else
2254                 {
2255 
2256                     for (content = node.content; content != null; content = content.next)
2257                     {
2258                         printTree(fout, mode, indent, lexer, content);
2259                     }
2260                 }
2261 
2262                 printEndTag(mode, indent, node);
2263             }
2264             else
2265             {
2266                 // other tags
2267                 condFlushLine(fout, indent);
2268 
2269                 if (this.configuration.smartIndent && node.prev != null)
2270                 {
2271                     flushLine(fout, indent);
2272                 }
2273 
2274                 // do not omit elements with attributes
2275                 if (!this.configuration.hideEndTags
2276                     || !(node.tag != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_OMITST))
2277                     || node.attributes != null)
2278                 {
2279                     printTag(lexer, fout, mode, indent, node);
2280 
2281                     if (shouldIndent(node))
2282                     {
2283                         condFlushLine(fout, indent);
2284                     }
2285                     else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_HTML)
2286                         || node.tag == tt.tagNoframes
2287                         || (TidyUtils.toBoolean(node.tag.model & Dict.CM_HEAD) && !(node.tag == tt.tagTitle)))
2288                     {
2289                         flushLine(fout, indent);
2290                     }
2291                 }
2292 
2293                 if (node.tag == tt.tagBody && this.configuration.burstSlides)
2294                 {
2295                     printSlide(fout, mode, (this.configuration.indentContent
2296                         ? indent + this.configuration.spaces
2297                         : indent), lexer);
2298                 }
2299                 else
2300                 {
2301                     last = null;
2302 
2303                     for (content = node.content; content != null; content = content.next)
2304                     {
2305                         // kludge for naked text before block level tag
2306                         if (last != null
2307                             && !this.configuration.indentContent
2308                             && last.type == Node.TEXT_NODE
2309                             && content.tag != null
2310                             && !TidyUtils.toBoolean(content.tag.model & Dict.CM_INLINE))
2311                         {
2312                             flushLine(fout, indent);
2313                         }
2314 
2315                         printTree(
2316                             fout,
2317                             mode,
2318                             (shouldIndent(node) ? indent + this.configuration.spaces : indent),
2319                             lexer,
2320                             content);
2321 
2322                         last = content;
2323                     }
2324                 }
2325 
2326                 // don't flush line for td and th
2327                 if (shouldIndent(node)
2328                     || ((TidyUtils.toBoolean(node.tag.model & Dict.CM_HTML) || node.tag == tt.tagNoframes || //
2329                     (TidyUtils.toBoolean(node.tag.model & Dict.CM_HEAD) && !(node.tag == tt.tagTitle))) && //
2330                     !this.configuration.hideEndTags))
2331                 {
2332                     condFlushLine(
2333                         fout,
2334                         (this.configuration.indentContent ? indent + this.configuration.spaces : indent));
2335 
2336                     if (!this.configuration.hideEndTags || !TidyUtils.toBoolean(node.tag.model & Dict.CM_OPT))
2337                     {
2338                         printEndTag(mode, indent, node);
2339 
2340                         // #603128 tidy adds newslines after </html> tag
2341                         // Fix by Fabrizio Giustina 12-02-2004
2342                         // fix is different from the one in original tidy
2343                         if (!lexer.seenEndHtml)
2344                         {
2345                             flushLine(fout, indent);
2346                         }
2347                     }
2348                 }
2349                 else
2350                 {
2351                     if (!this.configuration.hideEndTags || !TidyUtils.toBoolean(node.tag.model & Dict.CM_OPT))
2352                     {
2353                         printEndTag(mode, indent, node);
2354                     }
2355 
2356                     flushLine(fout, indent);
2357                 }
2358 
2359                 // FG commented out: double newlines
2360                 // if (!this.configuration.indentContent
2361                 //     && node.next != null
2362                 //     && !this.configuration.hideEndTags
2363                 //     && (node.tag.model
2364                 //     & TidyUtils.toBoolean(Dict.CM_BLOCK | Dict.CM_TABLE | Dict.CM_LIST | Dict.CM_DEFLIST)))
2365                 //     {
2366                 //         flushLine(fout, indent);
2367                 //     }
2368             }
2369         }
2370     }
2371 
2372     /***
2373      * @param fout
2374      * @param mode
2375      * @param indent
2376      * @param lexer
2377      * @param node
2378      */
2379     public void printXMLTree(Out fout, short mode, int indent, Lexer lexer, Node node)
2380     {
2381         TagTable tt = this.configuration.tt;
2382 
2383         if (node == null)
2384         {
2385             return;
2386         }
2387 
2388         if (node.type == Node.TEXT_NODE || (node.type == Node.CDATA_TAG && lexer.configuration.escapeCdata))
2389         {
2390             printText(fout, mode, indent, node.textarray, node.start, node.end);
2391         }
2392         else if (node.type == Node.COMMENT_TAG)
2393         {
2394             condFlushLine(fout, indent);
2395             printComment(fout, 0, node);
2396             condFlushLine(fout, 0);
2397         }
2398         else if (node.type == Node.ROOT_NODE)
2399         {
2400             Node content;
2401 
2402             for (content = node.content; content != null; content = content.next)
2403             {
2404                 printXMLTree(fout, mode, indent, lexer, content);
2405             }
2406         }
2407         else if (node.type == Node.DOCTYPE_TAG)
2408         {
2409             printDocType(fout, indent, lexer, node);
2410         }
2411         else if (node.type == Node.PROC_INS_TAG)
2412         {
2413             printPI(fout, indent, node);
2414         }
2415         else if (node.type == Node.XML_DECL)
2416         {
2417             printXmlDecl(fout, indent, node);
2418         }
2419         else if (node.type == Node.CDATA_TAG)
2420         {
2421             printCDATA(fout, indent, node);
2422         }
2423         else if (node.type == Node.SECTION_TAG)
2424         {
2425             printSection(fout, indent, node);
2426         }
2427         else if (node.type == Node.ASP_TAG)
2428         {
2429             printAsp(fout, indent, node);
2430         }
2431         else if (node.type == Node.JSTE_TAG)
2432         {
2433             printJste(fout, indent, node);
2434         }
2435         else if (node.type == Node.PHP_TAG)
2436         {
2437             printPhp(fout, indent, node);
2438         }
2439         else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_EMPTY)
2440             || node.type == Node.START_END_TAG
2441             && !configuration.xHTML)
2442         {
2443             condFlushLine(fout, indent);
2444             printTag(lexer, fout, mode, indent, node);
2445             // fgiust: Remove empty lines between tags in XML.
2446             //flushLine(fout, indent);
2447 
2448             // CPR: folks don't want so much vertical spacing in XML
2449             // if (node.next != null) { flushLine(fout, indent); }
2450 
2451         }
2452         else
2453         {
2454             // some kind of container element
2455             Node content;
2456             boolean mixed = false;
2457             int cindent;
2458 
2459             for (content = node.content; content != null; content = content.next)
2460             {
2461                 if (content.type == Node.TEXT_NODE)
2462                 {
2463                     mixed = true;
2464                     break;
2465                 }
2466             }
2467 
2468             condFlushLine(fout, indent);
2469 
2470             if (ParserImpl.XMLPreserveWhiteSpace(node, tt))
2471             {
2472                 indent = 0;
2473                 cindent = 0;
2474                 mixed = false;
2475             }
2476             else if (mixed)
2477             {
2478                 cindent = indent;
2479             }
2480             else
2481             {
2482                 cindent = indent + this.configuration.spaces;
2483             }
2484 
2485             printTag(lexer, fout, mode, indent, node);
2486 
2487             if (!mixed && node.content != null)
2488             {
2489                 flushLine(fout, indent);
2490             }
2491 
2492             for (content = node.content; content != null; content = content.next)
2493             {
2494                 printXMLTree(fout, mode, cindent, lexer, content);
2495             }
2496 
2497             if (!mixed && node.content != null)
2498             {
2499                 condFlushLine(fout, cindent);
2500             }
2501             printEndTag(mode, indent, node);
2502             //condFlushLine(fout, indent);
2503 
2504             // CPR: folks don't want so much vertical spacing in XML
2505             // if (node.next != null) { flushLine(fout, indent); }
2506 
2507         }
2508     }
2509 
2510     /***
2511      * Split parse tree by h2 elements and output to separate files. Counts number of h2 children (if any) belonging to
2512      * node.
2513      * @param node root node
2514      * @return number of slides (number of h2 elements)
2515      */
2516     public int countSlides(Node node)
2517     {
2518         // assume minimum of 1 slide
2519         int n = 1;
2520 
2521         TagTable tt = this.configuration.tt;
2522 
2523         //fix for [431716] avoid empty slides
2524         if (node != null && node.content != null && node.content.tag == tt.tagH2)
2525         {
2526             // "first" slide is empty, so ignore it
2527             n--;
2528         }
2529 
2530         if (node != null)
2531         {
2532             for (node = node.content; node != null; node = node.next)
2533             {
2534                 if (node.tag == tt.tagH2)
2535                 {
2536                     ++n;
2537                 }
2538             }
2539         }
2540 
2541         return n;
2542     }
2543 
2544     /***
2545      * @param fout
2546      * @param indent
2547      */
2548     private void printNavBar(Out fout, int indent)
2549     {
2550         String buf;
2551 
2552         condFlushLine(fout, indent);
2553         printString("<center><small>");
2554 
2555         NumberFormat numberFormat = NumberFormat.getInstance();
2556         numberFormat.setMinimumIntegerDigits(3);
2557 
2558         if (slide > 1)
2559         {
2560             buf = "<a href=\"slide" + numberFormat.format(slide - 1) + ".html\">previous</a> | ";
2561             // #427666 - fix by Eric Rossen 02 Aug 00
2562             printString(buf);
2563             condFlushLine(fout, indent);
2564 
2565             if (slide < count)
2566             {
2567                 printString("<a href=\"slide001.html\">start</a> | ");
2568                 // #427666 - fix by Eric Rossen 02 Aug 00
2569             }
2570             else
2571             {
2572                 printString("<a href=\"slide001.html\">start</a>");
2573                 // #427666 - fix by Eric Rossen 02 Aug 00
2574             }
2575 
2576             condFlushLine(fout, indent);
2577         }
2578 
2579         if (slide < count)
2580         {
2581             buf = "<a href=\"slide" + numberFormat.format(slide + 1) + ".html\">next</a>";
2582             // #427666 - fix by Eric Rossen 02 Aug 00
2583             printString(buf);
2584         }
2585 
2586         printString("</small></center>");
2587         condFlushLine(fout, indent);
2588     }
2589 
2590     /***
2591      * Called from printTree to print the content of a slide from the node slidecontent. On return slidecontent points
2592      * to the node starting the next slide or null. The variables slide and count are used to customise the navigation
2593      * bar.
2594      * @param fout
2595      * @param mode
2596      * @param indent
2597      * @param lexer
2598      */
2599     public void printSlide(Out fout, short mode, int indent, Lexer lexer)
2600     {
2601         Node content, last;
2602         TagTable tt = this.configuration.tt;
2603 
2604         NumberFormat numberFormat = NumberFormat.getInstance();
2605         numberFormat.setMinimumIntegerDigits(3);
2606 
2607         /* insert div for onclick handler */
2608         String s;
2609         s = "<div onclick=\"document.location='slide"
2610             + numberFormat.format(slide < count ? slide + 1 : 1)
2611             + ".html'\">";
2612         // #427666 - fix by Eric Rossen 02 Aug 00
2613         printString(s);
2614         condFlushLine(fout, indent);
2615 
2616         /* first print the h2 element and navbar */
2617         if (slidecontent != null && slidecontent.tag == tt.tagH2)
2618         {
2619             printNavBar(fout, indent);
2620 
2621             /* now print an hr after h2 */
2622 
2623             addC('<', linelen++);
2624 
2625             addC(TidyUtils.foldCase('h', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2626             addC(TidyUtils.foldCase('r', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2627 
2628             if (this.configuration.xmlOut)
2629             {
2630                 printString(" />");
2631             }
2632             else
2633             {
2634                 addC('>', linelen++);
2635             }
2636 
2637             if (this.configuration.indentContent)
2638             {
2639                 condFlushLine(fout, indent);
2640             }
2641 
2642             // PrintVertSpacer(fout, indent);
2643 
2644             // condFlushLine(fout, indent);
2645 
2646             // print the h2 element
2647             printTree(
2648                 fout,
2649                 mode,
2650                 (this.configuration.indentContent ? indent + this.configuration.spaces : indent),
2651                 lexer,
2652                 slidecontent);
2653 
2654             slidecontent = slidecontent.next;
2655         }
2656 
2657         // now continue until we reach the next h2
2658 
2659         last = null;
2660         content = slidecontent;
2661 
2662         for (; content != null; content = content.next)
2663         {
2664             if (content.tag == tt.tagH2)
2665             {
2666                 break;
2667             }
2668 
2669             // kludge for naked text before block level tag
2670             if (last != null
2671                 && !this.configuration.indentContent
2672                 && last.type == Node.TEXT_NODE
2673                 && content.tag != null
2674                 && TidyUtils.toBoolean(content.tag.model & Dict.CM_BLOCK))
2675             {
2676                 flushLine(fout, indent);
2677                 flushLine(fout, indent);
2678             }
2679 
2680             printTree(
2681                 fout,
2682                 mode,
2683                 (this.configuration.indentContent ? indent + this.configuration.spaces : indent),
2684                 lexer,
2685                 content);
2686 
2687             last = content;
2688         }
2689 
2690         slidecontent = content;
2691 
2692         // now print epilog
2693 
2694         condFlushLine(fout, indent);
2695 
2696         printString("<br clear=\"all\">");
2697         condFlushLine(fout, indent);
2698 
2699         addC('<', linelen++);
2700 
2701         addC(TidyUtils.foldCase('h', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2702         addC(TidyUtils.foldCase('r', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2703 
2704         if (this.configuration.xmlOut)
2705         {
2706             printString(" />");
2707         }
2708         else
2709         {
2710             addC('>', linelen++);
2711         }
2712 
2713         if (this.configuration.indentContent)
2714         {
2715             condFlushLine(fout, indent);
2716         }
2717 
2718         printNavBar(fout, indent);
2719 
2720         // end tag for div
2721         printString("</div>");
2722         condFlushLine(fout, indent);
2723     }
2724 
2725     /***
2726      * Add meta element for page transition effect, this works on IE but not NS.
2727      * @param lexer
2728      * @param root
2729      * @param duration
2730      */
2731     public void addTransitionEffect(Lexer lexer, Node root, double duration)
2732     {
2733         Node head = root.findHEAD(lexer.configuration.tt);
2734         String transition;
2735 
2736         transition = "blendTrans(Duration=" + (new Double(duration)).toString() + ")";
2737 
2738         if (head != null)
2739         {
2740             Node meta = lexer.inferredTag("meta");
2741             meta.addAttribute("http-equiv", "Page-Enter");
2742             meta.addAttribute("content", transition);
2743             head.insertNodeAtStart(meta);
2744         }
2745     }
2746 
2747     /***
2748      * Creates slides from h2.
2749      * @param lexer Lexer
2750      * @param root root node
2751      */
2752     public void createSlides(Lexer lexer, Node root)
2753     {
2754         Node body;
2755         String buf;
2756 
2757         NumberFormat numberFormat = NumberFormat.getInstance();
2758         numberFormat.setMinimumIntegerDigits(3);
2759 
2760         body = root.findBody(lexer.configuration.tt);
2761         count = countSlides(body);
2762         slidecontent = body.content;
2763 
2764         addTransitionEffect(lexer, root, 3.0);
2765 
2766         for (slide = 1; slide <= count; ++slide)
2767         {
2768             buf = "slide" + numberFormat.format(slide) + ".html";
2769 
2770             try
2771             {
2772                 FileOutputStream fis = new FileOutputStream(buf);
2773                 Out out = OutFactory.getOut(configuration, fis);
2774 
2775                 printTree(out, (short) 0, 0, lexer, root);
2776                 flushLine(out, 0);
2777                 out.close();
2778             }
2779             catch (IOException e)
2780             {
2781                 System.err.println(buf + e.toString());
2782             }
2783         }
2784 
2785         // delete superfluous slides by deleting slideN.html for N = count+1, count+2, etc.
2786         // until no such file is found.
2787 
2788         // #427666 - fix by Eric Rossen 02 Aug 00
2789         while ((new File("slide" + numberFormat.format(slide) + ".html")).delete())
2790         {
2791             ++slide;
2792         }
2793     }
2794 
2795 }