1 /* 2 * Copyright (c) 2012, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.util.xml.impl; 27 28 import java.io.OutputStream; 29 import java.io.UnsupportedEncodingException; 30 31 /** 32 * Implementation of a reduced version of XMLStreamWriter 33 * 34 * @author Joe Wang 35 */ 36 public class XMLStreamWriterImpl implements XMLStreamWriter { 37 //Document state 38 static final int STATE_XML_DECL = 1; 39 static final int STATE_PROLOG = 2; 40 static final int STATE_DTD_DECL = 3; 41 static final int STATE_ELEMENT = 4; 42 //Element state 43 static final int ELEMENT_STARTTAG_OPEN = 10; 44 static final int ELEMENT_STARTTAG_CLOSE = 11; 45 static final int ELEMENT_ENDTAG_OPEN = 12; 46 static final int ELEMENT_ENDTAG_CLOSE = 13; 47 public static final char CLOSE_START_TAG = '>'; 48 public static final char OPEN_START_TAG = '<'; 49 public static final String OPEN_END_TAG = "</"; 50 public static final char CLOSE_END_TAG = '>'; 51 public static final String START_CDATA = "<![CDATA["; 52 public static final String END_CDATA = "]]>"; 53 public static final String CLOSE_EMPTY_ELEMENT = "/>"; 54 public static final String ENCODING_PREFIX = "&#x"; 55 public static final char SPACE = ' '; 56 public static final char AMPERSAND = '&'; 57 public static final char DOUBLEQUOT = '"'; 58 public static final char SEMICOLON = ';'; 59 60 //current state 61 private int _state = 0; 62 private Element _currentEle; 63 private XMLWriter _writer; 64 private String _encoding; 65 /** 66 * This flag can be used to turn escaping off for content. It does 67 * not apply to attribute content. 68 */ 69 boolean _escapeCharacters = true; 70 //pretty print by default 71 private boolean _doIndent = true; 72 //The system line separator for writing out line breaks. 73 private char[] _lineSep = 74 System.getProperty("line.separator").toCharArray(); 75 76 public XMLStreamWriterImpl(OutputStream os) 77 throws XMLStreamException { 78 this(os, XMLStreamWriter.DEFAULT_ENCODING); 79 } 80 81 public XMLStreamWriterImpl(OutputStream os, String encoding) 82 throws XMLStreamException { 83 if (encoding == null) { 84 _encoding = XMLStreamWriter.DEFAULT_ENCODING; 85 } else { 86 if (!isEncodingSupported(encoding)) { 87 throw new XMLStreamException( 88 new UnsupportedEncodingException("The basic XMLWriter does " 89 + "not support " + encoding)); 90 } 91 this._encoding = encoding; 92 } 93 94 _writer = new XMLWriter(os, encoding); 95 } 96 97 /** 98 * Write the XML Declaration. Defaults the XML version to 1.0, and the 99 * encoding to utf-8. 100 * 101 * @throws XMLStreamException 102 */ 103 public void writeStartDocument() 104 throws XMLStreamException { 105 writeStartDocument(_encoding, XMLStreamWriter.DEFAULT_XML_VERSION); 106 } 107 108 /** 109 * Write the XML Declaration. Defaults the encoding to utf-8 110 * 111 * @param version version of the xml document 112 * @throws XMLStreamException 113 */ 114 public void writeStartDocument(String version) 115 throws XMLStreamException { 116 writeStartDocument(_encoding, version, null); 117 } 118 119 /** 120 * Write the XML Declaration. Note that the encoding parameter does not set 121 * the actual encoding of the underlying output. That must be set when the 122 * instance of the XMLStreamWriter is created 123 * 124 * @param encoding encoding of the xml declaration 125 * @param version version of the xml document 126 * @throws XMLStreamException If given encoding does not match encoding of the 127 * underlying stream 128 */ 129 public void writeStartDocument(String encoding, String version) 130 throws XMLStreamException { 131 writeStartDocument(encoding, version, null); 132 } 133 134 /** 135 * Write the XML Declaration. Note that the encoding parameter does not set 136 * the actual encoding of the underlying output. That must be set when the 137 * instance of the XMLStreamWriter is created 138 * 139 * @param encoding encoding of the xml declaration 140 * @param version version of the xml document 141 * @param standalone indicate if the xml document is standalone 142 * @throws XMLStreamException If given encoding does not match encoding of the 143 * underlying stream 144 */ 145 public void writeStartDocument(String encoding, String version, String standalone) 146 throws XMLStreamException { 147 if (_state > 0) { 148 throw new XMLStreamException("XML declaration must be as the first line in the XML document."); 149 } 150 _state = STATE_XML_DECL; 151 String enc = encoding; 152 if (enc == null) { 153 enc = _encoding; 154 } else { 155 if (!isEncodingSupported(encoding)) { 156 throw new XMLStreamException( 157 new UnsupportedEncodingException("The basic XMLWriter does " 158 + "not support " + encoding)); 159 } 160 } 161 162 if (version == null) { 163 version = XMLStreamWriter.DEFAULT_XML_VERSION; 164 } 165 166 _writer.write("<?xml version=\""); 167 _writer.write(version); 168 _writer.write(DOUBLEQUOT); 169 170 if (enc != null) { 171 _writer.write(" encoding=\""); 172 _writer.write(enc); 173 _writer.write(DOUBLEQUOT); 174 } 175 176 if (standalone != null) { 177 _writer.write(" standalone=\""); 178 _writer.write(standalone); 179 _writer.write(DOUBLEQUOT); 180 } 181 _writer.write("?>"); 182 writeLineSeparator(); 183 } 184 185 186 /** 187 * Write a DTD section. This string represents the entire doctypedecl production 188 * from the XML 1.0 specification. 189 * 190 * @param dtd the DTD to be written 191 * @throws XMLStreamException 192 */ 193 public void writeDTD(String dtd) throws XMLStreamException { 194 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 195 closeStartTag(); 196 } 197 _writer.write(dtd); 198 writeLineSeparator(); 199 } 200 201 /** 202 * Writes a start tag to the output. 203 * @param localName local name of the tag, may not be null 204 * @throws XMLStreamException 205 */ 206 public void writeStartElement(String localName) throws XMLStreamException { 207 if (localName == null || localName.length() == 0) { 208 throw new XMLStreamException("Local Name cannot be null or empty"); 209 } 210 211 _state = STATE_ELEMENT; 212 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 213 closeStartTag(); 214 } 215 216 _currentEle = new Element(_currentEle, localName, false); 217 openStartTag(); 218 219 _writer.write(localName); 220 } 221 222 /** 223 * Writes an empty element tag to the output 224 * @param localName local name of the tag, may not be null 225 * @throws XMLStreamException 226 */ 227 public void writeEmptyElement(String localName) throws XMLStreamException { 228 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 229 closeStartTag(); 230 } 231 232 _currentEle = new Element(_currentEle, localName, true); 233 234 openStartTag(); 235 _writer.write(localName); 236 } 237 238 /** 239 * Writes an attribute to the output stream without a prefix. 240 * @param localName the local name of the attribute 241 * @param value the value of the attribute 242 * @throws IllegalStateException if the current state does not allow Attribute writing 243 * @throws XMLStreamException 244 */ 245 public void writeAttribute(String localName, String value) 246 throws XMLStreamException { 247 if (_currentEle.getState() != ELEMENT_STARTTAG_OPEN) { 248 throw new XMLStreamException( 249 "Attribute not associated with any element"); 250 } 251 252 _writer.write(SPACE); 253 _writer.write(localName); 254 _writer.write("=\""); 255 writeXMLContent( 256 value, 257 true, // true = escapeChars 258 true); // true = escapeDoubleQuotes 259 _writer.write(DOUBLEQUOT); 260 } 261 262 public void writeEndDocument() throws XMLStreamException { 263 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 264 closeStartTag(); 265 } 266 267 /** 268 * close unclosed elements if any 269 */ 270 while (_currentEle != null) { 271 272 if (!_currentEle.isEmpty()) { 273 _writer.write(OPEN_END_TAG); 274 _writer.write(_currentEle.getLocalName()); 275 _writer.write(CLOSE_END_TAG); 276 } 277 278 _currentEle = _currentEle.getParent(); 279 } 280 } 281 282 public void writeEndElement() throws XMLStreamException { 283 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 284 closeStartTag(); 285 } 286 287 if (_currentEle == null) { 288 throw new XMLStreamException("No element was found to write"); 289 } 290 291 if (_currentEle.isEmpty()) { 292 return; 293 } 294 295 _writer.write(OPEN_END_TAG); 296 _writer.write(_currentEle.getLocalName()); 297 _writer.write(CLOSE_END_TAG); 298 writeLineSeparator(); 299 300 _currentEle = _currentEle.getParent(); 301 } 302 303 public void writeCData(String cdata) throws XMLStreamException { 304 if (cdata == null) { 305 throw new XMLStreamException("cdata cannot be null"); 306 } 307 308 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 309 closeStartTag(); 310 } 311 312 _writer.write(START_CDATA); 313 _writer.write(cdata); 314 _writer.write(END_CDATA); 315 } 316 317 public void writeCharacters(String data) throws XMLStreamException { 318 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 319 closeStartTag(); 320 } 321 322 writeXMLContent(data); 323 } 324 325 public void writeCharacters(char[] data, int start, int len) 326 throws XMLStreamException { 327 if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { 328 closeStartTag(); 329 } 330 331 writeXMLContent(data, start, len, _escapeCharacters); 332 } 333 334 /** 335 * Close this XMLStreamWriter by closing underlying writer. 336 */ 337 public void close() throws XMLStreamException { 338 if (_writer != null) { 339 _writer.close(); 340 } 341 _writer = null; 342 _currentEle = null; 343 _state = 0; 344 } 345 346 /** 347 * Flush this XMLStreamWriter by flushing underlying writer. 348 */ 349 public void flush() throws XMLStreamException { 350 if (_writer != null) { 351 _writer.flush(); 352 } 353 } 354 355 /** 356 * Set the flag to indicate if the writer should add line separator 357 * @param doIndent 358 */ 359 public void setDoIndent(boolean doIndent) { 360 _doIndent = doIndent; 361 } 362 /** 363 * Writes XML content to underlying writer. Escapes characters unless 364 * escaping character feature is turned off. 365 */ 366 private void writeXMLContent(char[] content, int start, int length, 367 boolean escapeChars) throws XMLStreamException { 368 if (!escapeChars) { 369 _writer.write(content, start, length); 370 return; 371 } 372 373 // Index of the next char to be written 374 int startWritePos = start; 375 376 final int end = start + length; 377 378 for (int index = start; index < end; index++) { 379 char ch = content[index]; 380 381 if (!_writer.canEncode(ch)){ 382 _writer.write(content, startWritePos, index - startWritePos ); 383 384 // Escape this char as underlying encoder cannot handle it 385 _writer.write( ENCODING_PREFIX ); 386 _writer.write(Integer.toHexString(ch)); 387 _writer.write( SEMICOLON ); 388 startWritePos = index + 1; 389 continue; 390 } 391 392 switch (ch) { 393 case OPEN_START_TAG: 394 _writer.write(content, startWritePos, index - startWritePos); 395 _writer.write("<"); 396 startWritePos = index + 1; 397 398 break; 399 400 case AMPERSAND: 401 _writer.write(content, startWritePos, index - startWritePos); 402 _writer.write("&"); 403 startWritePos = index + 1; 404 405 break; 406 407 case CLOSE_START_TAG: 408 _writer.write(content, startWritePos, index - startWritePos); 409 _writer.write(">"); 410 startWritePos = index + 1; 411 412 break; 413 } 414 } 415 416 // Write any pending data 417 _writer.write(content, startWritePos, end - startWritePos); 418 } 419 420 private void writeXMLContent(String content) throws XMLStreamException { 421 if ((content != null) && (content.length() > 0)) { 422 writeXMLContent(content, 423 _escapeCharacters, // boolean = escapeChars 424 false); // false = escapeDoubleQuotes 425 } 426 } 427 428 /** 429 * Writes XML content to underlying writer. Escapes characters unless 430 * escaping character feature is turned off. 431 */ 432 private void writeXMLContent( 433 String content, 434 boolean escapeChars, 435 boolean escapeDoubleQuotes) 436 throws XMLStreamException { 437 438 if (!escapeChars) { 439 _writer.write(content); 440 441 return; 442 } 443 444 // Index of the next char to be written 445 int startWritePos = 0; 446 447 final int end = content.length(); 448 449 for (int index = 0; index < end; index++) { 450 char ch = content.charAt(index); 451 452 if (!_writer.canEncode(ch)){ 453 _writer.write(content, startWritePos, index - startWritePos ); 454 455 // Escape this char as underlying encoder cannot handle it 456 _writer.write( ENCODING_PREFIX ); 457 _writer.write(Integer.toHexString(ch)); 458 _writer.write( SEMICOLON ); 459 startWritePos = index + 1; 460 continue; 461 } 462 463 switch (ch) { 464 case OPEN_START_TAG: 465 _writer.write(content, startWritePos, index - startWritePos); 466 _writer.write("<"); 467 startWritePos = index + 1; 468 469 break; 470 471 case AMPERSAND: 472 _writer.write(content, startWritePos, index - startWritePos); 473 _writer.write("&"); 474 startWritePos = index + 1; 475 476 break; 477 478 case CLOSE_START_TAG: 479 _writer.write(content, startWritePos, index - startWritePos); 480 _writer.write(">"); 481 startWritePos = index + 1; 482 483 break; 484 485 case DOUBLEQUOT: 486 _writer.write(content, startWritePos, index - startWritePos); 487 if (escapeDoubleQuotes) { 488 _writer.write("""); 489 } else { 490 _writer.write(DOUBLEQUOT); 491 } 492 startWritePos = index + 1; 493 494 break; 495 } 496 } 497 498 // Write any pending data 499 _writer.write(content, startWritePos, end - startWritePos); 500 } 501 502 503 /** 504 * marks open of start tag and writes the same into the writer. 505 */ 506 private void openStartTag() throws XMLStreamException { 507 _currentEle.setState(ELEMENT_STARTTAG_OPEN); 508 _writer.write(OPEN_START_TAG); 509 } 510 511 /** 512 * marks close of start tag and writes the same into the writer. 513 */ 514 private void closeStartTag() throws XMLStreamException { 515 if (_currentEle.isEmpty()) { 516 _writer.write(CLOSE_EMPTY_ELEMENT); 517 } else { 518 _writer.write(CLOSE_START_TAG); 519 520 } 521 522 if (_currentEle.getParent() == null) { 523 writeLineSeparator(); 524 } 525 526 _currentEle.setState(ELEMENT_STARTTAG_CLOSE); 527 528 } 529 530 /** 531 * Write a line separator 532 * @throws XMLStreamException 533 */ 534 private void writeLineSeparator() throws XMLStreamException { 535 if (_doIndent) { 536 _writer.write(_lineSep, 0, _lineSep.length); 537 } 538 } 539 private boolean isEncodingSupported(String encoding) { 540 if (encoding.equalsIgnoreCase("UTF-32")) { 541 return false; 542 } 543 return true; 544 } 545 /* 546 * Start of Internal classes. 547 * 548 */ 549 protected class Element { 550 551 /** 552 * the parent element 553 */ 554 protected Element _parent; 555 /** 556 * The size of the stack. 557 */ 558 protected short _Depth; 559 /** 560 * indicate if an element is an empty one 561 */ 562 boolean _isEmptyElement = false; 563 String _localpart; 564 int _state; 565 566 /** 567 * Default constructor. 568 */ 569 public Element() { 570 } 571 572 /** 573 * @param parent the parent of the element 574 * @param localpart name of the element 575 * @param isEmpty indicate if the element is an empty one 576 */ 577 public Element(Element parent, String localpart, boolean isEmpty) { 578 _parent = parent; 579 _localpart = localpart; 580 _isEmptyElement = isEmpty; 581 } 582 583 public Element getParent() { 584 return _parent; 585 } 586 587 public String getLocalName() { 588 return _localpart; 589 } 590 591 /** 592 * get the state of the element 593 */ 594 public int getState() { 595 return _state; 596 } 597 598 /** 599 * Set the state of the element 600 * 601 * @param state the state of the element 602 */ 603 public void setState(int state) { 604 _state = state; 605 } 606 607 public boolean isEmpty() { 608 return _isEmptyElement; 609 } 610 } 611 }