1 /*
2  * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.BufferedWriter;
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.Writer;
28 import java.net.URL;
29 import java.text.MessageFormat;
30 import java.util.ResourceBundle;
31 
32 /**
33  * A class to facilitate writing HTML via a stream.
34  */
35 public class HTMLWriter
36 {
37     /**
38      * Create an HTMLWriter object, using a default doctype for HTML 3.2.
39      * @param out a Writer to which to write the generated HTML
40      * @throws IOException if there is a problem writing to the underlying stream
41      */
HTMLWriter(Writer out)42     public HTMLWriter(Writer out) throws IOException {
43         this(out, "<!DOCTYPE html\">");
44     }
45 
46     /**
47      * Create an HTMLWriter object, using a specifed doctype header.
48      * @param out a Writer to which to write the generated HTML
49      * @param docType a string containing a doctype header for the HTML to be generetaed
50      * @throws IOException if there is a problem writing to the underlying stream
51      */
HTMLWriter(Writer out, String docType)52     public HTMLWriter(Writer out, String docType) throws IOException {
53         if (out instanceof BufferedWriter)
54             this.out = (BufferedWriter) out;
55         else
56             this.out = new BufferedWriter(out);
57         this.out.write(docType);
58         this.out.newLine();
59     }
60 
61     /**
62      * Create an HTMLWriter object, using a specified bundle for localizing messages.
63      * @param out a Writer to which to write the generated HTML
64      * @param i18n a resource bundle to use to localize messages
65      * @throws IOException if there is a problem writing to the underlying stream
66      */
HTMLWriter(Writer out, ResourceBundle i18n)67     public HTMLWriter(Writer out, ResourceBundle i18n) throws IOException {
68         this(out);
69         this.i18n = i18n;
70     }
71 
72 
73     /**
74      * Create an HTMLWriter object, using a specifed doctype header and
75      * using a specified bundle for l0calizing messages.
76      * @param out a Writer to which to write the generated HTML
77      * @param docType a string containing a doctype header for the HTML to be generetaed
78      * @param i18n a resource bundle to use to localize messages
79      * @throws IOException if there is a problem writing to the underlying stream
80      */
HTMLWriter(Writer out, String docType, ResourceBundle i18n)81     public HTMLWriter(Writer out, String docType, ResourceBundle i18n) throws IOException {
82         this(out, docType);
83         this.i18n = i18n;
84     }
85 
86     /**
87      * Set the reource bundle to be used for localizing messages.
88      * @param i18n the resource bundle to be used for localizing messages
89      */
setResourceBundle(ResourceBundle i18n)90     public void setResourceBundle(ResourceBundle i18n) {
91         this.i18n = i18n;
92     }
93 
94     /**
95      * Flush the stream, and the underlying output stream.
96      * @throws IOException if there is a problem writing to the underlying stream
97      */
flush()98     public void flush() throws IOException {
99         out.flush();
100     }
101 
102     /**
103      * Close the stream, and the underlying output stream.
104      * @throws IOException if there is a problem closing the underlying stream
105      */
close()106     public void close() throws IOException {
107         out.close();
108     }
109 
110     /**
111      * Write a newline to the underlying output stream.
112      * @throws IOException if there is a problem writing to the underlying stream
113      */
newLine()114     public void newLine() throws IOException {
115         out.newLine();
116     }
117 
118     /**
119      * Start an HTML tag.  If a prior tag has been started, it will
120      * be closed first. Once a tag has been opened, attributes for the
121      * tag may be written out, followed by body content before finally
122      * ending the tag.
123      * @param tag the tag to be started
124      * @throws IOException if there is a problem writing to the underlying stream
125      * @see #writeAttr
126      * @see #write
127      * @see #endTag
128      */
startTag(String tag)129     public void startTag(String tag) throws IOException {
130         if (state == IN_TAG) {
131             out.write(">");
132             state = IN_BODY;
133         }
134         //newLine();
135         out.write("<");
136         out.write(tag);
137         state = IN_TAG;
138     }
139 
140     /**
141      * Finish an HTML tag. It is expected that a call to endTag will match
142      * a corresponding earlier call to startTag, but there is no formal check
143      * for this.
144      * @param tag the tag to be closed.
145      * @throws IOException if there is a problem writing to the underlying stream
146      */
endTag(String tag)147     public void endTag(String tag) throws IOException {
148         if (state == IN_TAG) {
149             out.write(">");
150             state = IN_BODY;
151             out.newLine();
152         }
153         out.write("</");
154         out.write(tag);
155         out.write(">");
156         //out.newLine();   // PATCHED, jjg
157         state = IN_BODY;
158     }
159 
160     /**
161      * Finish an empty element tag, such as a META, BASE or LINK tag.
162      * This is expected to correspond with a startTag.
163      * @param tag the tag which is being closed.  this is only useful for
164      *        validation, it is not written out
165      * @throws IllegalStateException if this call does not follow startTag
166      *         (stream is not currently inside a tag)
167      * @throws IOException if there is a problem writing to the underlying stream
168      */
endEmptyTag(String tag)169     public void endEmptyTag(String tag) throws IOException {
170         if (state != IN_TAG)
171             throw new IllegalStateException();
172 
173         out.write(">");
174         state = IN_BODY;
175         out.newLine();
176     }
177 
178     /**
179      * Write an attribute for a tag. A tag must previously have been started.
180      * All tag attributes must be written before any body text is written.
181      * The value will be quoted if necessary when writing it to the underlying
182      * stream. No check is made that the attribute is valid for the current tag.
183      * @param name the name of the attribute to be written
184      * @param value the value of the attribute to be written
185      * @throws IllegalStateException if the stream is not in a state to
186      * write attributes -- e.g. if this call does not follow startTag or other
187      * calls of writteAttr
188      * @throws IOException if there is a problem writing to the underlying stream
189      */
writeAttr(String name, String value)190     public void writeAttr(String name, String value) throws IOException {
191         if (state != IN_TAG)
192             throw new IllegalStateException();
193 
194         out.write(" ");
195         out.write(name);
196         out.write("=");
197         boolean alpha = true;
198         for (int i = 0; i < value.length() && alpha; i++)
199             alpha = Character.isLetter(value.charAt(i));
200         if (!alpha)
201             out.write("\"");
202         out.write(value);
203         if (!alpha)
204             out.write("\"");
205     }
206 
207     /**
208      * Write an attribute for a tag. A tag must previously have been started.
209      * All tag attributes must be written before any body text is written.
210      * The value will be quoted if necessary when writing it to the underlying
211      * stream. No check is made that the attribute is valid for the current tag.
212      * @param name the name of the attribute to be written
213      * @param value the value of the attribute to be written
214      * @throws IllegalStateException if the stream is not in a state to
215      * write attributes -- e.g. if this call does not follow startTag or other
216      * calls of writteAttr
217      * @throws IOException if there is a problem writing to the underlying stream
218      */
writeAttr(String name, int value)219     public void writeAttr(String name, int value) throws IOException {
220         writeAttr(name, Integer.toString(value));
221     }
222 
223     /**
224      * Write a line of text, followed by a newline.
225      * The text will be escaped as necessary.
226      * @param text the text to be written.
227      * @throws IOException if there is a problem closing the underlying stream
228      */
writeLine(String text)229     public void writeLine(String text) throws IOException {
230         write(text);
231         out.newLine();
232     }
233 
234     /**
235      * Write body text, escaping it as necessary.
236      * If this call follows a call of startTag, the open tag will be
237      * closed -- meaning that no more attributes can be written until another
238      * tag is started.  If the text value is null, the current tag will still
239      * be closed, but no other text will be written.
240      * @param text the text to be written, may be null or zero length.
241      * @throws IOException if there is a problem writing to the underlying stream
242      */
write(String text)243     public void write(String text) throws IOException {
244         if (state == IN_TAG) {
245             out.write(">");
246             state = IN_BODY;
247         }
248 
249         if (text == null)
250             return;
251 
252         // check to see if there are any special characters
253         boolean specialChars = false;
254         for (int i = 0; i < text.length() && !specialChars; i++) {
255             switch (text.charAt(i)) {
256             case '<': case '>': case '&':
257                 specialChars = true;
258             }
259         }
260 
261         // if there are special characters write the string character at a time;
262         // otherwise, write it out as is
263         if (specialChars) {
264             for (int i = 0; i < text.length(); i++) {
265                 char c = text.charAt(i);
266                 switch (c) {
267                 case '<': out.write("&lt;"); break;
268                 case '>': out.write("&gt;"); break;
269                 case '&': out.write("&amp;"); break;
270                 default: out.write(c);
271                 }
272             }
273         }
274         else
275             out.write(text);
276     }
277 
278     /**
279      * Write a basic HTML entity, such as &nbsp; or &#123; .
280      * @param entity the entity to write
281      * @throws IOException if there is a problem writing to the underlying stream
282      */
writeEntity(String entity)283     public void writeEntity(String entity) throws IOException {
284         if (state == IN_TAG) {
285             out.write(">");
286             state = IN_BODY;
287         }
288         out.write(entity);
289     }
290 
291     /**
292      * Write an image tag, using a specified path for the image source attribute.
293      * @param imagePath the path for the image source
294      * @throws IOException if there is a problem closing the underlying stream
295      */
writeImage(String imagePath)296     public void writeImage(String imagePath) throws IOException {
297         startTag(IMAGE);
298         writeAttr(SRC, imagePath);
299     }
300 
301     /**
302      * Write an image tag, using a specified path for the image source attribute.
303      * @param imageURL the url for the image source
304      * @throws IOException if there is a problem closing the underlying stream
305      */
writeImage(URL imageURL)306     public void writeImage(URL imageURL) throws IOException {
307         writeImage(imageURL.toString());
308     }
309 
310     /**
311      * Write a hypertext link.
312      * @param anchor the target for the link
313      * @param body the body text for the link
314      * @throws IOException if there is a problem closing the underlying stream
315      */
writeLink(String anchor, String body)316     public void writeLink(String anchor, String body) throws IOException {
317         startTag(A);
318         writeAttr(HREF, anchor);
319         write(body);
320         endTag(A);
321     }
322 
323     /**
324      * Write a hypertext link.
325      * @param file the target for the link
326      * @param body the body text for the link
327      * @throws IOException if there is a problem closing the underlying stream
328      */
writeLink(File file, String body)329     public void writeLink(File file, String body) throws IOException {
330         startTag(A);
331         StringBuilder sb = new StringBuilder();
332         String path = file.getPath().replace(File.separatorChar, '/');
333         if (file.isAbsolute() && !path.startsWith("/"))
334             sb.append('/');
335         sb.append(path);
336         writeAttr(HREF, sb.toString());
337         write(body);
338         endTag(A);
339     }
340 
341     /**
342      * Write a hypertext link.
343      * @param file the target and body for the link
344      * @throws IOException if there is a problem closing the underlying stream
345      */
writeLink(File file)346     public void writeLink(File file) throws IOException {
347         writeLink(file, file.getPath());
348     }
349 
350     /**
351      * Write a hypertext link.
352      * @param url the target for the link
353      * @param body the body text for the link
354      * @throws IOException if there is a problem closing the underlying stream
355      */
writeLink(URL url, String body)356     public void writeLink(URL url, String body) throws IOException {
357         startTag(A);
358         writeAttr(HREF, url.toString());
359         write(body);
360         endTag(A);
361     }
362 
363     /**
364      * Write the destination marker for a hypertext link.
365      * @param anchor the destination marker for hypertext links
366      * @param body the body text for the marker
367      * @throws IOException if there is a problem closing the underlying stream
368      */
writeLinkDestination(String anchor, String body)369     public void writeLinkDestination(String anchor, String body) throws IOException {
370         startTag(A);
371         writeAttr(NAME, anchor);
372         write(body);
373         endTag(A);
374     }
375 
376     /**
377      * Write a parameter tag.
378      * @param name the name of the parameter
379      * @param value the value of the parameter
380      * @throws IOException if there is a problem closing the underlying stream
381      */
writeParam(String name, String value)382     public void writeParam(String name, String value) throws IOException {
383         startTag(PARAM);
384         writeAttr(NAME, name);
385         writeAttr(VALUE, value);
386     }
387 
388     /**
389      * Write a style attribute.
390      * @param value the value for the style atrtribute
391      * @throws IOException if there is a problem closing the underlying stream
392      */
writeStyleAttr(String value)393     public void writeStyleAttr(String value) throws IOException {
394         writeAttr(STYLE, value);
395     }
396 
397     /**
398      * Write a localized message, using a specified resource bundle.
399      * @param i18n the resource bundle used to localize the message
400      * @param key the key for the message to be localized
401      * @throws IOException if there is a problem closing the underlying stream
402      */
write(ResourceBundle i18n, String key)403     public void write(ResourceBundle i18n, String key) throws IOException {
404         write(getString(i18n, key));
405     }
406 
407     /**
408      * Write a localized message, using a specified resource bundle.
409      * @param i18n the resource bundle used to localize the message
410      * @param key the key for the message to be localized
411      * @param arg an argument to be formatted into the localized message
412      * @throws IOException if there is a problem closing the underlying stream
413      */
write(ResourceBundle i18n, String key, Object arg)414     public void write(ResourceBundle i18n, String key, Object arg) throws IOException {
415         write(getString(i18n, key, arg));
416     }
417 
418     /**
419      * Write a localized message, using a specified resource bundle.
420      * @param i18n the resource bundle used to localize the message
421      * @param key the key for the message to be localized
422      * @param args arguments to be formatted into the localized message
423      * @throws IOException if there is a problem closing the underlying stream
424      */
write(ResourceBundle i18n, String key, Object[] args)425     public void write(ResourceBundle i18n, String key, Object[] args) throws IOException {
426         write(getString(i18n, key, args));
427     }
428 
429     /**
430      * Write a localized message, using the default resource bundle.
431      * @param key the key for the message to be localized
432      * @throws IOException if there is a problem closing the underlying stream
433      */
writeI18N(String key)434     public void writeI18N(String key) throws IOException {
435         write(getString(i18n, key));
436     }
437 
438     /**
439      * Write a localized message, using the default resource bundle.
440      * @param key the key for the message to be localized
441      * @param arg an argument to be formatted into the localized message
442      * @throws IOException if there is a problem closing the underlying stream
443      */
writeI18N(String key, Object arg)444     public void writeI18N(String key, Object arg) throws IOException {
445         write(getString(i18n, key, arg));
446     }
447 
448     /**
449      * Write a localized message, using the default resource bundle.
450      * @param key the key for the message to be localized
451      * @param args arguments to be formatted into the localized message
452      * @throws IOException if there is a problem closing the underlying stream
453      */
writeI18N(String key, Object[] args)454     public void writeI18N(String key, Object[] args) throws IOException {
455         write(getString(i18n, key, args));
456     }
457 
getString(ResourceBundle rb, String key, Object... args)458     private String getString(ResourceBundle rb, String key, Object... args) {
459         String s = rb.getString(key);
460         return MessageFormat.format(s, args);
461     }
462 
463     /** The HTML "a" tag. */
464     public static final String A = "a";
465     /** The HTML "align" attribute. */
466     public static final String ALIGN = "align";
467     /** The HTML "b" tag. */
468     public static final String B = "b";
469     /** The HTML "body" tag. */
470     public static final String BODY = "body";
471     /** The HTML "border" attribute. */
472     public static final String BORDER = "border";
473     /** The HTML "br" tag. */
474     public static final String BR = "br";
475     /** The HTML "charset" attribute. */
476     public static final String CHARSET  = "charset";
477     /** The HTML "class" attribute. */
478     public static final String CLASS  = "class";
479     /** The HTML "classid" attribute. */
480     public static final String CLASSID  = "classid";
481     /** The HTML "code" tag. */
482     public static final String CODE  = "code";
483     /** The HTML "color" attribute. */
484     public static final String COLOR  = "color";
485     /** The HTML "col" attribute value. */
486     public static final String COL = "col";
487     /** The HTML "dd" tag. */
488     public static final String DD = "dd";
489     /** The HTML "div" tag. */
490     public static final String DIV = "div";
491     /** The HTML "dl" tag. */
492     public static final String DL = "dl";
493     /** The HTML "dt" tag. */
494     public static final String DT = "dt";
495     /** The HTML "font" tag. */
496     public static final String FONT = "font";
497     /** The HTML "h1" tag. */
498     public static final String H1 = "h1";
499     /** The HTML "h2" tag. */
500     public static final String H2 = "h2";
501     /** The HTML "h3" tag. */
502     public static final String H3 = "h3";
503     /** The HTML "h4" tag. */
504     public static final String H4 = "h4";
505     /** The HTML "h5" tag. */
506     public static final String H5 = "h5";
507     /** The HTML "head" tag. */
508     public static final String HEAD = "head";
509     /** The HTML "href" attribute. */
510     public static final String HREF = "href";
511     /** The HTML "html" tag. */
512     public static final String HTML = "html";
513     /** The HTML "hr" tag. */
514     public static final String HR = "hr";
515     /** The HTML "i" tag. */
516     public static final String I = "i";
517     /** The HTML "id" tag. */
518     public static final String ID = "id";
519     /** The HTML "image" tag. */
520     public static final String IMAGE = "image";
521     /** The HTML "left" attribute value. */
522     public static final String LEFT = "left";
523     /** The HTML "li" tag. */
524     public static final String LI = "li";
525     /** The HTML "link" tag. */
526     public static final String LINK = "link";
527     /** The HTML "meta" attribute. */
528     public static final String META = "meta";
529     /** The HTML "name" attribute. */
530     public static final String NAME = "name";
531     /** The HTML "object" tag. */
532     public static final String OBJECT = "object";
533     /** The HTML "p" tag. */
534     public static final String PARAM = "param";
535     /** The HTML "param" tag. */
536     public static final String P = "p";
537     /** The HTML "rel" attribute value. */
538     public static final String REL = "rel";
539     /** The HTML "right" attribute value. */
540     public static final String RIGHT = "right";
541     /** The HTML "row" attribute value. */
542     public static final String ROW = "row";
543     /** The HTML "script" tag. */
544     public static final String SCRIPT = "script";
545     /** The HTML "small" tag. */
546     public static final String SMALL = "small";
547     /** The HTML "span" tag. */
548     public static final String SPAN = "span";
549     /** The HTML "src" attribute. */
550     public static final String SRC = "src";
551     /** The HTML "scope" attribute. */
552     public static final String SCOPE = "scope";
553     /** The HTML "style" attribute. */
554     public static final String STYLE = "style";
555     /** The HTML "table" tag. */
556     public static final String TABLE = "table";
557     /** The HTML "td" tag. */
558     public static final String TD = "td";
559     /** The HTML type for JavaScript. */
560     public static final String TEXT_JAVASCRIPT = "text/javascript";
561     /** The HTML "title"attribute. */
562     public static final String TITLE = "title";
563     /** The HTML "th" tag. */
564     public static final String TH = "th";
565     /** The HTML "top" attribute value. */
566     public static final String TOP = "top";
567     /** The HTML "tr" tag. */
568     public static final String TR = "tr";
569     /** The HTML "type" attribute. */
570     public static final String TYPE = "type";
571     /** The HTML "ul" tag. */
572     public static final String UL = "ul";
573     /** The HTML "valign" attribute. */
574     public static final String VALIGN = "valign";
575     /** The HTML "value" attribute. */
576     public static final String VALUE = "value";
577 
578 
579     private BufferedWriter out;
580     private int state;
581     private ResourceBundle i18n;
582     private static final int IN_TAG = 1;
583     private static final int IN_BODY = 2;
584 }
585