| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| Formatter |
|
| 0.0;0 | ||||
| Formatter$Tag |
|
| 0.0;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>/></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 "&", | |
| 308 | * ">", "<", and """. This also writes any non text | |
| 309 | * and non symbol characters as integer values like "{". | |
| 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 "&", | |
| 325 | * ">", "<", and """. This also writes any non text | |
| 326 | * and non symbol characters as integer values like "{". | |
| 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 | } |