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
24import java.io.BufferedWriter;
25import java.io.File;
26import java.io.IOException;
27import java.io.Writer;
28import java.net.URL;
29import java.text.MessageFormat;
30import java.util.ResourceBundle;
31
32/**
33 * A class to facilitate writing HTML via a stream.
34 */
35public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
454    public void writeI18N(String key, Object[] args) throws IOException {
455        write(getString(i18n, key, args));
456    }
457
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