Coverage Report - org.simpleframework.xml.stream.Formatter
 
Classes in this File Line Coverage Branch Coverage Complexity
Formatter
91%
129/141
67%
36/53
2.583
Formatter$Tag
100%
5/5
N/A
2.583
 
 1  
 /*
 2  
  * Formatter.java July 2006
 3  
  *
 4  
  * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 15  
  * implied. See the License for the specific language governing 
 16  
  * permissions and limitations under the License.
 17  
  */
 18  
 
 19  
 package org.simpleframework.xml.stream;
 20  
 
 21  
 import java.io.BufferedWriter;
 22  
 import java.io.Writer;
 23  
 
 24  
 /**
 25  
  * The <code>Formatter</code> object is used to format output as XML
 26  
  * indented with a configurable indent level. This is used to write
 27  
  * start and end tags, as well as attributes and values to the given
 28  
  * writer. The output is written directly to the stream with and
 29  
  * indentation for each element appropriate to its position in the
 30  
  * document hierarchy. If the indent is set to zero then no indent
 31  
  * is performed and all XML will appear on the same line.
 32  
  *
 33  
  * @see org.simpleframework.xml.stream.Indenter
 34  
  */ 
 35  
 class Formatter {
 36  
 
 37  
    /**
 38  
     * Represents the prefix used when declaring an XML namespace.
 39  
     */
 40  1
    private static final char[] NAMESPACE = { 'x', 'm', 'l', 'n', 's' };
 41  
    
 42  
    /**
 43  
     * Represents the XML escape sequence for the less than sign.
 44  
     */ 
 45  1
    private static final char[] LESS = { '&', 'l', 't', ';'};        
 46  
    
 47  
    /**
 48  
     * Represents the XML escape sequence for the greater than sign.
 49  
     */ 
 50  1
    private static final char[] GREATER = { '&', 'g', 't', ';' };
 51  
 
 52  
    /**
 53  
     * Represents the XML escape sequence for the double quote.
 54  
     */ 
 55  1
    private static final char[] DOUBLE = { '&', 'q', 'u', 'o', 't', ';' };
 56  
 
 57  
    /**
 58  
     * Represents the XML escape sequence for the single quote.
 59  
     */ 
 60  1
    private static final char[] SINGLE = { '&', 'a', 'p', 'o', 's', ';' };
 61  
 
 62  
    /**
 63  
     * Represents the XML escape sequence for the ampersand sign.
 64  
     */ 
 65  1
    private static final char[] AND = { '&', 'a', 'm', 'p', ';' };
 66  
    
 67  
    /**
 68  
     * This is used to open a comment section within the document.
 69  
     */
 70  1
    private static final char[] OPEN = { '<', '!', '-', '-', ' ' };
 71  
    
 72  
    /**
 73  
     * This is used to close a comment section within the document.
 74  
     */
 75  1
    private static final char[] CLOSE = { ' ', '-', '-', '>' };
 76  
    
 77  
    /**
 78  
     * Output buffer used to write the generated XML result to.
 79  
     */ 
 80  
    private OutputBuffer buffer;
 81  
    
 82  
    /**
 83  
     * Creates the indentations that are used buffer the XML file.
 84  
     */         
 85  
    private Indenter indenter;
 86  
    
 87  
    /**
 88  
     * This is the writer that is used to write the XML document.
 89  
     */
 90  
    private Writer result;
 91  
 
 92  
    /**
 93  
     * Represents the prolog to insert at the start of the document.
 94  
     */ 
 95  
    private String prolog;
 96  
    
 97  
    /**
 98  
     * Represents the last type of content that was written.
 99  
     */ 
 100  
    private Tag last;
 101  
    
 102  
    /**
 103  
     * Constructor for the <code>Formatter</code> object. This creates
 104  
     * an object that can be used to write XML in an indented format
 105  
     * to the specified writer. The XML written will be well formed.
 106  
     *
 107  
     * @param result this is where the XML should be written to
 108  
     * @param format this is the format object to use 
 109  
     */ 
 110  24420
    public Formatter(Writer result, Format format){
 111  24420
        this.result = new BufferedWriter(result, 1024);
 112  24420
        this.indenter = new Indenter(format);
 113  24420
        this.buffer = new OutputBuffer();
 114  24420
        this.prolog = format.getProlog();      
 115  24420
    }
 116  
 
 117  
    /**
 118  
     * This is used to write a prolog to the specified output. This is
 119  
     * only written if the specified <code>Format</code> object has
 120  
     * been given a non null prolog. If no prolog is specified then no
 121  
     * prolog is written to the generated XML.
 122  
     *
 123  
     * @throws Exception thrown if there is an I/O problem 
 124  
     */ 
 125  
    public void writeProlog() throws Exception {
 126  24420
       if(prolog != null) {
 127  2
          write(prolog);         
 128  2
          write("\n");         
 129  
       }
 130  24420
    }
 131  
   
 132  
    /**
 133  
     * This is used to write any comments that have been set. The
 134  
     * comment will typically be written at the start of an element
 135  
     * to describe the purpose of the element or include debug data
 136  
     * that can be used to determine any issues in serialization.
 137  
     * 
 138  
     * @param comment this is the comment that is to be written
 139  
     */  
 140  
    public void writeComment(String comment) throws Exception {
 141  25450
       String text = indenter.top();
 142  
       
 143  25450
       if(last == Tag.START) {
 144  7644
          append('>');
 145  
       }
 146  25450
       if(text != null) {
 147  25450
          append(text);
 148  25450
          append(OPEN);
 149  25450
          append(comment);
 150  25450
          append(CLOSE);
 151  
       }
 152  25450
       last = Tag.COMMENT;
 153  25450
    }
 154  
    
 155  
    /**
 156  
     * This method is used to write a start tag for an element. If a
 157  
     * start tag was written before this then it is closed. Before
 158  
     * the start tag is written an indent is generated and placed in
 159  
     * front of the tag, this is done for all but the first start tag.
 160  
     * 
 161  
     * @param name this is the name of the start tag to be written
 162  
     *
 163  
     * @throws Exception thrown if there is an I/O exception
 164  
     */ 
 165  
    public void writeStart(String name, String prefix) throws Exception{
 166  561691
       String text = indenter.push();
 167  
 
 168  561691
       if(last == Tag.START) {
 169  231486
          append('>');    
 170  
       }        
 171  561691
       flush();
 172  561691
       append(text);
 173  561691
       append('<');
 174  
       
 175  561691
       if(!isEmpty(prefix)) {
 176  317
          append(prefix);
 177  317
          append(':');
 178  
       }
 179  561691
       append(name);
 180  561691
       last = Tag.START;
 181  561691
    }
 182  
   
 183  
    /**
 184  
     * This is used to write a name value attribute pair. If the last
 185  
     * tag written was not a start tag then this throws an exception.
 186  
     * All attribute values written are enclosed in double quotes.
 187  
     * 
 188  
     * @param name this is the name of the attribute to be written
 189  
     * @param value this is the value to assigne to the attribute
 190  
     *
 191  
     * @throws Exception thrown if there is an I/O exception
 192  
     */  
 193  
    public void writeAttribute(String name, String value, String prefix) throws Exception{
 194  271196
       if(last != Tag.START) {
 195  0
          throw new NodeException("Start element required");              
 196  
       }         
 197  271196
       write(' ');
 198  271196
       write(name, prefix);
 199  271196
       write('=');
 200  271196
       write('"');
 201  271196
       escape(value);
 202  271196
       write('"');               
 203  271196
    }
 204  
    
 205  
    /**
 206  
     * This is used to write the namespace to the element. This will
 207  
     * write the special attribute using the prefix and reference
 208  
     * specified. This will escape the reference if it is required.
 209  
     * 
 210  
     * @param reference this is the namespace URI reference to use
 211  
     * @param prefix this is the prefix to used for the namespace
 212  
     *
 213  
     * @throws Exception thrown if there is an I/O exception
 214  
     */  
 215  
    public void writeNamespace(String reference, String prefix) throws Exception{
 216  308
       if(last != Tag.START) {
 217  0
          throw new NodeException("Start element required");              
 218  
       }         
 219  308
       write(' ');
 220  308
       write(NAMESPACE);
 221  
       
 222  308
       if(!isEmpty(prefix)) {
 223  221
          write(':');
 224  221
          write(prefix);
 225  
       }
 226  308
       write('=');
 227  308
       write('"');
 228  308
       escape(reference);
 229  308
       write('"');               
 230  308
    }
 231  
 
 232  
    /**
 233  
     * This is used to write the specified text value to the writer.
 234  
     * If the last tag written was a start tag then it is closed.
 235  
     * By default this will escape any illegal XML characters. 
 236  
     *
 237  
     * @param text this is the text to write to the output
 238  
     *
 239  
     * @throws Exception thrown if there is an I/O exception
 240  
     */ 
 241  
    public void writeText(String text) throws Exception{
 242  0
       writeText(text, Mode.ESCAPE);      
 243  0
    }
 244  
    
 245  
    /**
 246  
     * This is used to write the specified text value to the writer.
 247  
     * If the last tag written was a start tag then it is closed.
 248  
     * This will use the output mode specified. 
 249  
     *
 250  
     * @param text this is the text to write to the output
 251  
     *
 252  
     * @throws Exception thrown if there is an I/O exception
 253  
     */ 
 254  
    public void writeText(String text, Mode mode) throws Exception{
 255  317059
       if(last == Tag.START) {
 256  317025
          write('>');
 257  
       }                
 258  317059
       if(mode == Mode.DATA) {
 259  214
          data(text);
 260  
       } else {
 261  316845
          escape(text);
 262  
       }         
 263  317059
       last = Tag.TEXT;
 264  317059
    }
 265  
    
 266  
    /**
 267  
     * This is used to write an end element tag to the writer. This
 268  
     * will close the element with a short <code>/&gt;</code> if the
 269  
     * last tag written was a start tag. However if an end tag or 
 270  
     * some text was written then a full end tag is written.
 271  
     *
 272  
     * @param name this is the name of the element to be closed
 273  
     *
 274  
     * @throws Exception thrown if there is an I/O exception
 275  
     */ 
 276  
    public void writeEnd(String name, String prefix) throws Exception {
 277  561685
       String text = indenter.pop();
 278  
 
 279  561685
       if(last == Tag.START) {
 280  5534
          write('/');
 281  5534
          write('>');
 282  
       } else {                       
 283  556151
          if(last != Tag.TEXT) {
 284  239151
             write(text);   
 285  
          }                        
 286  556151
          if(last != Tag.START) {
 287  556151
             write('<');
 288  556151
             write('/');
 289  556151
             write(name, prefix);
 290  556151
             write('>');
 291  
          }                    
 292  
       }                    
 293  561685
       last = Tag.END;
 294  561685
    }
 295  
 
 296  
    /**
 297  
     * This is used to write a character to the output stream without
 298  
     * any translation. This is used when writing the start tags and
 299  
     * end tags, this is also used to write attribute names.
 300  
     *
 301  
     * @param ch this is the character to be written to the output
 302  
     */ 
 303  
    private void write(char ch) throws Exception {     
 304  8154319
       buffer.write(result);
 305  8154319
       buffer.clear();
 306  8154319
       result.write(ch);      
 307  8154319
    }
 308  
 
 309  
    /**
 310  
     * This is used to write plain text to the output stream without
 311  
     * any translation. This is used when writing the start tags and
 312  
     * end tags, this is also used to write attribute names.
 313  
     *
 314  
     * @param plain this is the text to be written to the output
 315  
     */    
 316  
    private void write(char[] plain) throws Exception {      
 317  160553
       buffer.write(result);
 318  160553
       buffer.clear();
 319  160553
       result.write(plain);  
 320  160553
    }
 321  
 
 322  
    /**
 323  
     * This is used to write plain text to the output stream without
 324  
     * any translation. This is used when writing the start tags and
 325  
     * end tags, this is also used to write attribute names.
 326  
     *
 327  
     * @param plain this is the text to be written to the output
 328  
     */    
 329  
    private void write(String plain) throws Exception{      
 330  240018
       buffer.write(result);
 331  240018
       buffer.clear();
 332  240018
       result.write(plain);  
 333  240018
    }
 334  
    
 335  
    /**
 336  
     * This is used to write plain text to the output stream without
 337  
     * any translation. This is used when writing the start tags and
 338  
     * end tags, this is also used to write attribute names.
 339  
     *
 340  
     * @param plain this is the text to be written to the output
 341  
     * @param prefix this is the namespace prefix to be written
 342  
     */    
 343  
    private void write(String plain, String prefix) throws Exception{      
 344  827347
       buffer.write(result);
 345  827347
       buffer.clear();
 346  
       
 347  827347
       if(!isEmpty(prefix)) {
 348  266
          result.write(prefix);
 349  266
          result.write(':');
 350  
       }
 351  827347
       result.write(plain);  
 352  827347
    }
 353  
    
 354  
    /**
 355  
     * This is used to buffer a character to the output stream without
 356  
     * any translation. This is used when buffering the start tags so
 357  
     * that they can be reset without affecting the resulting document.
 358  
     *
 359  
     * @param ch this is the character to be written to the output
 360  
     */ 
 361  
    private void append(char ch) throws Exception {
 362  801138
       buffer.append(ch);           
 363  801138
    }
 364  
    
 365  
    /**
 366  
     * This is used to buffer characters to the output stream without
 367  
     * any translation. This is used when buffering the start tags so
 368  
     * that they can be reset without affecting the resulting document.
 369  
     *
 370  
     * @param plain this is the string that is to be buffered
 371  
     */     
 372  
    private void append(char[] plain) throws Exception {
 373  50900
       buffer.append(plain);
 374  50900
    }
 375  
 
 376  
    /**
 377  
     * This is used to buffer characters to the output stream without
 378  
     * any translation. This is used when buffering the start tags so
 379  
     * that they can be reset without affecting the resulting document.
 380  
     *
 381  
     * @param plain this is the string that is to be buffered
 382  
     */     
 383  
    private void append(String plain) throws Exception{
 384  1174599
       buffer.append(plain);                    
 385  1174599
    }
 386  
    
 387  
    /**
 388  
     * This method is used to write the specified text as a CDATA block
 389  
     * within the XML element. This is typically used when the value is
 390  
     * large or if it must be preserved in a format that will not be
 391  
     * affected by other XML parsers. For large text values this is 
 392  
     * also faster than performing a character by character escaping.
 393  
     * 
 394  
     * @param value this is the text value to be written as CDATA
 395  
     */
 396  
    private void data(String value) throws Exception {
 397  214
       write("<![CDATA[");
 398  214
       write(value);
 399  214
       write("]]>");
 400  214
    }
 401  
    
 402  
    /**
 403  
     * This is used to write the specified value to the output with
 404  
     * translation to any symbol characters or non text characters.
 405  
     * This will translate the symbol characters such as "&amp;",
 406  
     * "&gt;", "&lt;", and "&quot;". This also writes any non text
 407  
     * and non symbol characters as integer values like "&#123;".
 408  
     *
 409  
     * @param value the text value to be escaped and written
 410  
     */ 
 411  
    private void escape(String value) throws Exception {
 412  588349
       int size = value.length();
 413  
 
 414  5820130
       for(int i = 0; i < size; i++){
 415  5231781
          escape(value.charAt(i));
 416  
       }
 417  588349
    }
 418  
 
 419  
    /**
 420  
     * This is used to write the specified value to the output with
 421  
     * translation to any symbol characters or non text characters.
 422  
     * This will translate the symbol characters such as "&amp;",
 423  
     * "&gt;", "&lt;", and "&quot;". This also writes any non text
 424  
     * and non symbol characters as integer values like "&#123;".
 425  
     *
 426  
     * @param ch the text character to be escaped and written
 427  
     */ 
 428  
    private void escape(char ch) throws Exception {
 429  5231781
       char[] text = symbol(ch);
 430  
          
 431  5231781
       if(text != null) {
 432  160245
          write(text);
 433  
       } else {
 434  5071536
          write(ch);                 
 435  
       }
 436  5231781
    }   
 437  
 
 438  
    /**
 439  
     * This is used to flush the writer when the XML if it has been
 440  
     * buffered. The flush method is used by the node writer after an
 441  
     * end element has been written. Flushing ensures that buffering
 442  
     * does not affect the result of the node writer.
 443  
     */ 
 444  
    public void flush() throws Exception{
 445  1123376
       buffer.write(result);
 446  1123376
       buffer.clear();
 447  1123376
       result.flush();
 448  1123376
    }
 449  
 
 450  
    /**
 451  
     * This is used to convert the the specified character to unicode.
 452  
     * This will simply get the decimal representation of the given
 453  
     * character as a string so it can be written as an escape.
 454  
     *
 455  
     * @param ch this is the character that is to be converted
 456  
     *
 457  
     * @return this is the decimal value of the given character
 458  
     */ 
 459  
    private String unicode(char ch) {
 460  0
       return Integer.toString(ch);           
 461  
    }
 462  
    
 463  
    /**
 464  
     * This method is used to determine if a root annotation value is
 465  
     * an empty value. Rather than determining if a string is empty
 466  
     * be comparing it to an empty string this method allows for the
 467  
     * value an empty string represents to be changed in future.
 468  
     * 
 469  
     * @param value this is the value to determine if it is empty
 470  
     * 
 471  
     * @return true if the string value specified is an empty value
 472  
     */
 473  
    private boolean isEmpty(String value) {
 474  1389346
       if(value != null) {
 475  1049
          return value.length() == 0;
 476  
       }
 477  1388297
       return true;  
 478  
    }
 479  
 
 480  
    /**
 481  
     * This is used to determine if the specified character is a text
 482  
     * character. If the character specified is not a text value then
 483  
     * this returns true, otherwise this returns false.
 484  
     *
 485  
     * @param ch this is the character to be evaluated as text
 486  
     *
 487  
     * @return this returns the true if the character is textual
 488  
     */ 
 489  
    private boolean isText(char ch) {
 490  0
       switch(ch) {
 491  
       case ' ': case '\n':
 492  
       case '\r': case '\t':
 493  0
          return true;              
 494  
       }           
 495  0
       if(ch > ' ' && ch <= 0x7E){
 496  0
          return ch != 0xF7;
 497  
       }
 498  0
       return false;      
 499  
    }
 500  
 
 501  
    /**
 502  
     * This is used to convert the specified character to an XML text
 503  
     * symbol if the specified character can be converted. If the
 504  
     * character cannot be converted to a symbol null is returned.
 505  
     *
 506  
     * @param ch this is the character that is to be converted
 507  
     *
 508  
     * @return this is the symbol character that has been resolved
 509  
     */ 
 510  
    private char[] symbol(char ch) {
 511  5231781
       switch(ch) {
 512  
       case '<':
 513  80068
         return LESS;
 514  
       case '>':
 515  80068
         return GREATER;
 516  
       case '"':
 517  0
         return DOUBLE;
 518  
       case '\'':
 519  109
         return SINGLE;
 520  
       case '&':
 521  0
         return AND;
 522  
       }
 523  5071536
       return null;
 524  
   }  
 525  
    
 526  
    /**
 527  
     * This is used to enumerate the different types of tag that can
 528  
     * be written. Each tag represents a state for the writer. After
 529  
     * a specific tag type has been written the state of the writer
 530  
     * is updated. This is needed to write well formed XML text.
 531  
     */ 
 532  5
    private enum Tag {
 533  1
       COMMENT,
 534  1
       START,
 535  1
       TEXT,
 536  1
       END                
 537  
   }
 538  
 }