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: 807 $ ($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         if ("UTF8".equals(this.configuration.getOutCharEncodingName()))
675         {
676             // Chinese doesn't have spaces, so it needs other kinds of breaks
677             // This will also help documents using nice Unicode punctuation
678             // But we leave the ASCII range punctuation untouched
679 
680             // Break after any punctuation or spaces characters
681             if ((c >= 0x2000) && !TidyUtils.toBoolean(mode & PREFORMATTED))
682             {
683                 if (((c >= 0x2000) && (c <= 0x2006))
684                     || ((c >= 0x2008) && (c <= 0x2010))
685                     || ((c >= 0x2011) && (c <= 0x2046))
686                     || ((c >= 0x207D) && (c <= 0x207E))
687                     || ((c >= 0x208D) && (c <= 0x208E))
688                     || ((c >= 0x2329) && (c <= 0x232A))
689                     || ((c >= 0x3001) && (c <= 0x3003))
690                     || ((c >= 0x3008) && (c <= 0x3011))
691                     || ((c >= 0x3014) && (c <= 0x301F))
692                     || ((c >= 0xFD3E) && (c <= 0xFD3F))
693                     || ((c >= 0xFE30) && (c <= 0xFE44))
694                     || ((c >= 0xFE49) && (c <= 0xFE52))
695                     || ((c >= 0xFE54) && (c <= 0xFE61))
696                     || ((c >= 0xFE6A) && (c <= 0xFE6B))
697                     || ((c >= 0xFF01) && (c <= 0xFF03))
698                     || ((c >= 0xFF05) && (c <= 0xFF0A))
699                     || ((c >= 0xFF0C) && (c <= 0xFF0F))
700                     || ((c >= 0xFF1A) && (c <= 0xFF1B))
701                     || ((c >= 0xFF1F) && (c <= 0xFF20))
702                     || ((c >= 0xFF3B) && (c <= 0xFF3D))
703                     || ((c >= 0xFF61) && (c <= 0xFF65)))
704                 {
705                     wraphere = linelen + 2; // 2, because AddChar is not till later
706                     breakable = true;
707                 }
708                 else
709                 {
710                     switch (c)
711                     {
712                         case 0xFE63 :
713                         case 0xFE68 :
714                         case 0x3030 :
715                         case 0x30FB :
716                         case 0xFF3F :
717                         case 0xFF5B :
718                         case 0xFF5D :
719                             wraphere = linelen + 2;
720                             breakable = true;
721                     }
722                 }
723                 // but break before a left punctuation
724                 if (breakable)
725                 {
726                     if (((c >= 0x201A) && (c <= 0x201C)) || ((c >= 0x201E) && (c <= 0x201F)))
727                     {
728                         wraphere--;
729                     }
730                     else
731                     {
732                         switch (c)
733                         {
734                             case 0x2018 :
735                             case 0x2039 :
736                             case 0x2045 :
737                             case 0x207D :
738                             case 0x208D :
739                             case 0x2329 :
740                             case 0x3008 :
741                             case 0x300A :
742                             case 0x300C :
743                             case 0x300E :
744                             case 0x3010 :
745                             case 0x3014 :
746                             case 0x3016 :
747                             case 0x3018 :
748                             case 0x301A :
749                             case 0x301D :
750                             case 0xFD3E :
751                             case 0xFE35 :
752                             case 0xFE37 :
753                             case 0xFE39 :
754                             case 0xFE3B :
755                             case 0xFE3D :
756                             case 0xFE3F :
757                             case 0xFE41 :
758                             case 0xFE43 :
759                             case 0xFE59 :
760                             case 0xFE5B :
761                             case 0xFE5D :
762                             case 0xFF08 :
763                             case 0xFF3B :
764                             case 0xFF5B :
765                             case 0xFF62 :
766                                 wraphere--;
767                         }
768                     }
769                 }
770             }
771             else if ("BIG5".equals(this.configuration.getOutCharEncodingName()))
772             {
773                 // Allow linebreak at Chinese punctuation characters
774                 // There are not many spaces in Chinese
775                 addC(c, linelen++);
776                 if (((c & 0xFF00) == 0xA100) && !TidyUtils.toBoolean(mode & PREFORMATTED))
777                 {
778                     wraphere = linelen;
779                     // opening brackets have odd codes: break before them
780                     if ((c > 0x5C) && (c < 0xAD) && ((c & 1) == 1))
781                     {
782                         wraphere--;
783                     }
784                 }
785                 return;
786             }
787             else if ("SHIFTJIS".equals(this.configuration.getOutCharEncodingName())
788                 || "ISO2022".equals(this.configuration.getOutCharEncodingName()))
789             {
790                 // ISO 2022 characters are passed raw
791                 addC(c, linelen++);
792                 return;
793             }
794             else
795             {
796                 if (this.configuration.rawOut)
797                 {
798                     addC(c, linelen++);
799                     return;
800                 }
801             }
802             // #431953 - end RJ
803         }
804 
805         // if preformatted text, map &nbsp; to space
806         if (c == 160 && TidyUtils.toBoolean(mode & PREFORMATTED))
807         {
808             addC(' ', linelen++);
809             return;
810         }
811 
812         // Filters from Word and PowerPoint often use smart quotes resulting in character codes between 128 and 159.
813         // Unfortunately, the corresponding HTML 4.0 entities for these are not widely supported.
814         // The following converts dashes and quotation marks to the nearest ASCII equivalent.
815         // My thanks to Andrzej Novosiolov for his help with this code.
816 
817         if (this.configuration.makeClean && this.configuration.asciiChars || this.configuration.makeBare)
818         {
819             if (c >= 0x2013 && c <= 0x201E)
820             {
821                 switch (c)
822                 {
823                     case 0x2013 : // en dash
824                     case 0x2014 : // em dash
825                         c = '-';
826                         break;
827                     case 0x2018 : // left single quotation mark
828                     case 0x2019 : // right single quotation mark
829                     case 0x201A : // single low-9 quotation mark
830                         c = '\'';
831                         break;
832                     case 0x201C : // left double quotation mark
833                     case 0x201D : // right double quotation mark
834                     case 0x201E : // double low-9 quotation mark
835                         c = '"';
836                         break;
837                 }
838             }
839         }
840 
841         // don't map latin-1 chars to entities
842         if ("ISO8859_1".equals(this.configuration.getOutCharEncodingName()))
843         {
844             if (c > 255) /* multi byte chars */
845             {
846                 if (!this.configuration.numEntities)
847                 {
848                     entity = EntityTable.getDefaultEntityTable().entityName((short) c);
849                     if (entity != null)
850                     {
851                         entity = "&" + entity + ";";
852                     }
853                     else
854                     {
855                         entity = "&#" + c + ";";
856                     }
857                 }
858                 else
859                 {
860                     entity = "&#" + c + ";";
861                 }
862 
863                 for (int i = 0; i < entity.length(); i++)
864                 {
865                     addC(entity.charAt(i), linelen++);
866                 }
867 
868                 return;
869             }
870 
871             if (c > 126 && c < 160)
872             {
873                 entity = "&#" + c + ";";
874 
875                 for (int i = 0; i < entity.length(); i++)
876                 {
877                     addC(entity.charAt(i), linelen++);
878                 }
879 
880                 return;
881             }
882 
883             addC(c, linelen++);
884             return;
885         }
886 
887         // don't map utf8 or utf16 chars to entities
888         if (this.configuration.getOutCharEncodingName().startsWith("UTF"))
889         {
890             addC(c, linelen++);
891             return;
892         }
893 
894         // use numeric entities only for XML
895         if (this.configuration.xmlTags)
896         {
897             // if ASCII use numeric entities for chars > 127
898             if (c > 127 && "ASCII".equals(this.configuration.getOutCharEncodingName()))
899             {
900                 entity = "&#" + c + ";";
901 
902                 for (int i = 0; i < entity.length(); i++)
903                 {
904                     addC(entity.charAt(i), linelen++);
905                 }
906 
907                 return;
908             }
909 
910             // otherwise output char raw
911             addC(c, linelen++);
912             return;
913         }
914 
915         // default treatment for ASCII
916         if ("ASCII".equals(this.configuration.getOutCharEncodingName()) && (c > 126 || (c < ' ' && c != '\t')))
917         {
918             if (!this.configuration.numEntities)
919             {
920                 entity = EntityTable.getDefaultEntityTable().entityName((short) c);
921                 if (entity != null)
922                 {
923                     entity = "&" + entity + ";";
924                 }
925                 else
926                 {
927                     entity = "&#" + c + ";";
928                 }
929             }
930             else
931             {
932                 entity = "&#" + c + ";";
933             }
934 
935             for (int i = 0; i < entity.length(); i++)
936             {
937                 addC(entity.charAt(i), linelen++);
938             }
939 
940             return;
941         }
942 
943         addC(c, linelen++);
944     }
945 
946     /**
947      * The line buffer is uint not char so we can hold Unicode values unencoded. The translation to UTF-8 is deferred to
948      * the outc routine called to flush the line buffer.
949      * @param fout
950      * @param mode
951      * @param indent
952      * @param textarray
953      * @param start
954      * @param end
955      */
956     private void printText(Out fout, short mode, int indent, byte[] textarray, int start, int end)
957     {
958         int i, c;
959         int[] ci = new int[1];
960 
961         for (i = start; i < end; ++i)
962         {
963             if (indent + linelen >= this.configuration.wraplen)
964             {
965                 wrapLine(fout, indent);
966             }
967 
968             c = (textarray[i]) & 0xFF; // Convert to unsigned.
969 
970             // look for UTF-8 multibyte character
971             if (c > 0x7F)
972             {
973                 i += getUTF8(textarray, i, ci);
974                 c = ci[0];
975             }
976 
977             if (c == '\n')
978             {
979                 flushLine(fout, indent);
980                 continue;
981             }
982 
983             printChar(c, mode);
984         }
985     }
986 
987     /**
988      * @param str
989      */
990     private void printString(String str)
991     {
992         for (int i = 0; i < str.length(); i++)
993         {
994             addC(str.charAt(i), linelen++);
995         }
996     }
997 
998     /**
999      * @param fout
1000      * @param indent
1001      * @param value
1002      * @param delim
1003      * @param wrappable
1004      */
1005     private void printAttrValue(Out fout, int indent, String value, int delim, boolean wrappable)
1006     {
1007         int c;
1008         int[] ci = new int[1];
1009         boolean wasinstring = false;
1010         byte[] valueChars = null;
1011         int i;
1012         short mode = (wrappable ? (short) (NORMAL | ATTRIBVALUE) : (short) (PREFORMATTED | ATTRIBVALUE));
1013 
1014         if (value != null)
1015         {
1016             valueChars = TidyUtils.getBytes(value);
1017         }
1018 
1019         // look for ASP, Tango or PHP instructions for computed attribute value
1020         if (valueChars != null && valueChars.length >= 5 && valueChars[0] == '<')
1021         {
1022             if (valueChars[1] == '%' || valueChars[1] == '@' || (new String(valueChars, 0, 5)).equals("<?php"))
1023             {
1024                 mode |= CDATA;
1025             }
1026         }
1027 
1028         if (delim == 0)
1029         {
1030             delim = '"';
1031         }
1032 
1033         addC('=', linelen++);
1034 
1035         // don't wrap after "=" for xml documents
1036         if (!this.configuration.xmlOut)
1037         {
1038 
1039             if (indent + linelen < this.configuration.wraplen)
1040             {
1041                 wraphere = linelen;
1042             }
1043 
1044             if (indent + linelen >= this.configuration.wraplen)
1045             {
1046                 wrapLine(fout, indent);
1047             }
1048 
1049             if (indent + linelen < this.configuration.wraplen)
1050             {
1051                 wraphere = linelen;
1052             }
1053             else
1054             {
1055                 condFlushLine(fout, indent);
1056             }
1057         }
1058 
1059         addC(delim, linelen++);
1060 
1061         if (value != null)
1062         {
1063             inString = false;
1064 
1065             i = 0;
1066             while (i < valueChars.length)
1067             {
1068                 c = (valueChars[i]) & 0xFF; // Convert to unsigned.
1069 
1070                 if (wrappable && c == ' ' && indent + linelen < this.configuration.wraplen)
1071                 {
1072                     wraphere = linelen;
1073                     wasinstring = inString;
1074                 }
1075 
1076                 if (wrappable && wraphere > 0 && indent + linelen >= this.configuration.wraplen)
1077                 {
1078                     wrapAttrVal(fout, indent, wasinstring);
1079                 }
1080 
1081                 if (c == delim)
1082                 {
1083                     String entity;
1084 
1085                     entity = (c == '"' ? "&quot;" : "&#39;");
1086 
1087                     for (int j = 0; j < entity.length(); j++)
1088                     {
1089                         addC(entity.charAt(j), linelen++);
1090                     }
1091 
1092                     ++i;
1093                     continue;
1094                 }
1095                 else if (c == '"')
1096                 {
1097                     if (this.configuration.quoteMarks)
1098                     {
1099                         addC('&', linelen++);
1100                         addC('q', linelen++);
1101                         addC('u', linelen++);
1102                         addC('o', linelen++);
1103                         addC('t', linelen++);
1104                         addC(';', linelen++);
1105                     }
1106                     else
1107                     {
1108                         addC('"', linelen++);
1109                     }
1110 
1111                     if (delim == '\'')
1112                     {
1113                         inString = !inString;
1114                     }
1115 
1116                     ++i;
1117                     continue;
1118                 }
1119                 else if (c == '\'')
1120                 {
1121                     if (this.configuration.quoteMarks)
1122                     {
1123                         addC('&', linelen++);
1124                         addC('#', linelen++);
1125                         addC('3', linelen++);
1126                         addC('9', linelen++);
1127                         addC(';', linelen++);
1128                     }
1129                     else
1130                     {
1131                         addC('\'', linelen++);
1132                     }
1133 
1134                     if (delim == '"')
1135                     {
1136                         inString = !inString;
1137                     }
1138 
1139                     ++i;
1140                     continue;
1141                 }
1142 
1143                 // look for UTF-8 multibyte character
1144                 if (c > 0x7F)
1145                 {
1146                     i += getUTF8(valueChars, i, ci);
1147                     c = ci[0];
1148                 }
1149 
1150                 ++i;
1151 
1152                 if (c == '\n')
1153                 {
1154                     flushLine(fout, indent);
1155                     continue;
1156                 }
1157 
1158                 printChar(c, mode);
1159             }
1160         }
1161 
1162         inString = false;
1163         addC(delim, linelen++);
1164     }
1165 
1166     /**
1167      * @param fout
1168      * @param indent
1169      * @param node
1170      * @param attr
1171      */
1172     private void printAttribute(Out fout, int indent, Node node, AttVal attr)
1173     {
1174         String name;
1175         boolean wrappable = false;
1176 
1177         if (this.configuration.indentAttributes)
1178         {
1179             flushLine(fout, indent);
1180             indent += this.configuration.spaces;
1181         }
1182 
1183         name = attr.attribute;
1184 
1185         if (indent + linelen >= this.configuration.wraplen)
1186         {
1187             wrapLine(fout, indent);
1188         }
1189 
1190         if (!this.configuration.xmlTags && !this.configuration.xmlOut && attr.dict != null)
1191         {
1192             if (AttributeTable.getDefaultAttributeTable().isScript(name))
1193             {
1194                 wrappable = this.configuration.wrapScriptlets;
1195             }
1196             else if (!attr.dict.isNowrap() && this.configuration.wrapAttVals)
1197             {
1198                 wrappable = true;
1199             }
1200         }
1201 
1202         if (indent + linelen < this.configuration.wraplen)
1203         {
1204             wraphere = linelen;
1205             addC(' ', linelen++);
1206         }
1207         else
1208         {
1209             condFlushLine(fout, indent);
1210             addC(' ', linelen++);
1211         }
1212 
1213         for (int i = 0; i < name.length(); i++)
1214         {
1215             addC(
1216                 TidyUtils.foldCase(name.charAt(i), this.configuration.upperCaseAttrs, this.configuration.xmlTags),
1217                 linelen++);
1218         }
1219 
1220         if (indent + linelen >= this.configuration.wraplen)
1221         {
1222             wrapLine(fout, indent);
1223         }
1224 
1225         if (attr.value == null)
1226         {
1227             if (this.configuration.xmlTags || this.configuration.xmlOut)
1228             {
1229                 printAttrValue(fout, indent, (attr.isBoolAttribute() ? attr.attribute : ""), attr.delim, true);
1230             }
1231             else if (!attr.isBoolAttribute() && node != null && !node.isNewNode())
1232             {
1233                 printAttrValue(fout, indent, "", attr.delim, true);
1234             }
1235             else if (indent + linelen < this.configuration.wraplen)
1236             {
1237                 wraphere = linelen;
1238             }
1239 
1240         }
1241         else
1242         {
1243             printAttrValue(fout, indent, attr.value, attr.delim, wrappable);
1244         }
1245     }
1246 
1247     /**
1248      * @param fout
1249      * @param indent
1250      * @param node
1251      * @param attr
1252      */
1253     private void printAttrs(Out fout, int indent, Node node, AttVal attr)
1254     {
1255         // add xml:space attribute to pre and other elements
1256         if (configuration.xmlOut
1257             && configuration.xmlSpace
1258             && ParserImpl.XMLPreserveWhiteSpace(node, configuration.tt)
1259             && node.getAttrByName("xml:space") == null)
1260         {
1261             node.addAttribute("xml:space", "preserve");
1262             if (attr != null)
1263             {
1264                 attr = node.attributes;
1265             }
1266         }
1267 
1268         if (attr != null)
1269         {
1270             if (attr.next != null)
1271             {
1272                 printAttrs(fout, indent, node, attr.next);
1273             }
1274 
1275             if (attr.attribute != null)
1276             {
1277                 Attribute attribute = attr.dict;
1278 
1279                 if (!this.configuration.dropProprietaryAttributes
1280                     || !(attribute == null || TidyUtils.toBoolean(attribute.getVersions() & Dict.VERS_PROPRIETARY)))
1281                 {
1282                     printAttribute(fout, indent, node, attr);
1283                 }
1284             }
1285             else if (attr.asp != null)
1286             {
1287                 addC(' ', linelen++);
1288                 printAsp(fout, indent, attr.asp);
1289             }
1290             else if (attr.php != null)
1291             {
1292                 addC(' ', linelen++);
1293                 printPhp(fout, indent, attr.php);
1294             }
1295         }
1296 
1297     }
1298 
1299     /**
1300      * Line can be wrapped immediately after inline start tag provided if follows a text node ending in a space, or it
1301      * parent is an inline element that that rule applies to. This behaviour was reverse engineered from Netscape 3.0
1302      * @param node current Node
1303      * @return <code>true</code> if the current char follows a space
1304      */
1305     private static boolean afterSpace(Node node)
1306     {
1307         Node prev;
1308         int c;
1309 
1310         if (node == null || node.tag == null || !TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
1311         {
1312             return true;
1313         }
1314 
1315         prev = node.prev;
1316 
1317         if (prev != null)
1318         {
1319             if (prev.type == Node.TEXT_NODE && prev.end > prev.start)
1320             {
1321                 c = (prev.textarray[prev.end - 1]) & 0xFF; // Convert to unsigned.
1322 
1323                 if (c == 160 || c == ' ' || c == '\n')
1324                 {
1325                     return true;
1326                 }
1327             }
1328 
1329             return false;
1330         }
1331 
1332         return afterSpace(node.parent);
1333     }
1334 
1335     /**
1336      * @param lexer
1337      * @param fout
1338      * @param mode
1339      * @param indent
1340      * @param node
1341      */
1342     private void printTag(Lexer lexer, Out fout, short mode, int indent, Node node)
1343     {
1344         String p;
1345         TagTable tt = this.configuration.tt;
1346 
1347         addC('<', linelen++);
1348 
1349         if (node.type == Node.END_TAG)
1350         {
1351             addC('/', linelen++);
1352         }
1353 
1354         p = node.element;
1355         for (int i = 0; i < p.length(); i++)
1356         {
1357             addC(
1358                 TidyUtils.foldCase(p.charAt(i), this.configuration.upperCaseTags, this.configuration.xmlTags),
1359                 linelen++);
1360         }
1361 
1362         printAttrs(fout, indent, node, node.attributes);
1363 
1364         if ((this.configuration.xmlOut || this.configuration.xHTML)
1365             && (node.type == Node.START_END_TAG || TidyUtils.toBoolean(node.tag.model & Dict.CM_EMPTY)))
1366         {
1367             addC(' ', linelen++); // Space is NS compatibility hack <br />
1368             addC('/', linelen++); // Required end tag marker
1369         }
1370 
1371         addC('>', linelen++);
1372 
1373         if ((node.type != Node.START_END_TAG || configuration.xHTML) && !TidyUtils.toBoolean(mode & PREFORMATTED))
1374         {
1375             if (indent + linelen >= this.configuration.wraplen)
1376             {
1377                 wrapLine(fout, indent);
1378             }
1379 
1380             if (indent + linelen < this.configuration.wraplen)
1381             {
1382 
1383                 // wrap after start tag if is <br/> or if it's not inline
1384                 // fix for [514348]
1385                 if (!TidyUtils.toBoolean(mode & NOWRAP)
1386                     && (!TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE) || (node.tag == tt.tagBr))
1387                     && afterSpace(node))
1388                 {
1389                     wraphere = linelen;
1390                 }
1391 
1392             }
1393         }
1394         else
1395         {
1396             condFlushLine(fout, indent);
1397         }
1398 
1399     }
1400 
1401     /**
1402      * @param mode
1403      * @param indent
1404      * @param node
1405      */
1406     private void printEndTag(short mode, int indent, Node node)
1407     {
1408         String p;
1409 
1410         // Netscape ignores SGML standard by not ignoring a line break before </A> or </U> etc.
1411         // To avoid rendering this as an underlined space, I disable line wrapping before inline end tags
1412 
1413         // if (indent + linelen < this.configuration.wraplen && !TidyUtils.toBoolean(mode & NOWRAP))
1414         // {
1415         // wraphere = linelen;
1416         // }
1417 
1418         addC('<', linelen++);
1419         addC('/', linelen++);
1420 
1421         p = node.element;
1422         for (int i = 0; i < p.length(); i++)
1423         {
1424             addC(
1425                 TidyUtils.foldCase(p.charAt(i), this.configuration.upperCaseTags, this.configuration.xmlTags),
1426                 linelen++);
1427         }
1428 
1429         addC('>', linelen++);
1430     }
1431 
1432     /**
1433      * @param fout
1434      * @param indent
1435      * @param node
1436      */
1437     private void printComment(Out fout, int indent, Node node)
1438     {
1439         if (this.configuration.hideComments)
1440         {
1441             return;
1442         }
1443 
1444         if (indent + linelen < this.configuration.wraplen)
1445         {
1446             wraphere = linelen;
1447         }
1448 
1449         addC('<', linelen++);
1450         addC('!', linelen++);
1451         addC('-', linelen++);
1452         addC('-', linelen++);
1453 
1454         printText(fout, COMMENT, indent, node.textarray, node.start, node.end);
1455 
1456         // See Lexer.java: AQ 8Jul2000
1457         addC('-', linelen++);
1458         addC('-', linelen++);
1459         addC('>', linelen++);
1460 
1461         if (node.linebreak)
1462         {
1463             flushLine(fout, indent);
1464         }
1465     }
1466 
1467     /**
1468      * @param fout
1469      * @param indent
1470      * @param lexer
1471      * @param node
1472      */
1473     private void printDocType(Out fout, int indent, Lexer lexer, Node node)
1474     {
1475         int i, c = 0;
1476         short mode = 0;
1477         boolean q = this.configuration.quoteMarks;
1478 
1479         this.configuration.quoteMarks = false;
1480 
1481         if (indent + linelen < this.configuration.wraplen)
1482         {
1483             wraphere = linelen;
1484         }
1485 
1486         condFlushLine(fout, indent);
1487 
1488         addC('<', linelen++);
1489         addC('!', linelen++);
1490         addC('D', linelen++);
1491         addC('O', linelen++);
1492         addC('C', linelen++);
1493         addC('T', linelen++);
1494         addC('Y', linelen++);
1495         addC('P', linelen++);
1496         addC('E', linelen++);
1497         addC(' ', linelen++);
1498 
1499         if (indent + linelen < this.configuration.wraplen)
1500         {
1501             wraphere = linelen;
1502         }
1503 
1504         for (i = node.start; i < node.end; ++i)
1505         {
1506             if (indent + linelen >= this.configuration.wraplen)
1507             {
1508                 wrapLine(fout, indent);
1509             }
1510 
1511             c = node.textarray[i] & 0xFF; // Convert to unsigned.
1512 
1513             // inDTDSubset?
1514             if (TidyUtils.toBoolean(mode & CDATA))
1515             {
1516                 if (c == ']')
1517                 {
1518                     mode &= ~CDATA;
1519                 }
1520             }
1521             else if (c == '[')
1522             {
1523                 mode |= CDATA;
1524             }
1525             int[] ci = new int[1];
1526 
1527             // look for UTF-8 multibyte character
1528             if (c > 0x7F)
1529             {
1530                 i += getUTF8(node.textarray, i, ci);
1531                 c = ci[0];
1532             }
1533 
1534             if (c == '\n')
1535             {
1536                 flushLine(fout, indent);
1537                 continue;
1538             }
1539 
1540             printChar(c, mode);
1541         }
1542 
1543         if (linelen < this.configuration.wraplen)
1544         {
1545             wraphere = linelen;
1546         }
1547 
1548         addC('>', linelen++);
1549         this.configuration.quoteMarks = q;
1550         condFlushLine(fout, indent);
1551     }
1552 
1553     /**
1554      * @param fout
1555      * @param indent
1556      * @param node
1557      */
1558     private void printPI(Out fout, int indent, Node node)
1559     {
1560         if (indent + linelen < this.configuration.wraplen)
1561         {
1562             wraphere = linelen;
1563         }
1564 
1565         addC('<', linelen++);
1566         addC('?', linelen++);
1567 
1568         // set CDATA to pass < and > unescaped
1569         printText(fout, CDATA, indent, node.textarray, node.start, node.end);
1570 
1571         if (node.end <= 0 || node.textarray[node.end - 1] != '?') // #542029 - fix by Terry Teague 10 Apr 02
1572         {
1573             addC('?', linelen++);
1574         }
1575 
1576         addC('>', linelen++);
1577         condFlushLine(fout, indent);
1578     }
1579 
1580     /**
1581      * Pretty print the xml declaration.
1582      * @param fout
1583      * @param indent
1584      * @param node
1585      */
1586     private void printXmlDecl(Out fout, int indent, Node node)
1587     {
1588         if (indent + linelen < this.configuration.wraplen)
1589         {
1590             wraphere = linelen;
1591         }
1592 
1593         addC('<', linelen++);
1594         addC('?', linelen++);
1595         addC('x', linelen++);
1596         addC('m', linelen++);
1597         addC('l', linelen++);
1598 
1599         printAttrs(fout, indent, node, node.attributes);
1600 
1601         if (node.end <= 0 || node.textarray[node.end - 1] != '?') // #542029 - fix by Terry Teague 10 Apr 02
1602         {
1603             addC('?', linelen++);
1604         }
1605 
1606         addC('>', linelen++);
1607 
1608         condFlushLine(fout, indent);
1609     }
1610 
1611     /**
1612      * note ASP and JSTE share <% ... %> syntax.
1613      * @param fout
1614      * @param indent
1615      * @param node
1616      */
1617     private void printAsp(Out fout, int indent, Node node)
1618     {
1619         int savewraplen = this.configuration.wraplen;
1620 
1621         // disable wrapping if so requested
1622 
1623         if (!this.configuration.wrapAsp || !this.configuration.wrapJste)
1624         {
1625             this.configuration.wraplen = 0xFFFFFF; // a very large number
1626         }
1627 
1628         addC('<', linelen++);
1629         addC('%', linelen++);
1630 
1631         printText(fout, (this.configuration.wrapAsp ? CDATA : COMMENT), indent, node.textarray, node.start, node.end);
1632 
1633         addC('%', linelen++);
1634         addC('>', linelen++);
1635         /* condFlushLine(fout, indent); */
1636         this.configuration.wraplen = savewraplen;
1637     }
1638 
1639     /**
1640      * JSTE also supports <# ... #> syntax
1641      * @param fout
1642      * @param indent
1643      * @param node
1644      */
1645     private void printJste(Out fout, int indent, Node node)
1646     {
1647         int savewraplen = this.configuration.wraplen;
1648 
1649         // disable wrapping if so requested
1650 
1651         if (!this.configuration.wrapJste)
1652         {
1653             this.configuration.wraplen = 0xFFFFFF; // a very large number
1654         }
1655 
1656         addC('<', linelen++);
1657         addC('#', linelen++);
1658 
1659         printText(fout, (this.configuration.wrapJste ? CDATA : COMMENT), indent, node.textarray, node.start, node.end);
1660 
1661         addC('#', linelen++);
1662         addC('>', linelen++);
1663         // condFlushLine(fout, indent);
1664         this.configuration.wraplen = savewraplen;
1665     }
1666 
1667     /**
1668      * PHP is based on XML processing instructions.
1669      * @param fout
1670      * @param indent
1671      * @param node
1672      */
1673     private void printPhp(Out fout, int indent, Node node)
1674     {
1675         int savewraplen = this.configuration.wraplen;
1676 
1677         // disable wrapping if so requested
1678 
1679         if (!this.configuration.wrapPhp)
1680         {
1681             this.configuration.wraplen = 0xFFFFFF; // a very large number
1682         }
1683 
1684         addC('<', linelen++);
1685         addC('?', linelen++);
1686 
1687         printText(fout, (this.configuration.wrapPhp ? CDATA : COMMENT), indent, node.textarray, node.start, node.end);
1688 
1689         addC('?', linelen++);
1690         addC('>', linelen++);
1691         // PCondFlushLine(fout, indent);
1692         this.configuration.wraplen = savewraplen;
1693     }
1694 
1695     /**
1696      * @param fout
1697      * @param indent
1698      * @param node
1699      */
1700     private void printCDATA(Out fout, int indent, Node node)
1701     {
1702         int savewraplen = this.configuration.wraplen;
1703 
1704         if (!this.configuration.indentCdata)
1705         {
1706             indent = 0;
1707         }
1708 
1709         condFlushLine(fout, indent);
1710 
1711         // disable wrapping
1712         this.configuration.wraplen = 0xFFFFFF; // a very large number
1713 
1714         addC('<', linelen++);
1715         addC('!', linelen++);
1716         addC('[', linelen++);
1717         addC('C', linelen++);
1718         addC('D', linelen++);
1719         addC('A', linelen++);
1720         addC('T', linelen++);
1721         addC('A', linelen++);
1722         addC('[', linelen++);
1723 
1724         printText(fout, COMMENT, indent, node.textarray, node.start, node.end);
1725 
1726         addC(']', linelen++);
1727         addC(']', linelen++);
1728         addC('>', linelen++);
1729         condFlushLine(fout, indent);
1730         this.configuration.wraplen = savewraplen;
1731     }
1732 
1733     /**
1734      * @param fout
1735      * @param indent
1736      * @param node
1737      */
1738     private void printSection(Out fout, int indent, Node node)
1739     {
1740         int savewraplen = this.configuration.wraplen;
1741 
1742         // disable wrapping if so requested
1743 
1744         if (!this.configuration.wrapSection)
1745         {
1746             this.configuration.wraplen = 0xFFFFFF; // a very large number
1747         }
1748 
1749         addC('<', linelen++);
1750         addC('!', linelen++);
1751         addC('[', linelen++);
1752 
1753         printText(
1754             fout,
1755             (this.configuration.wrapSection ? CDATA : COMMENT),
1756             indent,
1757             node.textarray,
1758             node.start,
1759             node.end);
1760 
1761         addC(']', linelen++);
1762         addC('>', linelen++);
1763         // PCondFlushLine(fout, indent);
1764         this.configuration.wraplen = savewraplen;
1765     }
1766 
1767     /**
1768      * Is the current node inside HEAD?
1769      * @param node Node
1770      * @return <code>true</code> if node is inside an HEAD tag
1771      */
1772     private boolean insideHead(Node node)
1773     {
1774         if (node.tag == this.configuration.tt.tagHead)
1775         {
1776             return true;
1777         }
1778 
1779         if (node.parent != null)
1780         {
1781             return insideHead(node.parent);
1782         }
1783         return false;
1784     }
1785 
1786     /**
1787      * Is text node and already ends w/ a newline? Used to pretty print CDATA/PRE text content. If it already ends on a
1788      * newline, it is not necessary to print another before printing end tag.
1789      * @param lexer Lexer
1790      * @param node text node
1791      * @return text indent
1792      */
1793     private int textEndsWithNewline(Lexer lexer, Node node)
1794     {
1795         if (node.type == Node.TEXT_NODE && node.end > node.start)
1796         {
1797             int ch, ix = node.end - 1;
1798             // Skip non-newline whitespace
1799             while (ix >= node.start
1800                 && TidyUtils.toBoolean(ch = (node.textarray[ix] & 0xff))
1801                 && (ch == ' ' || ch == '\t' || ch == '\r'))
1802             {
1803                 --ix;
1804             }
1805 
1806             if (node.textarray[ix] == '\n')
1807             {
1808                 return node.end - ix - 1; // #543262 tidy eats all memory
1809             }
1810         }
1811         return -1;
1812     }
1813 
1814     /**
1815      * Does the current node contain a CDATA section?
1816      * @param lexer Lexer
1817      * @param node Node
1818      * @return <code>true</code> if node contains a CDATA section
1819      */
1820     static boolean hasCDATA(Lexer lexer, Node node)
1821     {
1822         // Scan forward through the textarray. Since the characters we're
1823         // looking for are < 0x7f, we don't have to do any UTF-8 decoding.
1824 
1825         if (node.type != Node.TEXT_NODE)
1826         {
1827             return false;
1828         }
1829 
1830         int len = node.end - node.start + 1;
1831         String start = TidyUtils.getString(node.textarray, node.start, len);
1832 
1833         int indexOfCData = start.indexOf(CDATA_START);
1834         return indexOfCData > -1 && indexOfCData <= len;
1835     }
1836 
1837     /**
1838      * Print script and style elements. For XHTML, wrap the content as follows:
1839      * 
1840      * <pre>
1841      *     JavaScript:
1842      *         //&lt;![CDATA[
1843      *             content
1844      *         //]]>
1845      *     VBScript:
1846      *         '&lt;![CDATA[
1847      *             content
1848      *         ']]>
1849      *     CSS:
1850      *         /*&lt;![CDATA[* /
1851      *             content
1852      *         /*]]>* /
1853      *     other:
1854      *        &lt;![CDATA[
1855      *             content
1856      *         ]]>
1857      * </pre>
1858      * 
1859      * @param fout
1860      * @param mode
1861      * @param indent
1862      * @param lexer
1863      * @param node
1864      */
1865     private void printScriptStyle(Out fout, short mode, int indent, Lexer lexer, Node node)
1866     {
1867         Node content;
1868         String commentStart = DEFAULT_COMMENT_START;
1869         String commentEnd = DEFAULT_COMMENT_END;
1870         boolean hasCData = false;
1871         int contentIndent = -1;
1872 
1873         if (insideHead(node))
1874         {
1875             // flushLine(fout, indent);
1876         }
1877 
1878         indent = 0;
1879 
1880         // start script
1881         printTag(lexer, fout, mode, indent, node);
1882         // flushLine(fout, indent); // extra newline
1883 
1884         if (lexer.configuration.xHTML && node.content != null)
1885         {
1886             AttVal type = node.getAttrByName("type");
1887             if (type != null)
1888             {
1889                 if ("text/javascript".equalsIgnoreCase(type.value))
1890                 {
1891                     commentStart = JS_COMMENT_START;
1892                     commentEnd = JS_COMMENT_END;
1893                 }
1894                 else if ("text/css".equalsIgnoreCase(type.value))
1895                 {
1896                     commentStart = CSS_COMMENT_START;
1897                     commentEnd = CSS_COMMENT_END;
1898                 }
1899                 else if ("text/vbscript".equalsIgnoreCase(type.value))
1900                 {
1901                     commentStart = VB_COMMENT_START;
1902                     commentEnd = VB_COMMENT_END;
1903                 }
1904             }
1905 
1906             hasCData = hasCDATA(lexer, node.content);
1907             if (!hasCData)
1908             {
1909                 // disable wrapping
1910                 int savewraplen = lexer.configuration.wraplen;
1911                 lexer.configuration.wraplen = 0xFFFFFF; // a very large number
1912 
1913                 linelen = addAsciiString(commentStart, linelen);
1914                 linelen = addAsciiString(CDATA_START, linelen);
1915                 linelen = addAsciiString(commentEnd, linelen);
1916                 condFlushLine(fout, indent);
1917 
1918                 // restore wrapping
1919                 lexer.configuration.wraplen = savewraplen;
1920             }
1921         }
1922 
1923         for (content = node.content; content != null; content = content.next)
1924         {
1925             printTree(fout, (short) (mode | PREFORMATTED | NOWRAP | CDATA), 0, lexer, content);
1926 
1927             if (content.next == null)
1928             {
1929                 contentIndent = textEndsWithNewline(lexer, content);
1930             }
1931 
1932         }
1933 
1934         if (contentIndent < 0)
1935         {
1936             condFlushLine(fout, indent);
1937             contentIndent = 0;
1938         }
1939 
1940         if (lexer.configuration.xHTML && node.content != null)
1941         {
1942             if (!hasCData)
1943             {
1944                 // disable wrapping
1945                 int ix, savewraplen = lexer.configuration.wraplen;
1946                 lexer.configuration.wraplen = 0xFFFFFF; // a very large number
1947 
1948                 // Add spaces to last text node to align w/ indent
1949                 if (contentIndent > 0 && linelen < contentIndent)
1950                 {
1951                     linelen = contentIndent;
1952                 }
1953                 for (ix = 0; contentIndent < indent && ix < indent - contentIndent; ++ix)
1954                 {
1955                     addC(' ', linelen++);
1956                 }
1957 
1958                 linelen = addAsciiString(commentStart, linelen);
1959                 linelen = addAsciiString(CDATA_END, linelen);
1960                 linelen = addAsciiString(commentEnd, linelen);
1961 
1962                 // restore wrapping
1963                 lexer.configuration.wraplen = savewraplen;
1964                 condFlushLine(fout, 0);
1965             }
1966         }
1967 
1968         printEndTag(mode, indent, node);
1969 
1970         if (!lexer.configuration.indentContent && node.next != null
1971 
1972         && !((node.tag != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
1973 
1974         || node.type != Node.TEXT_NODE
1975 
1976         ))
1977         {
1978             flushLine(fout, indent);
1979         }
1980 
1981         flushLine(fout, indent);
1982     }
1983 
1984     /**
1985      * Should tidy indent the give tag?
1986      * @param node actual node
1987      * @return <code>true</code> if line should be indented
1988      */
1989     private boolean shouldIndent(Node node)
1990     {
1991         TagTable tt = this.configuration.tt;
1992 
1993         if (!this.configuration.indentContent)
1994         {
1995             return false;
1996         }
1997 
1998         if (this.configuration.smartIndent)
1999         {
2000             if (node.content != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_NO_INDENT))
2001             {
2002                 for (node = node.content; node != null; node = node.next)
2003                 {
2004                     if (node.tag != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_BLOCK))
2005                     {
2006                         return true;
2007                     }
2008                 }
2009 
2010                 return false;
2011             }
2012 
2013             if (TidyUtils.toBoolean(node.tag.model & Dict.CM_HEADING))
2014             {
2015                 return false;
2016             }
2017 
2018             if (node.tag == tt.tagP)
2019             {
2020                 return false;
2021             }
2022 
2023             if (node.tag == tt.tagTitle)
2024             {
2025                 return false;
2026             }
2027         }
2028 
2029         if (TidyUtils.toBoolean(node.tag.model & (Dict.CM_FIELD | Dict.CM_OBJECT)))
2030         {
2031             return true;
2032         }
2033 
2034         if (node.tag == tt.tagMap)
2035         {
2036             return true;
2037         }
2038 
2039         return !TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE);
2040     }
2041 
2042     /**
2043      * Print just the content of the body element. Useful when you want to reuse material from other documents.
2044      * @param fout
2045      * @param lexer
2046      * @param root
2047      * @param xml
2048      */
2049     void printBody(Out fout, Lexer lexer, Node root, boolean xml)
2050     {
2051         if (root == null)
2052         {
2053             return;
2054         }
2055 
2056         // Feature request #434940 - fix by Dave Raggett/Ignacio Vazquez-Abrams 21 Jun 01
2057         // Sebastiano Vigna <vigna@dsi.unimi.it>
2058         Node body = root.findBody(lexer.configuration.tt);
2059 
2060         if (body != null)
2061         {
2062             Node content;
2063             for (content = body.content; content != null; content = content.next)
2064             {
2065                 if (xml)
2066                 {
2067                     printXMLTree(fout, (short) 0, 0, lexer, content);
2068                 }
2069                 else
2070                 {
2071                     printTree(fout, (short) 0, 0, lexer, content);
2072                 }
2073             }
2074         }
2075     }
2076 
2077     /**
2078      * @param fout
2079      * @param mode
2080      * @param indent
2081      * @param lexer
2082      * @param node
2083      */
2084     public void printTree(Out fout, short mode, int indent, Lexer lexer, Node node)
2085     {
2086         Node content, last;
2087         TagTable tt = this.configuration.tt;
2088 
2089         if (node == null)
2090         {
2091             return;
2092         }
2093 
2094         if (node.type == Node.TEXT_NODE || (node.type == Node.CDATA_TAG && lexer.configuration.escapeCdata))
2095         {
2096             printText(fout, mode, indent, node.textarray, node.start, node.end);
2097         }
2098         else if (node.type == Node.COMMENT_TAG)
2099         {
2100             printComment(fout, indent, node);
2101         }
2102         else if (node.type == Node.ROOT_NODE)
2103         {
2104             for (content = node.content; content != null; content = content.next)
2105             {
2106                 printTree(fout, mode, indent, lexer, content);
2107             }
2108         }
2109         else if (node.type == Node.DOCTYPE_TAG)
2110         {
2111             printDocType(fout, indent, lexer, node);
2112         }
2113         else if (node.type == Node.PROC_INS_TAG)
2114         {
2115             printPI(fout, indent, node);
2116         }
2117         else if (node.type == Node.XML_DECL)
2118         {
2119             printXmlDecl(fout, indent, node);
2120         }
2121         else if (node.type == Node.CDATA_TAG)
2122         {
2123             printCDATA(fout, indent, node);
2124         }
2125         else if (node.type == Node.SECTION_TAG)
2126         {
2127             printSection(fout, indent, node);
2128         }
2129         else if (node.type == Node.ASP_TAG)
2130         {
2131             printAsp(fout, indent, node);
2132         }
2133         else if (node.type == Node.JSTE_TAG)
2134         {
2135             printJste(fout, indent, node);
2136         }
2137         else if (node.type == Node.PHP_TAG)
2138         {
2139             printPhp(fout, indent, node);
2140         }
2141         else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_EMPTY)
2142             || (node.type == Node.START_END_TAG && !configuration.xHTML))
2143         {
2144             if (!TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
2145             {
2146                 condFlushLine(fout, indent);
2147             }
2148 
2149             if (node.tag == tt.tagBr
2150                 && node.prev != null
2151                 && node.prev.tag != tt.tagBr
2152                 && this.configuration.breakBeforeBR)
2153             {
2154                 flushLine(fout, indent);
2155             }
2156 
2157             if (this.configuration.makeClean && node.tag == tt.tagWbr)
2158             {
2159                 printString(" ");
2160             }
2161             else
2162             {
2163                 printTag(lexer, fout, mode, indent, node);
2164             }
2165 
2166             if (node.tag == tt.tagParam || node.tag == tt.tagArea)
2167             {
2168                 condFlushLine(fout, indent);
2169             }
2170             else if (node.tag == tt.tagBr || node.tag == tt.tagHr)
2171             {
2172                 flushLine(fout, indent);
2173             }
2174         }
2175         else
2176         {
2177             if (node.type == Node.START_END_TAG)
2178             {
2179                 node.type = Node.START_TAG;
2180             }
2181 
2182             // some kind of container element
2183             if (node.tag != null && node.tag.getParser() == ParserImpl.PRE)
2184             {
2185                 condFlushLine(fout, indent);
2186 
2187                 indent = 0;
2188                 condFlushLine(fout, indent);
2189                 printTag(lexer, fout, mode, indent, node);
2190                 flushLine(fout, indent);
2191 
2192                 for (content = node.content; content != null; content = content.next)
2193                 {
2194                     printTree(fout, (short) (mode | PREFORMATTED | NOWRAP), indent, lexer, content);
2195                 }
2196 
2197                 condFlushLine(fout, indent);
2198                 printEndTag(mode, indent, node);
2199                 flushLine(fout, indent);
2200 
2201                 if (!this.configuration.indentContent && node.next != null)
2202                 {
2203                     flushLine(fout, indent);
2204                 }
2205             }
2206             else if (node.tag == tt.tagStyle || node.tag == tt.tagScript)
2207             {
2208                 printScriptStyle(fout, (short) (mode | PREFORMATTED | NOWRAP | CDATA), indent, lexer, node);
2209             }
2210             else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_INLINE))
2211             {
2212                 if (this.configuration.makeClean)
2213                 {
2214                     // discards <font> and </font> tags
2215                     if (node.tag == tt.tagFont)
2216                     {
2217                         for (content = node.content; content != null; content = content.next)
2218                         {
2219                             printTree(fout, mode, indent, lexer, content);
2220                         }
2221                         return;
2222                     }
2223 
2224                     // replace <nobr> ... </nobr> by &nbsp; or &#160; etc.
2225                     if (node.tag == tt.tagNobr)
2226                     {
2227                         for (content = node.content; content != null; content = content.next)
2228                         {
2229                             printTree(fout, (short) (mode | NOWRAP), indent, lexer, content);
2230                         }
2231                         return;
2232                     }
2233                 }
2234 
2235                 // otherwise a normal inline element
2236 
2237                 printTag(lexer, fout, mode, indent, node);
2238 
2239                 // indent content for SELECT, TEXTAREA, MAP, OBJECT and APPLET
2240 
2241                 if (shouldIndent(node))
2242                 {
2243                     condFlushLine(fout, indent);
2244                     indent += this.configuration.spaces;
2245 
2246                     for (content = node.content; content != null; content = content.next)
2247                     {
2248                         printTree(fout, mode, indent, lexer, content);
2249                     }
2250 
2251                     condFlushLine(fout, indent);
2252                     indent -= this.configuration.spaces;
2253                     condFlushLine(fout, indent);
2254                 }
2255                 else
2256                 {
2257 
2258                     for (content = node.content; content != null; content = content.next)
2259                     {
2260                         printTree(fout, mode, indent, lexer, content);
2261                     }
2262                 }
2263 
2264                 printEndTag(mode, indent, node);
2265             }
2266             else
2267             {
2268                 // other tags
2269                 condFlushLine(fout, indent);
2270 
2271                 if (this.configuration.smartIndent && node.prev != null)
2272                 {
2273                     flushLine(fout, indent);
2274                 }
2275 
2276                 // do not omit elements with attributes
2277                 if (!this.configuration.hideEndTags
2278                     || !(node.tag != null && TidyUtils.toBoolean(node.tag.model & Dict.CM_OMITST))
2279                     || node.attributes != null)
2280                 {
2281                     printTag(lexer, fout, mode, indent, node);
2282 
2283                     if (shouldIndent(node))
2284                     {
2285                         condFlushLine(fout, indent);
2286                     }
2287                     else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_HTML)
2288                         || node.tag == tt.tagNoframes
2289                         || (TidyUtils.toBoolean(node.tag.model & Dict.CM_HEAD) && !(node.tag == tt.tagTitle)))
2290                     {
2291                         flushLine(fout, indent);
2292                     }
2293                 }
2294 
2295                 if (node.tag == tt.tagBody && this.configuration.burstSlides)
2296                 {
2297                     printSlide(fout, mode, (this.configuration.indentContent
2298                         ? indent + this.configuration.spaces
2299                         : indent), lexer);
2300                 }
2301                 else
2302                 {
2303                     last = null;
2304 
2305                     for (content = node.content; content != null; content = content.next)
2306                     {
2307                         // kludge for naked text before block level tag
2308                         if (last != null
2309                             && !this.configuration.indentContent
2310                             && last.type == Node.TEXT_NODE
2311                             && content.tag != null
2312                             && !TidyUtils.toBoolean(content.tag.model & Dict.CM_INLINE))
2313                         {
2314                             flushLine(fout, indent);
2315                         }
2316 
2317                         printTree(
2318                             fout,
2319                             mode,
2320                             (shouldIndent(node) ? indent + this.configuration.spaces : indent),
2321                             lexer,
2322                             content);
2323 
2324                         last = content;
2325                     }
2326                 }
2327 
2328                 // don't flush line for td and th
2329                 if (shouldIndent(node)
2330                     || ((TidyUtils.toBoolean(node.tag.model & Dict.CM_HTML) || node.tag == tt.tagNoframes || //
2331                     (TidyUtils.toBoolean(node.tag.model & Dict.CM_HEAD) && !(node.tag == tt.tagTitle))) && //
2332                     !this.configuration.hideEndTags))
2333                 {
2334                     condFlushLine(
2335                         fout,
2336                         (this.configuration.indentContent ? indent + this.configuration.spaces : indent));
2337 
2338                     if (!this.configuration.hideEndTags || !TidyUtils.toBoolean(node.tag.model & Dict.CM_OPT))
2339                     {
2340                         printEndTag(mode, indent, node);
2341 
2342                         // #603128 tidy adds newslines after </html> tag
2343                         // Fix by Fabrizio Giustina 12-02-2004
2344                         // fix is different from the one in original tidy
2345                         if (!lexer.seenEndHtml)
2346                         {
2347                             flushLine(fout, indent);
2348                         }
2349                     }
2350                 }
2351                 else
2352                 {
2353                     if (!this.configuration.hideEndTags || !TidyUtils.toBoolean(node.tag.model & Dict.CM_OPT))
2354                     {
2355                         printEndTag(mode, indent, node);
2356                     }
2357 
2358                     flushLine(fout, indent);
2359                 }
2360 
2361                 // FG commented out: double newlines
2362                 // if (!this.configuration.indentContent
2363                 // && node.next != null
2364                 // && !this.configuration.hideEndTags
2365                 // && (node.tag.model
2366                 // & TidyUtils.toBoolean(Dict.CM_BLOCK | Dict.CM_TABLE | Dict.CM_LIST | Dict.CM_DEFLIST)))
2367                 // {
2368                 // flushLine(fout, indent);
2369                 // }
2370             }
2371         }
2372     }
2373 
2374     /**
2375      * @param fout
2376      * @param mode
2377      * @param indent
2378      * @param lexer
2379      * @param node
2380      */
2381     public void printXMLTree(Out fout, short mode, int indent, Lexer lexer, Node node)
2382     {
2383         TagTable tt = this.configuration.tt;
2384 
2385         if (node == null)
2386         {
2387             return;
2388         }
2389 
2390         if (node.type == Node.TEXT_NODE || (node.type == Node.CDATA_TAG && lexer.configuration.escapeCdata))
2391         {
2392             printText(fout, mode, indent, node.textarray, node.start, node.end);
2393         }
2394         else if (node.type == Node.COMMENT_TAG)
2395         {
2396             condFlushLine(fout, indent);
2397             printComment(fout, 0, node);
2398             condFlushLine(fout, 0);
2399         }
2400         else if (node.type == Node.ROOT_NODE)
2401         {
2402             Node content;
2403 
2404             for (content = node.content; content != null; content = content.next)
2405             {
2406                 printXMLTree(fout, mode, indent, lexer, content);
2407             }
2408         }
2409         else if (node.type == Node.DOCTYPE_TAG)
2410         {
2411             printDocType(fout, indent, lexer, node);
2412         }
2413         else if (node.type == Node.PROC_INS_TAG)
2414         {
2415             printPI(fout, indent, node);
2416         }
2417         else if (node.type == Node.XML_DECL)
2418         {
2419             printXmlDecl(fout, indent, node);
2420         }
2421         else if (node.type == Node.CDATA_TAG)
2422         {
2423             printCDATA(fout, indent, node);
2424         }
2425         else if (node.type == Node.SECTION_TAG)
2426         {
2427             printSection(fout, indent, node);
2428         }
2429         else if (node.type == Node.ASP_TAG)
2430         {
2431             printAsp(fout, indent, node);
2432         }
2433         else if (node.type == Node.JSTE_TAG)
2434         {
2435             printJste(fout, indent, node);
2436         }
2437         else if (node.type == Node.PHP_TAG)
2438         {
2439             printPhp(fout, indent, node);
2440         }
2441         else if (TidyUtils.toBoolean(node.tag.model & Dict.CM_EMPTY)
2442             || node.type == Node.START_END_TAG
2443             && !configuration.xHTML)
2444         {
2445             condFlushLine(fout, indent);
2446             printTag(lexer, fout, mode, indent, node);
2447             // fgiust: Remove empty lines between tags in XML.
2448             // flushLine(fout, indent);
2449 
2450             // CPR: folks don't want so much vertical spacing in XML
2451             // if (node.next != null) { flushLine(fout, indent); }
2452 
2453         }
2454         else
2455         {
2456             // some kind of container element
2457             Node content;
2458             boolean mixed = false;
2459             int cindent;
2460 
2461             for (content = node.content; content != null; content = content.next)
2462             {
2463                 if (content.type == Node.TEXT_NODE)
2464                 {
2465                     mixed = true;
2466                     break;
2467                 }
2468             }
2469 
2470             condFlushLine(fout, indent);
2471 
2472             if (ParserImpl.XMLPreserveWhiteSpace(node, tt))
2473             {
2474                 indent = 0;
2475                 cindent = 0;
2476                 mixed = false;
2477             }
2478             else if (mixed)
2479             {
2480                 cindent = indent;
2481             }
2482             else
2483             {
2484                 cindent = indent + this.configuration.spaces;
2485             }
2486 
2487             printTag(lexer, fout, mode, indent, node);
2488 
2489             if (!mixed && node.content != null)
2490             {
2491                 flushLine(fout, indent);
2492             }
2493 
2494             for (content = node.content; content != null; content = content.next)
2495             {
2496                 printXMLTree(fout, mode, cindent, lexer, content);
2497             }
2498 
2499             if (!mixed && node.content != null)
2500             {
2501                 condFlushLine(fout, cindent);
2502             }
2503             printEndTag(mode, indent, node);
2504             // condFlushLine(fout, indent);
2505 
2506             // CPR: folks don't want so much vertical spacing in XML
2507             // if (node.next != null) { flushLine(fout, indent); }
2508 
2509         }
2510     }
2511 
2512     /**
2513      * Split parse tree by h2 elements and output to separate files. Counts number of h2 children (if any) belonging to
2514      * node.
2515      * @param node root node
2516      * @return number of slides (number of h2 elements)
2517      */
2518     public int countSlides(Node node)
2519     {
2520         // assume minimum of 1 slide
2521         int n = 1;
2522 
2523         TagTable tt = this.configuration.tt;
2524 
2525         // fix for [431716] avoid empty slides
2526         if (node != null && node.content != null && node.content.tag == tt.tagH2)
2527         {
2528             // "first" slide is empty, so ignore it
2529             n--;
2530         }
2531 
2532         if (node != null)
2533         {
2534             for (node = node.content; node != null; node = node.next)
2535             {
2536                 if (node.tag == tt.tagH2)
2537                 {
2538                     ++n;
2539                 }
2540             }
2541         }
2542 
2543         return n;
2544     }
2545 
2546     /**
2547      * @param fout
2548      * @param indent
2549      */
2550     private void printNavBar(Out fout, int indent)
2551     {
2552         String buf;
2553 
2554         condFlushLine(fout, indent);
2555         printString("<center><small>");
2556 
2557         NumberFormat numberFormat = NumberFormat.getInstance();
2558         numberFormat.setMinimumIntegerDigits(3);
2559 
2560         if (slide > 1)
2561         {
2562             buf = "<a href=\"slide" + numberFormat.format(slide - 1) + ".html\">previous</a> | ";
2563             // #427666 - fix by Eric Rossen 02 Aug 00
2564             printString(buf);
2565             condFlushLine(fout, indent);
2566 
2567             if (slide < count)
2568             {
2569                 printString("<a href=\"slide001.html\">start</a> | ");
2570                 // #427666 - fix by Eric Rossen 02 Aug 00
2571             }
2572             else
2573             {
2574                 printString("<a href=\"slide001.html\">start</a>");
2575                 // #427666 - fix by Eric Rossen 02 Aug 00
2576             }
2577 
2578             condFlushLine(fout, indent);
2579         }
2580 
2581         if (slide < count)
2582         {
2583             buf = "<a href=\"slide" + numberFormat.format(slide + 1) + ".html\">next</a>";
2584             // #427666 - fix by Eric Rossen 02 Aug 00
2585             printString(buf);
2586         }
2587 
2588         printString("</small></center>");
2589         condFlushLine(fout, indent);
2590     }
2591 
2592     /**
2593      * Called from printTree to print the content of a slide from the node slidecontent. On return slidecontent points
2594      * to the node starting the next slide or null. The variables slide and count are used to customise the navigation
2595      * bar.
2596      * @param fout
2597      * @param mode
2598      * @param indent
2599      * @param lexer
2600      */
2601     public void printSlide(Out fout, short mode, int indent, Lexer lexer)
2602     {
2603         Node content, last;
2604         TagTable tt = this.configuration.tt;
2605 
2606         NumberFormat numberFormat = NumberFormat.getInstance();
2607         numberFormat.setMinimumIntegerDigits(3);
2608 
2609         /* insert div for onclick handler */
2610         String s;
2611         s = "<div onclick=\"document.location='slide"
2612             + numberFormat.format(slide < count ? slide + 1 : 1)
2613             + ".html'\">";
2614         // #427666 - fix by Eric Rossen 02 Aug 00
2615         printString(s);
2616         condFlushLine(fout, indent);
2617 
2618         /* first print the h2 element and navbar */
2619         if (slidecontent != null && slidecontent.tag == tt.tagH2)
2620         {
2621             printNavBar(fout, indent);
2622 
2623             /* now print an hr after h2 */
2624 
2625             addC('<', linelen++);
2626 
2627             addC(TidyUtils.foldCase('h', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2628             addC(TidyUtils.foldCase('r', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2629 
2630             if (this.configuration.xmlOut)
2631             {
2632                 printString(" />");
2633             }
2634             else
2635             {
2636                 addC('>', linelen++);
2637             }
2638 
2639             if (this.configuration.indentContent)
2640             {
2641                 condFlushLine(fout, indent);
2642             }
2643 
2644             // PrintVertSpacer(fout, indent);
2645 
2646             // condFlushLine(fout, indent);
2647 
2648             // print the h2 element
2649             printTree(
2650                 fout,
2651                 mode,
2652                 (this.configuration.indentContent ? indent + this.configuration.spaces : indent),
2653                 lexer,
2654                 slidecontent);
2655 
2656             slidecontent = slidecontent.next;
2657         }
2658 
2659         // now continue until we reach the next h2
2660 
2661         last = null;
2662         content = slidecontent;
2663 
2664         for (; content != null; content = content.next)
2665         {
2666             if (content.tag == tt.tagH2)
2667             {
2668                 break;
2669             }
2670 
2671             // kludge for naked text before block level tag
2672             if (last != null
2673                 && !this.configuration.indentContent
2674                 && last.type == Node.TEXT_NODE
2675                 && content.tag != null
2676                 && TidyUtils.toBoolean(content.tag.model & Dict.CM_BLOCK))
2677             {
2678                 flushLine(fout, indent);
2679                 flushLine(fout, indent);
2680             }
2681 
2682             printTree(
2683                 fout,
2684                 mode,
2685                 (this.configuration.indentContent ? indent + this.configuration.spaces : indent),
2686                 lexer,
2687                 content);
2688 
2689             last = content;
2690         }
2691 
2692         slidecontent = content;
2693 
2694         // now print epilog
2695 
2696         condFlushLine(fout, indent);
2697 
2698         printString("<br clear=\"all\">");
2699         condFlushLine(fout, indent);
2700 
2701         addC('<', linelen++);
2702 
2703         addC(TidyUtils.foldCase('h', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2704         addC(TidyUtils.foldCase('r', this.configuration.upperCaseTags, this.configuration.xmlTags), linelen++);
2705 
2706         if (this.configuration.xmlOut)
2707         {
2708             printString(" />");
2709         }
2710         else
2711         {
2712             addC('>', linelen++);
2713         }
2714 
2715         if (this.configuration.indentContent)
2716         {
2717             condFlushLine(fout, indent);
2718         }
2719 
2720         printNavBar(fout, indent);
2721 
2722         // end tag for div
2723         printString("</div>");
2724         condFlushLine(fout, indent);
2725     }
2726 
2727     /**
2728      * Add meta element for page transition effect, this works on IE but not NS.
2729      * @param lexer
2730      * @param root
2731      * @param duration
2732      */
2733     public void addTransitionEffect(Lexer lexer, Node root, double duration)
2734     {
2735         Node head = root.findHEAD(lexer.configuration.tt);
2736         String transition;
2737 
2738         transition = "blendTrans(Duration=" + (new Double(duration)).toString() + ")";
2739 
2740         if (head != null)
2741         {
2742             Node meta = lexer.inferredTag("meta");
2743             meta.addAttribute("http-equiv", "Page-Enter");
2744             meta.addAttribute("content", transition);
2745             head.insertNodeAtStart(meta);
2746         }
2747     }
2748 
2749     /**
2750      * Creates slides from h2.
2751      * @param lexer Lexer
2752      * @param root root node
2753      */
2754     public void createSlides(Lexer lexer, Node root)
2755     {
2756         Node body;
2757         String buf;
2758 
2759         NumberFormat numberFormat = NumberFormat.getInstance();
2760         numberFormat.setMinimumIntegerDigits(3);
2761 
2762         body = root.findBody(lexer.configuration.tt);
2763         count = countSlides(body);
2764         slidecontent = body.content;
2765 
2766         addTransitionEffect(lexer, root, 3.0);
2767 
2768         for (slide = 1; slide <= count; ++slide)
2769         {
2770             buf = "slide" + numberFormat.format(slide) + ".html";
2771 
2772             try
2773             {
2774                 FileOutputStream fis = new FileOutputStream(buf);
2775                 Out out = OutFactory.getOut(configuration, fis);
2776 
2777                 printTree(out, (short) 0, 0, lexer, root);
2778                 flushLine(out, 0);
2779 
2780                 fis.close();
2781             }
2782             catch (IOException e)
2783             {
2784                 System.err.println(buf + e.toString());
2785             }
2786         }
2787 
2788         // delete superfluous slides by deleting slideN.html for N = count+1, count+2, etc.
2789         // until no such file is found.
2790 
2791         // #427666 - fix by Eric Rossen 02 Aug 00
2792         while ((new File("slide" + numberFormat.format(slide) + ".html")).delete())
2793         {
2794             ++slide;
2795         }
2796     }
2797 
2798 }