Coverage Report - org.simpleframework.xml.stream.Formatter
 
Classes in this File Line Coverage Branch Coverage Complexity
Formatter
89%
98/110
80%
12/15
0
Formatter$Tag
100%
4/4
N/A
0
 
 1  
 /*
 2  
  * Formatter.java July 2006
 3  
  *
 4  
  * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * This library is free software; you can redistribute it and/or
 7  
  * modify it under the terms of the GNU Lesser General Public
 8  
  * License as published by the Free Software Foundation.
 9  
  *
 10  
  * This library is distributed in the hope that it will be useful,
 11  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  
  * GNU Lesser General Public License for more details.
 14  
  *
 15  
  * You should have received a copy of the GNU Lesser General 
 16  
  * Public License along with this library; if not, write to the 
 17  
  * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 18  
  * Boston, MA  02111-1307  USA
 19  
  */
 20  
 
 21  
 package org.simpleframework.xml.stream;
 22  
 
 23  
 import java.io.BufferedWriter;
 24  
 import java.io.Writer;
 25  
 
 26  
 /**
 27  
  * The <code>Formatter</code> object is used to format output as XML
 28  
  * indented with a configurable indent level. This is used to write
 29  
  * start and end tags, as well as attributes and values to the given
 30  
  * writer. The output is written directly to the stream with and
 31  
  * indentation for each element appropriate to its position in the
 32  
  * document hierarchy. If the indent is set to zero then no indent
 33  
  * is performed and all XML will appear on the same line.
 34  
  *
 35  
  * @see org.simpleframework.xml.stream.Indenter
 36  
  */ 
 37  
 class Formatter {
 38  
 
 39  
    /**
 40  
     * Represents the XML escape sequence for the less than sign.
 41  
     */ 
 42  48
    private static final char[] LESS = { '&', 'l', 't', ';'};        
 43  
    
 44  
    /**
 45  
     * Represents the XML escape sequence for the greater than sign.
 46  
     */ 
 47  48
    private static final char[] GREATER = { '&', 'g', 't', ';' };
 48  
 
 49  
    /**
 50  
     * Represents the XML escape sequence for the double quote.
 51  
     */ 
 52  48
    private static final char[] DOUBLE = { '&', 'q', 'u', 'o', 't', ';'};
 53  
 
 54  
    /**
 55  
     * Represents the XML escape sequence for the single quote.
 56  
     */ 
 57  48
    private static final char[] SINGLE = { '&', 'a', 'p', 'o', 's', ';'};
 58  
 
 59  
    /**
 60  
     * Represents the XML escape sequence for the ampersand sign.
 61  
     */ 
 62  48
    private static final char[] AND = { '&', 'a', 'm', 'p', ';'};
 63  
    
 64  
    /**
 65  
     * Output buffer used to write the generated XML result to.
 66  
     */ 
 67  
    private OutputBuffer buffer;
 68  
    
 69  
    /**
 70  
     * Creates the indentations that are used bu the XML file.
 71  
     */         
 72  
    private Indenter indenter;
 73  
    
 74  
    /**
 75  
     * This is the writer that is used to write the XML document.
 76  
     */
 77  
    private Writer result;
 78  
 
 79  
    /**
 80  
     * Represents the prolog to insert at the start of the document.
 81  
     */ 
 82  
    private String prolog;
 83  
    
 84  
    /**
 85  
     * Represents the last type of content that was written.
 86  
     */ 
 87  
    private Tag last;
 88  
    
 89  
    /**
 90  
     * Constructor for the <code>Formatter</code> object. This creates
 91  
     * an object that can be used to write XML in an indented format
 92  
     * to the specified writer. The XML written will be well formed.
 93  
     *
 94  
     * @param result this is where the XML should be written to
 95  
     * @param format this is the format object to use 
 96  
     */ 
 97  11230
    public Formatter(Writer result, Format format){
 98  11230
        this.result = new BufferedWriter(result);
 99  11230
        this.indenter = new Indenter(format);
 100  11230
        this.buffer = new OutputBuffer();
 101  11230
        this.prolog = format.getProlog();      
 102  11230
    }
 103  
 
 104  
    /**
 105  
     * This is used to write a prolog to the specified output. This is
 106  
     * only written if the specified <code>Format</code> object has
 107  
     * been given a non null prolog. If no prolog is specified then no
 108  
     * prolog is written to the generated XML.
 109  
     *
 110  
     * @throws Exception thrown if there is an I/O problem 
 111  
     */ 
 112  
    public void writeProlog() throws Exception {
 113  11230
       if(prolog != null) {
 114  2
          write(prolog);         
 115  2
          write("\n");         
 116  
       }
 117  11230
    }
 118  
    
 119  
    /**
 120  
     * This method is used to write a start tag for an element. If a
 121  
     * start tag was written before this then it is closed. Before
 122  
     * the start tag is written an indent is generated and placed in
 123  
     * front of the tag, this is done for all but the first start tag.
 124  
     * 
 125  
     * @param name this is the name of the start tag to be written
 126  
     *
 127  
     * @throws Exception thrown if there is an I/O exception
 128  
     */ 
 129  
    public void writeStart(String name) throws Exception{
 130  265710
       String text = indenter.push();
 131  
 
 132  265710
       if(last == Tag.START) {
 133  116939
          append('>');    
 134  
       }        
 135  265710
       flush();
 136  265710
       append(text);
 137  265710
       append('<');
 138  265710
       append(name);
 139  265710
       last = Tag.START;
 140  265710
    }
 141  
   
 142  
    /**
 143  
     * This is used to write a name value attribute pair. If the last
 144  
     * tag written was not a start tag then this throws an exception.
 145  
     * All attribute values written are enclosed in double quotes.
 146  
     * 
 147  
     * @param name this is the name of the attribute to be written
 148  
     * @param value this is the value to assigne to the attribute
 149  
     *
 150  
     * @throws Exception thrown if there is an I/O exception
 151  
     */  
 152  
    public void writeAttribute(String name, String value) throws Exception{
 153  129790
       if(last != Tag.START) {
 154  0
          throw new NodeException("Start element required");              
 155  
       }         
 156  129790
       write(' ');
 157  129790
       write(name);
 158  129790
       write('=');
 159  129790
       write('"');
 160  129790
       escape(value);
 161  129790
       write('"');               
 162  129790
    }
 163  
 
 164  
    /**
 165  
     * This is used to write the specified text value to the writer.
 166  
     * If the last tag written was a start tag then it is closed.
 167  
     * By default this will escape any illegal XML characters. 
 168  
     *
 169  
     * @param text this is the text to write to the output
 170  
     *
 171  
     * @throws Exception thrown if there is an I/O exception
 172  
     */ 
 173  
    public void writeText(String text) throws Exception{
 174  0
       writeText(text, Mode.ESCAPE);      
 175  0
    }
 176  
    
 177  
    /**
 178  
     * This is used to write the specified text value to the writer.
 179  
     * If the last tag written was a start tag then it is closed.
 180  
     * This will use the output mode specified. 
 181  
     *
 182  
     * @param text this is the text to write to the output
 183  
     *
 184  
     * @throws Exception thrown if there is an I/O exception
 185  
     */ 
 186  
    public void writeText(String text, Mode mode) throws Exception{
 187  144084
       if(last == Tag.START) {
 188  144083
          write('>');
 189  
       }                
 190  144084
       if(mode == Mode.DATA) {
 191  30
          data(text);
 192  30
       } else {
 193  144054
          escape(text);
 194  
       }         
 195  144084
       last = Tag.TEXT;
 196  144084
    }
 197  
    
 198  
    /**
 199  
     * This is used to write an end element tag to the writer. This
 200  
     * will close the element with a short <code>/&gt;</code> if the
 201  
     * last tag written was a start tag. However if an end tag or 
 202  
     * some text was written then a full end tag is written.
 203  
     *
 204  
     * @param name this is the name of the element to be closed
 205  
     *
 206  
     * @throws Exception thrown if there is an I/O exception
 207  
     */ 
 208  
    public void writeEnd(String name) throws Exception {
 209  265675
       String text = indenter.pop();
 210  
 
 211  265675
       if(last == Tag.START) {
 212  4653
          write('/');
 213  4653
          write('>');
 214  4653
       } else {                       
 215  261022
          if(last != Tag.TEXT) {
 216  116938
             write(text);   
 217  
          }                        
 218  261022
          if(last != Tag.START) {
 219  261022
             write('<');
 220  261022
             write('/');
 221  261022
             write(name);
 222  261022
             write('>');
 223  
          }                    
 224  
       }                    
 225  265675
       last = Tag.END;
 226  265675
    }
 227  
 
 228  
    /**
 229  
     * This is used to write a character to the output stream without
 230  
     * any translation. This is used when writing the start tags and
 231  
     * end tags, this is also used to write attribute names.
 232  
     *
 233  
     * @param ch this is the character to be written to the output
 234  
     */ 
 235  
    private void write(char ch) throws Exception {     
 236  3846534
       buffer.write(result);
 237  3846534
       buffer.clear();
 238  3846534
       result.write(ch);      
 239  3846534
    }
 240  
 
 241  
    /**
 242  
     * This is used to write plain text to the output stream without
 243  
     * any translation. This is used when writing the start tags and
 244  
     * end tags, this is also used to write attribute names.
 245  
     *
 246  
     * @param plain this is the text to be written to the output
 247  
     */    
 248  
    private void write(char[] plain) throws Exception {      
 249  80052
       buffer.write(result);
 250  80052
       buffer.clear();
 251  80052
       result.write(plain);  
 252  80052
    }
 253  
 
 254  
    /**
 255  
     * This is used to write plain text to the output stream without
 256  
     * any translation. This is used when writing the start tags and
 257  
     * end tags, this is also used to write attribute names.
 258  
     *
 259  
     * @param plain this is the text to be written to the output
 260  
     */    
 261  
    private void write(String plain) throws Exception{      
 262  507844
       buffer.write(result);
 263  507844
       buffer.clear();
 264  507844
       result.write(plain);  
 265  507844
    }
 266  
    
 267  
    /**
 268  
     * This is used to buffer a character to the output stream without
 269  
     * any translation. This is used when buffering the start tags so
 270  
     * that they can be reset without affecting the resulting document.
 271  
     *
 272  
     * @param ch this is the character to be written to the output
 273  
     */ 
 274  
    private void append(char ch) throws Exception {
 275  382649
       buffer.append(ch);           
 276  382649
    }
 277  
 
 278  
    /**
 279  
     * This is used to buffer characters to the output stream without
 280  
     * any translation. This is used when buffering the start tags so
 281  
     * that they can be reset without affecting the resulting document.
 282  
     *
 283  
     * @param plain this is the string that is to be buffered
 284  
     */     
 285  
    private void append(String plain) throws Exception{
 286  531420
       buffer.append(plain);                    
 287  531420
    }
 288  
    
 289  
    /**
 290  
     * This method is used to write the specified text as a CDATA block
 291  
     * within the XML element. This is typically used when the value is
 292  
     * large or if it must be preserved in a format that will not be
 293  
     * affected by other XML parsers. For large text values this is 
 294  
     * also faster than performing a character by character escaping.
 295  
     * 
 296  
     * @param value this is the text value to be written as CDATA
 297  
     */
 298  
    private void data(String value) throws Exception {
 299  30
       write("<![CDATA[");
 300  30
       write(value);
 301  30
       write("]]>");
 302  30
    }
 303  
    
 304  
    /**
 305  
     * This is used to write the specified value to the output with
 306  
     * translation to any symbol characters or non text characters.
 307  
     * This will translate the symbol characters such as "&amp;",
 308  
     * "&gt;", "&lt;", and "&quot;". This also writes any non text
 309  
     * and non symbol characters as integer values like "&#123;".
 310  
     *
 311  
     * @param value the text value to be escaped and written
 312  
     */ 
 313  
    private void escape(String value) throws Exception {
 314  273844
       int size = value.length();
 315  
 
 316  2744815
       for(int i = 0; i < size; i++){
 317  2470971
          escape(value.charAt(i));
 318  
       }
 319  273844
    }
 320  
 
 321  
    /**
 322  
     * This is used to write the specified value to the output with
 323  
     * translation to any symbol characters or non text characters.
 324  
     * This will translate the symbol characters such as "&amp;",
 325  
     * "&gt;", "&lt;", and "&quot;". This also writes any non text
 326  
     * and non symbol characters as integer values like "&#123;".
 327  
     *
 328  
     * @param ch the text character to be escaped and written
 329  
     */ 
 330  
    private void escape(char ch) throws Exception {
 331  2470971
       char[] text = symbol(ch);
 332  
          
 333  2470971
       if(text != null) {
 334  80052
          write(text);
 335  80052
       } else {
 336  2390919
          write(ch);                 
 337  
       }
 338  2470971
    }   
 339  
    
 340  
    /**
 341  
     * This method is used to reset the internal buffer such that the
 342  
     * contents are discarded. This is useful when a tag needs to be
 343  
     * removed or deleted as its contents can be removed from the 
 344  
     * buffer so as not to affect the resulting XML document.    
 345  
     */
 346  
    public void reset() throws Exception {
 347  33
       if(last != Tag.START) {
 348  0
          throw new NodeException("Can not remove element text");
 349  
       }      
 350  33
       indenter.pop();
 351  33
       buffer.clear();
 352  33
       last = Tag.TEXT;
 353  33
    }
 354  
 
 355  
    /**
 356  
     * This is used to flush the writer when the XML if it has been
 357  
     * buffered. The flush method is used by the node writer after an
 358  
     * end element has been written. Flushing ensures that buffering
 359  
     * does not affect the result of the node writer.
 360  
     */ 
 361  
    public void flush() throws Exception{
 362  531385
       buffer.write(result);
 363  531385
       buffer.clear();
 364  531385
       result.flush();
 365  531385
    }
 366  
 
 367  
    /**
 368  
     * This is used to convert the the specified character to unicode.
 369  
     * This will simply get the decimal representation of the given
 370  
     * character as a string so it can be written as an escape.
 371  
     *
 372  
     * @param ch this is the character that is to be converted
 373  
     *
 374  
     * @return this is the decimal value of the given character
 375  
     */ 
 376  
    private String unicode(char ch) {
 377  0
       return Integer.toString(ch);           
 378  
    }
 379  
 
 380  
    /**
 381  
     * This is used to determine if the specified character is a text
 382  
     * character. If the character specified is not a text value then
 383  
     * this returls true, otherwise this returns false.
 384  
     *
 385  
     * @param ch this is the character to be evaluated as text
 386  
     *
 387  
     * @return this returns the true if the character is textual
 388  
     */ 
 389  
    private boolean isText(char ch) {
 390  0
       switch(ch) {
 391  
       case ' ': case '\n':
 392  
       case '\r': case '\t':
 393  0
          return true;              
 394  
       }           
 395  0
       if(ch > ' ' && ch <= 0x7E){
 396  0
          return ch != 0xF7;
 397  
       }
 398  0
       return false;      
 399  
    }
 400  
 
 401  
    /**
 402  
     * This is used to convert the specified character to an XML text
 403  
     * symbol if the specified character can be converted. If the
 404  
     * character cannot be converted to a symbol null is returned.
 405  
     *
 406  
     * @param ch this is the character that is to be converted
 407  
     *
 408  
     * @return this is the symbol chatacter that has been resolved
 409  
     */ 
 410  
    private char[] symbol(char ch) {
 411  2470971
       switch(ch) {
 412  
       case '<':
 413  40016
         return LESS;
 414  
       case '>':
 415  40016
         return GREATER;
 416  
       case '"':
 417  0
         return DOUBLE;
 418  
       case '\'':
 419  20
         return SINGLE;
 420  
       case '&':
 421  0
         return AND;
 422  
       }
 423  2390919
       return null;
 424  
   }  
 425  
    
 426  
    /**
 427  
     * This is used to enumerate the different types of tag that can
 428  
     * be written. Each tag represents a state for the writer. After
 429  
     * a specific tag type has been written the state of the writer
 430  
     * is updated. This is needed to write well formed XML text.
 431  
     */ 
 432  192
    private enum Tag {
 433  48
       START,
 434  48
       TEXT,
 435  48
       END                
 436  
   }
 437  
 }