Home | History | Annotate | Download | only in jheer
      1 /**
      2  * Copyright (c) 2004-2006 Regents of the University of California.
      3   All rights reserved.
      4 
      5   Redistribution and use in source and binary forms, with or without
      6   modification, are permitted provided that the following conditions
      7   are met:
      8 
      9   1. Redistributions of source code must retain the above copyright
     10   notice, this list of conditions and the following disclaimer.
     11 
     12   2. Redistributions in binary form must reproduce the above copyright
     13   notice and this list of conditions.
     14 
     15   3. The name of the University may not be used to endorse or promote products
     16   derived from this software without specific prior written permission.
     17 
     18   THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     19   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     21   ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     22   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     23   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     24   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     25   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     26   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     27   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     28   SUCH DAMAGE.
     29  */
     30 
     31 package org.jheer;
     32 
     33 //import java.io.PrintWriter;
     34 import java.io.FileWriter;
     35 import java.io.IOException;
     36 import java.util.ArrayList;
     37 
     38 /**
     39  * Utility class for writing XML files. This class provides convenience
     40  * methods for creating XML documents, such as starting and ending
     41  * tags, and adding content and comments. This class handles correct
     42  * XML formatting and will properly escape text to ensure that the
     43  * text remains valid XML.
     44  *
     45  * <p>To use this class, create a new instance with the desired
     46  * [Print]FileWriter to write the XML to. Call the {@link #begin()} or
     47  * {@link #begin(String, int)} method when ready to start outputting
     48  * XML. Then use the provided methods to generate the XML file.
     49  * Finally, call either the {@link #finish()} or {@link #finish(String)}
     50  * methods to signal the completion of the file.</p>
     51  *
     52  * @author <a href="http://jheer.org">jeffrey heer</a>
     53  *
     54  * Modified to take a FileWriter and now throws IOException.
     55  */
     56 
     57 public class XMLWriter {
     58 
     59 //    private PrintWriter m_out;
     60     private FileWriter m_out;
     61     private int m_bias = 0;
     62     private int m_tab;
     63     private ArrayList m_tagStack = new ArrayList();
     64 
     65     /**
     66      * Create a new XMLWriter.
     67      * @param out the  FileWriter to write the XML to
     68      */
     69 //    public XMLWriter(PrintWriter out) {
     70     public XMLWriter(FileWriter out) {
     71         this(out, 2);
     72     }
     73 
     74     /**
     75      * Create a new XMLWriter.
     76      * @param out the FileWriter to write the XML to
     77      * @param tabLength the number of spaces to use for each
     78      *  level of indentation in the XML file
     79      */
     80 //    public XMLWriter(PrintWriter out, int tabLength) {
     81     public XMLWriter(FileWriter out, int tabLength) {
     82         m_out = out;
     83         m_tab = 2;
     84     }
     85 
     86     /**
     87      * Write <em>unescaped</em> text into the XML file. To write
     88      * escaped text, use the {@link #content(String)} method instead.
     89      * @param s the text to write. This String will not be escaped.
     90      */
     91     public void write(String s) throws IOException {
     92         m_out.write(s);
     93     }
     94 
     95     /**
     96      * Write <em>unescaped</em> text into the XML file, followed by
     97      * a newline. To write escaped text, use the {@link #content(String)}
     98      * method instead.
     99      * @param s the text to write. This String will not be escaped.
    100      */
    101     public void writeln(String s) throws IOException {
    102         m_out.write(s);
    103         m_out.write("\n");
    104     }
    105 
    106     /**
    107      * Write a newline into the XML file.
    108      */
    109     public void writeln() throws IOException {
    110         m_out.write("\n");
    111     }
    112 
    113     /**
    114      * Begin the XML document. This must be called before any other
    115      * formatting methods. This method writes an XML header into
    116      * the top of the output stream.
    117      */
    118     public void begin() throws IOException {
    119         m_out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    120         writeln();
    121     }
    122 
    123     /**
    124      * Begin the XML document. This must be called before any other
    125      * formatting methods. This method writes an XML header into
    126      * the top of the output stream, plus additional header text
    127      * provided by the client
    128      * @param header header text to insert into the document
    129      * @param bias the spacing bias to use for all subsequent indenting
    130      */
    131     public void begin(String header, int bias) throws IOException {
    132         begin();
    133         m_out.write(header);
    134         m_bias = bias;
    135     }
    136 
    137     /**
    138      * Write a comment in the XML document. The comment will be written
    139      * according to the current spacing and followed by a newline.
    140      * @param comment the comment text
    141      */
    142     public void comment(String comment) throws IOException {
    143         spacing();
    144         m_out.write("<!-- ");
    145         m_out.write(comment);
    146         m_out.write(" -->");
    147         writeln();
    148     }
    149 
    150     /**
    151      * Internal method for writing a tag with attributes.
    152      * @param tag the tag name
    153      * @param names the names of the attributes
    154      * @param values the values of the attributes
    155      * @param nattr the number of attributes
    156      * @param close true to close the tag, false to leave it
    157      * open and adjust the spacing
    158      */
    159     protected void tag(String tag, String[] names, String[] values,
    160             int nattr, boolean close) throws IOException
    161     {
    162         spacing();
    163         m_out.write('<');
    164         m_out.write(tag);
    165         for ( int i=0; i<nattr; ++i ) {
    166             m_out.write(' ');
    167             m_out.write(names[i]);
    168             m_out.write('=');
    169             m_out.write('\"');
    170             escapeString(values[i]);
    171             m_out.write('\"');
    172         }
    173         if ( close ) m_out.write('/');
    174         m_out.write('>');
    175         writeln();
    176 
    177         if ( !close ) {
    178             m_tagStack.add(tag);
    179         }
    180     }
    181 
    182     /**
    183      * Write a closed tag with attributes. The tag will be followed by a
    184      * newline.
    185      * @param tag the tag name
    186      * @param names the names of the attributes
    187      * @param values the values of the attributes
    188      * @param nattr the number of attributes
    189      */
    190     public void tag(String tag, String[] names, String[] values, int nattr) throws IOException
    191     {
    192         tag(tag, names, values, nattr, true);
    193     }
    194 
    195     /**
    196      * Write a start tag with attributes. The tag will be followed by a
    197      * newline, and the indentation level will be increased.
    198      * @param tag the tag name
    199      * @param names the names of the attributes
    200      * @param values the values of the attributes
    201      * @param nattr the number of attributes
    202      */
    203     public void start(String tag, String[] names, String[] values, int nattr) throws IOException
    204     {
    205         tag(tag, names, values, nattr, false);
    206     }
    207 
    208     /**
    209      * Write a new attribut to an existing tag.  The attribute will be followed by a newline.
    210      * @param name the name of the attribute
    211      * @param value the value of the attribute
    212      */
    213      public void addAttribute(String name, String value) throws IOException {
    214         spacing();
    215         m_out.write(name);
    216         m_out.write('=');
    217         m_out.write('\"');
    218         escapeString(value);
    219         m_out.write('\"');
    220         writeln();
    221      }
    222 
    223      /**
    224      * Internal method for writing a tag with a single attribute.
    225      * @param tag the tag name
    226      * @param name the name of the attribute
    227      * @param value the value of the attribute
    228      * @param close true to close the tag, false to leave it
    229      * open and adjust the spacing
    230      */
    231     protected void tag(String tag, String name, String value, boolean close) throws IOException {
    232         spacing();
    233         m_out.write('<');
    234         m_out.write(tag);
    235         m_out.write(' ');
    236         m_out.write(name);
    237         m_out.write('=');
    238         m_out.write('\"');
    239         escapeString(value);
    240         m_out.write('\"');
    241         if ( close ) m_out.write('/');
    242         m_out.write('>');
    243         writeln();
    244 
    245         if ( !close ) {
    246             m_tagStack.add(tag);
    247         }
    248     }
    249 
    250     /**
    251      * Write a closed tag with one attribute. The tag will be followed by a
    252      * newline.
    253      * @param tag the tag name
    254      * @param name the name of the attribute
    255      * @param value the value of the attribute
    256      */
    257     public void tag(String tag, String name, String value) throws IOException
    258     {
    259         tag(tag, name, value, true);
    260     }
    261 
    262     /**
    263      * Write a start tag with one attribute. The tag will be followed by a
    264      * newline, and the indentation level will be increased.
    265      * @param tag the tag name
    266      * @param name the name of the attribute
    267      * @param value the value of the attribute
    268      */
    269     public void start(String tag, String name, String value) throws IOException
    270     {
    271         tag(tag, name, value, false);
    272     }
    273 
    274     /**
    275      * Internal method for writing a tag with attributes.
    276      * @param tag the tag name
    277      * @param names the names of the attributes
    278      * @param values the values of the attributes
    279      * @param nattr the number of attributes
    280      * @param close true to close the tag, false to leave it
    281      * open and adjust the spacing
    282      */
    283     protected void tag(String tag, ArrayList names, ArrayList values,
    284             int nattr, boolean close) throws IOException
    285     {
    286         spacing();
    287         m_out.write('<');
    288         m_out.write(tag);
    289         for ( int i=0; i<nattr; ++i ) {
    290             m_out.write(' ');
    291             m_out.write((String)names.get(i));
    292             m_out.write('=');
    293             m_out.write('\"');
    294             escapeString((String)values.get(i));
    295             m_out.write('\"');
    296         }
    297         if ( close ) m_out.write('/');
    298         m_out.write('>');
    299         writeln();
    300 
    301         if ( !close ) {
    302             m_tagStack.add(tag);
    303         }
    304     }
    305 
    306     /**
    307      * Write a closed tag with attributes. The tag will be followed by a
    308      * newline.
    309      * @param tag the tag name
    310      * @param names the names of the attributes
    311      * @param values the values of the attributes
    312      * @param nattr the number of attributes
    313      */
    314     public void tag(String tag, ArrayList names, ArrayList values, int nattr) throws IOException
    315     {
    316         tag(tag, names, values, nattr, true);
    317     }
    318 
    319     /**
    320      * Write a start tag with attributes. The tag will be followed by a
    321      * newline, and the indentation level will be increased.
    322      * @param tag the tag name
    323      * @param names the names of the attributes
    324      * @param values the values of the attributes
    325      * @param nattr the number of attributes
    326      */
    327     public void start(String tag, ArrayList names, ArrayList values, int nattr) throws IOException
    328     {
    329         tag(tag, names, values, nattr, false);
    330     }
    331 
    332     /**
    333      * Write a start tag without attributes. The tag will be followed by a
    334      * newline, and the indentation level will be increased.
    335      * @param tag the tag name
    336      */
    337     public void start(String tag) throws IOException {
    338         tag(tag, (String[])null, null, 0, false);
    339     }
    340 
    341     /**
    342      * Close the most recently opened tag. The tag will be followed by a
    343      * newline, and the indentation level will be decreased.
    344      */
    345     public void end() throws IOException {
    346         String tag = (String)m_tagStack.remove(m_tagStack.size()-1);
    347         spacing();
    348         m_out.write('<');
    349         m_out.write('/');
    350         m_out.write(tag);
    351         m_out.write('>');
    352         writeln();
    353     }
    354 
    355     /**
    356      * Write a new content tag with a single attribute, consisting of an
    357      * open tag, content text, and a closing tag, all on one line.
    358      * @param tag the tag name
    359      * @param name the name of the attribute
    360      * @param value the value of the attribute, this text will be escaped
    361      * @param content the text content, this text will be escaped
    362      */
    363     public void contentTag(String tag, String name, String value, String content) throws IOException
    364     {
    365         spacing();
    366         m_out.write('<'); m_out.write(tag); m_out.write(' ');
    367         m_out.write(name); m_out.write('=');
    368         m_out.write('\"'); escapeString(value); m_out.write('\"');
    369         m_out.write('>');
    370         escapeString(content);
    371         m_out.write('<'); m_out.write('/'); m_out.write(tag); m_out.write('>');
    372         writeln();
    373     }
    374 
    375     /**
    376      * Write a new content tag with no attributes, consisting of an
    377      * open tag, content text, and a closing tag, all on one line.
    378      * @param tag the tag name
    379      * @param content the text content, this text will be escaped
    380      */
    381     public void contentTag(String tag, String content) throws IOException {
    382         spacing();
    383         m_out.write('<'); m_out.write(tag); m_out.write('>');
    384         escapeString(content);
    385         m_out.write('<'); m_out.write('/'); m_out.write(tag); m_out.write('>');
    386         writeln();
    387     }
    388 
    389     /**
    390      * Write content text.
    391      * @param content the content text, this text will be escaped
    392      */
    393     public void content(String content) throws IOException {
    394         escapeString(content);
    395     }
    396 
    397     /**
    398      * Finish the XML document.
    399      */
    400     public void finish() throws IOException {
    401         m_bias = 0;
    402         m_out.flush();
    403     }
    404 
    405     /**
    406      * Finish the XML document, writing the given footer text at the
    407      * end of the document.
    408      * @param footer the footer text, this will not be escaped
    409      */
    410     public void finish(String footer) throws IOException {
    411         m_bias = 0;
    412         m_out.write(footer);
    413         m_out.flush();
    414     }
    415 
    416     /**
    417      * Write the current spacing (determined by the indentation level)
    418      * into the document. This method is used by many of the other
    419      * formatting methods, and so should only need to be called in
    420      * the case of custom text writing outside the mechanisms
    421      * provided by this class.
    422      */
    423     public void spacing() throws IOException {
    424         int len = m_bias + m_tagStack.size() * m_tab;
    425         for ( int i=0; i<len; ++i )
    426             m_out.write(' ');
    427     }
    428 
    429     // ------------------------------------------------------------------------
    430     // Escape Text
    431 
    432     // unicode ranges and valid/invalid characters
    433     private static final char   LOWER_RANGE = 0x20;
    434     private static final char   UPPER_RANGE = 0x7f;
    435     private static final char[] VALID_CHARS = { 0x9, 0xA, 0xD };
    436 
    437     private static final char[] INVALID = { '<', '>', '"', '\'', '&' };
    438     private static final String[] VALID =
    439         { "&lt;", "&gt;", "&quot;", "&apos;", "&amp;" };
    440 
    441     /**
    442      * Escape a string such that it is safe to use in an XML document.
    443      * @param str the string to escape
    444      */
    445     protected void escapeString(String str) throws IOException {
    446         if ( str == null ) {
    447             m_out.write("null");
    448             return;
    449         }
    450 
    451         int len = str.length();
    452         for (int i = 0; i < len; ++i) {
    453             char c = str.charAt(i);
    454 
    455             if ( (c < LOWER_RANGE     && c != VALID_CHARS[0] &&
    456                   c != VALID_CHARS[1] && c != VALID_CHARS[2])
    457                  || (c > UPPER_RANGE) )
    458             {
    459                 // character out of range, escape with character value
    460                 m_out.write("&#");
    461                 m_out.write(Integer.toString(c));
    462                 m_out.write(';');
    463             } else {
    464                 boolean valid = true;
    465                 // check for invalid characters (e.g., "<", "&", etc)
    466                 for (int j=INVALID.length-1; j >= 0; --j )
    467                 {
    468                     if ( INVALID[j] == c) {
    469                         valid = false;
    470                         m_out.write(VALID[j]);
    471                         break;
    472                     }
    473                 }
    474                 // if character is valid, don't escape
    475                 if (valid) {
    476                     m_out.write(c);
    477                 }
    478             }
    479         }
    480     }
    481 
    482 } // end of class XMLWriter
    483 
    484