001package org.hl7.fhir.utilities.xml; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.io.IOException; 035import java.io.OutputStream; 036import java.io.OutputStreamWriter; 037import java.io.UnsupportedEncodingException; 038 039import org.hl7.fhir.utilities.ElementDecoration; 040 041/** 042 * XML Writer class. 043 */ 044public class XMLWriter extends OutputStreamWriter implements IXMLWriter { 045 046 private boolean xmlHeader = true; 047 private String charset; 048 private boolean prettyBase; 049 private boolean prettyHeader; 050 private boolean pendingClose; 051 private boolean pendingOpen; 052 private String pendingComment; 053 private int lineType = LINE_UNIX; 054 private OutputStream stream; 055 private boolean started = false; 056 private String[] specialAttributeNames = new String[] {"id", "name" }; 057 private boolean sortAttributes; 058 private int attributeLineWrap; 059 private boolean xml1_1; 060 061 public final static int LINE_UNIX = 0; 062 public final static int LINE_WINDOWS = 1; 063 064 public XMLWriter(OutputStream stream, String charset, boolean xml1_1) throws UnsupportedEncodingException { 065 super(stream, charset); 066 this.stream = stream; 067 this.charset = charset; 068 this.xml1_1 = xml1_1; 069 } 070 public XMLWriter(OutputStream stream, String charset) throws UnsupportedEncodingException { 071 super(stream, charset); 072 this.stream = stream; 073 this.charset = charset; 074 this.xml1_1 = false; 075 } 076 077 protected boolean condition(boolean bTest, String message) throws IOException { 078 if (!bTest) 079 throw new IOException(message); 080 return bTest; 081 } 082 083 // -- writing context ------------------------------------------------ 084 085 086 087 /** 088 * Returns the encoding. 089 * 090 * @param charset 091 * @return encoding 092 * @throws IOException 093 */ 094 public static String getXMLCharsetName(String charset) throws IOException { 095 if (charset == null || charset.equals("")) 096 return "UTF-8"; 097 else if (charset.equals("US-ASCII")) 098 return "UTF-8"; 099 else if (XMLUtil.charSetImpliesAscii(charset)) 100 return "ISO-8859-1"; 101 else if (charset.equals("UTF-8")) 102 return "UTF-8"; 103 else if (charset.equals("UTF-16") || charset.equals("UTF-16BE") || charset.equals("UTF-16LE")) 104 return "UTF-16"; 105 else 106 throw new IOException("Unknown charset encoding "+charset); 107 } 108 109 /* (non-Javadoc) 110 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#start() 111 */ 112 @Override 113 public void start() throws IOException { 114 condition(!started, "attempt to start after starting"); 115 levels.clear(); 116 attributes = null; 117 try { 118 if (xmlHeader) { 119 write("<?xml version=\""+(xml1_1 ? "1.1" : "1.0")+"\" encoding=\""+getXMLCharsetName(charset)+"\"?>"); 120 if (prettyBase || prettyHeader) 121 write(lineType == LINE_UNIX ? "\n" : "\r\n"); 122 } 123 } catch (UnsupportedEncodingException e) { 124 // TODO Auto-generated catch block 125 throw new IOException(e.getMessage()); 126 } 127 started = true; 128 } 129 130 private void checkStarted () throws IOException { 131 condition(started, "not started"); 132 } 133 134 private void checkInElement() throws IOException { 135 condition(levels.size() > 0, "not in an element"); 136 } 137 138 // -- attributes ---------------------------------------------------- 139 140 private String[][] attributes; 141 142 private void addAttribute(String name, String value) throws IOException { 143 addAttribute(name, value, false); 144 } 145 146 private void addAttribute(String name, String value, boolean noLines) throws IOException { 147 if (!XMLUtil.isNMToken(name)) 148 throw new IOException("XML name "+name+" is not valid for value '"+value+"'"); 149 150 newLevelIfRequired(); 151 value = XMLUtil.escapeXML(value, charset, noLines); 152 153 if (attributes == null) 154 attributes = new String[][] {{name, value}}; 155 else { 156 String[][] newattr = new String[attributes.length+1][]; 157 for (int i = 0; i < attributes.length; i++) { 158 condition(!attributes[i][0].equals(name), "attempt to define attribute with name "+name+" more than once for value '"+value+"'"); 159 newattr[i] = attributes[i]; 160 } 161 attributes = newattr; 162 attributes[attributes.length-1] = new String[] {name, value}; 163 } 164 } 165 166 protected String getAttribute(String name) { 167 if (attributes != null) { 168 for (int i = 0; i < attributes.length; i++) { 169 if (attributes[i][0].equals(name)) { 170 return attributes[i][1]; 171 } 172 } 173 } 174 return null; 175 } 176 177 protected void setAttribute(String name, String value) throws IOException { 178 newLevelIfRequired(); 179 if (attributes == null) 180 addAttribute(name, value, false); 181 else { 182 for (int i = 0; i < attributes.length; i++) { 183 if (attributes[i][0].equals(name)) { 184 attributes[i][1] = XMLUtil.escapeXML(value, charset, false); 185 return; 186 } 187 } 188 addAttribute(name, value); 189 } 190 } 191 192 protected void commitAttributes() throws IOException { 193 194 } 195 196 197 private boolean nameIsSpecial(String name) { 198 for (int i = 0; i < specialAttributeNames.length; i++) { 199 String n = specialAttributeNames[i]; 200 if (n.equalsIgnoreCase(name)) 201 return true; 202 } 203 return false; 204 } 205 206 private void writeAttributes(int col) throws IOException { 207 commitAttributes(); 208 if (attributes != null && sortAttributes) 209 sortAttributes(); 210 int c = col; 211 c = writeAttributeSet(true, c, col); 212 writeAttributeSet(false, c, col); 213 attributes = null; 214 } 215 216 217 private void sortAttributes() { 218 // bubble sort - look, it's easy 219 for (int i = 0; i < attributes.length - 1; i++) { 220 for (int j = 0; j < attributes.length - 1; j++) { 221 if (String.CASE_INSENSITIVE_ORDER.compare(attributes[j][0], attributes[j+1][0]) < 0) { 222 String[] t = attributes[j]; 223 attributes[j] = attributes[j+1]; 224 attributes[j+1] = t; 225 } 226 } 227 } 228 229 } 230 231 232 private int writeAttributeSet(boolean special, int col, int wrap) throws IOException { 233 // first pass: name, id 234 if (attributes != null) { 235 for (int i=0; i < attributes.length; i++) { 236 String[] element = attributes[i]; 237 if (nameIsSpecial(element[0]) == special) { 238 col = col + element[0].length()+element[1].length() + 4; 239 if (isPretty() && attributeLineWrap > 0 && col > attributeLineWrap && col > wrap) { 240 write(lineType == LINE_UNIX ? "\n" : "\r\n"); 241 for (int j = 0; j < wrap; j++) 242 write(" "); 243 col = wrap; 244 } 245 write(' '); 246 write(element[0]); 247 write("=\""); 248 if (element[1] != null) 249 write(xmlEscape(element[1])); 250 write("\""); 251 } 252 } 253 } 254 return col; 255 } 256 257 protected String xmlEscape(String s) { 258 StringBuilder b = new StringBuilder(); 259 for (char c : s.toCharArray()) { 260 if (c < ' ') { 261 b.append("&#x"); 262 b.append(Integer.toHexString(c).toUpperCase()); 263 b.append(";"); 264 } else 265 b.append(c); 266 } 267 return b.toString(); 268 } 269 270 /* (non-Javadoc) 271 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String, java.lang.String, boolean) 272 */ 273 @Override 274 public void attribute(String namespace, String name, String value, boolean onlyIfNotEmpty) throws IOException { 275 if (!onlyIfNotEmpty || value != null && !value.equals("")) 276 attribute(namespace, name, value); 277 } 278 279 /* (non-Javadoc) 280 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String, java.lang.String) 281 */ 282 @Override 283 public void attribute(String namespace, String name, String value) throws IOException { 284 285 checkStarted(); 286 if (namespace == null || namespace.equals("")) 287 addAttribute(name, value); 288 else 289 addAttribute(getNSAbbreviation(namespace)+name, value); 290 } 291 292 /* (non-Javadoc) 293 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String, boolean) 294 */ 295 @Override 296 public void attribute(String name, String value, boolean onlyIfNotEmpty) throws IOException { 297 if (!onlyIfNotEmpty || value != null && !value.equals("")) 298 attribute(name, value); 299 } 300 301 /* (non-Javadoc) 302 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attribute(java.lang.String, java.lang.String) 303 */ 304 @Override 305 public void attribute(String name, String value) throws IOException { 306 checkStarted(); 307 addAttribute(name, value); 308 } 309 310 /* (non-Javadoc) 311 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#attributeNoLines(java.lang.String, java.lang.String) 312 */ 313 @Override 314 public void attributeNoLines(String name, String value) throws IOException { 315 checkStarted(); 316 addAttribute(name, value, true); 317 } 318 319 // -- levels ------------------------------------------------- 320 321 private XMLWriterStateStack levels = new XMLWriterStateStack(); 322 323 private void newLevelIfRequired() throws IOException { 324 if (!pendingOpen) { 325 if (!levels.empty()) 326 levels.current().seeChild(); 327 XMLWriterState level = new XMLWriterState(); 328 level.setPretty(isPretty()); 329 levels.push(level); 330 pendingOpen = true; 331 } 332 } 333 334 // -- namespaces --------------------------------------------- 335 336 337 private void defineNamespace(String namespace, String abbrev) throws IOException { 338 checkStarted(); 339 if (namespace != null && !namespace.equals("")) { 340 if ("".equals(abbrev)) 341 abbrev = null; 342 343 newLevelIfRequired(); 344 345 levels.current().addNamespaceDefn(namespace, abbrev); 346 if (abbrev == null) 347 addAttribute("xmlns", namespace); 348 else 349 addAttribute("xmlns:"+abbrev, namespace); 350 } 351 } 352 353 /* (non-Javadoc) 354 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#findByNamespace(java.lang.String) 355 */ 356 public XMLNamespace findByNamespace(String namespace) { 357 for (int i = levels.size() - 1; i >= 0; i--) { 358 XMLNamespace ns = levels.item(i).getDefnByNamespace(namespace); 359 if (ns != null) 360 return ns; 361 } 362 return null; 363 } 364 365 /* (non-Javadoc) 366 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#namespaceDefined(java.lang.String) 367 */ 368 @Override 369 public boolean namespaceDefined(String namespace) { 370 return namespace == null || namespace.equals("") || findByNamespace(namespace) != null; 371 } 372 373 /* (non-Javadoc) 374 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#findByAbbreviation(java.lang.String) 375 */ 376 public XMLNamespace findByAbbreviation(String abbreviation) { 377 for (int i = levels.size() - 1; i >= 0; i--) { 378 XMLNamespace ns = levels.item(i).getDefnByAbbreviation(abbreviation); 379 if (ns != null) 380 return ns; 381 } 382 return null; 383 } 384 385 /* (non-Javadoc) 386 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#abbreviationDefined(java.lang.String) 387 */ 388 @Override 389 public boolean abbreviationDefined(String abbreviation) { 390 return findByAbbreviation(abbreviation) != null; 391 } 392 393 protected XMLNamespace findDefaultNamespace() { 394 for (int i = levels.size() - 1; i >= 0; i--) { 395 XMLNamespace ns = levels.item(i).getDefaultNamespace(); 396 if (ns != null) 397 return ns; 398 } 399 return null; 400 } 401 402 /* (non-Javadoc) 403 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#getDefaultNamespace() 404 */ 405 @Override 406 public String getDefaultNamespace() { 407 XMLNamespace ns = findDefaultNamespace(); 408 if (ns == null) 409 return null; 410 else 411 return ns.getNamespace(); 412 } 413 414 /* (non-Javadoc) 415 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#namespace(java.lang.String) 416 */ 417 @Override 418 public void namespace(String namespace) throws IOException { 419 if (!namespaceDefined(namespace)) { 420 int index = 0; 421 while (abbreviationDefined("ns"+Integer.toString(index))) 422 index++; 423 defineNamespace(namespace, "ns"+Integer.toString(index)); 424 } 425 } 426 427 /* (non-Javadoc) 428 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#defaultNamespace(java.lang.String) 429 * 430 * Replace defaultNamespace() 431 */ 432 @Override 433 public void setDefaultNamespace(String namespace) throws IOException { 434 if ((namespace == null && getDefaultNamespace() != null) || 435 (namespace != null && !namespace.equals(getDefaultNamespace()))) 436 defineNamespace(namespace, ""); 437 } 438 439 /* (non-Javadoc) 440 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#namespace(java.lang.String, java.lang.String) 441 */ 442 @Override 443 public void namespace(String namespace, String abbreviation) throws IOException { 444 XMLNamespace ns = findByAbbreviation(abbreviation); 445 if (ns == null || !ns.getNamespace().equals(namespace)) 446 defineNamespace(namespace, abbreviation); 447 } 448 449 450 private String getNSAbbreviation(String namespace) throws IOException { 451 if ("http://www.w3.org/XML/1998/namespace".equals(namespace)) 452 return "xml:"; 453 454 if (namespace == null || "".equals(namespace) || "noNamespace".equals(namespace)) 455 return ""; 456 457 XMLNamespace ns = findByNamespace(namespace); 458 if (ns == null) 459 throw new IOException("Namespace "+namespace+" is not defined"); 460 else if (ns.getAbbreviation() == null) 461 return ""; 462 else 463 return ns.getAbbreviation()+":"; 464 } 465 466 // -- public API ----------------------------------------------------------- 467 468 /* (non-Javadoc) 469 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#comment(java.lang.String, boolean) 470 */ 471 @Override 472 public void comment(String comment, boolean doPretty) throws IOException { 473 checkStarted(); 474 if (pendingClose) { 475 write('>'); 476 writePendingComment(); 477 pendingClose = false; 478 } 479 if (doPretty) { 480 writePretty(); 481 } 482 if (levels.inComment()) 483 write("<!-- "+comment+" -- >"); 484 else 485 write("<!-- "+comment+" -->"); 486 if (doPretty && !isPretty()) 487 writePretty(); 488 } 489 490 491 private void writePendingComment() throws IOException { 492 if (pendingComment != null) { 493 if (isPretty()) 494 write(" "); 495 if (levels.inComment()) 496 write("<!-- "+pendingComment+" -- >"); 497 else 498 write("<!-- "+pendingComment+" -->"); 499 } 500 } 501 502 /* (non-Javadoc) 503 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#open(java.lang.String, java.lang.String) 504 */ 505 @Override 506 public void enter(String namespace, String name) throws IOException { 507 enter(namespace, name, null); 508 } 509 510 511 /* (non-Javadoc) 512 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#open(java.lang.String, java.lang.String, java.lang.String) 513 */ 514 @Override 515 public void enter(String namespace, String name, String comment) throws IOException { 516 if (name == null) 517 throw new Error("name == null"); 518 if (!XMLUtil.isNMToken(name)) 519 throw new IOException("XML name "+name+" is not valid"); 520 checkStarted(); 521 if (pendingClose) { 522 write('>'); 523 writePendingComment(); 524 pendingClose = false; 525 } 526 527 if (name == null) { 528 throw new IOException("name is null"); 529 } 530 newLevelIfRequired(); 531 levels.current().setName(name); 532 levels.current().setNamespace(namespace); 533 int col = writePretty(); 534 write('<'); 535 if (namespace == null) { 536 write(name); 537 col = col + name.length()+1; 538 } else { 539 String n = getNSAbbreviation(namespace)+name; 540 write(n); 541 col = col + n.length()+1; 542 } 543 writeAttributes(col); 544 pendingOpen = false; 545 pendingClose = true; 546 pendingComment = comment; 547 } 548 549 550 /* (non-Javadoc) 551 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#close(java.lang.String) 552 */ 553 @Override 554 public void exit(String name) throws IOException { 555 checkStarted(); 556 if (levels.empty()) 557 throw new IOException("Unable to close null|"+name+", nothing to close"); 558 if (levels.current().getNamespace() != null || !levels.current().getName().equals(name)) 559 throw new IOException("Unable to close null|"+name+", found "+levels.current().getNamespace()+"|"+levels.current().getName()); 560 exit(); 561 } 562 563 /* (non-Javadoc) 564 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#close(java.lang.String, java.lang.String) 565 */ 566 @Override 567 public void exit(String namespace, String name) throws IOException { 568 checkStarted(); 569 if (levels.empty()) 570 throw new IOException("Unable to close "+namespace+"|"+name+", nothing to close"); 571 if (levels == null) 572 throw new Error("levels = null"); 573 if (levels.current() == null) 574 throw new Error("levels.current() = null"); 575 if (levels.current().getName() == null) 576 throw new Error("levels.current().getName() = null"); 577 if (levels.current().getNamespace() == null) 578 throw new Error("levels.current().getNamespace() = null"); 579 if (name == null) 580 throw new Error("name = null"); 581 if (namespace == null) 582 throw new Error("namespace = null"); 583 if (!levels.current().getNamespace().equals(namespace) || !levels.current().getName().equals(name)) 584 throw new IOException("Unable to close "+namespace+"|"+name+", found "+levels.current().getNamespace()+"|"+levels.current().getName()); 585 exit(); 586 } 587 588 /* (non-Javadoc) 589 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#closeToLevel(int) 590 */ 591 @Override 592 public void exitToLevel(int count) throws IOException { 593 checkStarted(); 594 while (levels.size() > count) 595 exit(); 596 } 597 598 599 /* (non-Javadoc) 600 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#close() 601 */ 602 @Override 603 public void close() throws IOException { 604 checkStarted(); 605 if (!levels.empty()) 606 throw new IOException("Called close before exiting all opened elements"); 607 super.close(); 608 } 609 610 @Override 611 public void end() throws IOException { 612 checkStarted(); 613 if (!levels.empty()) 614 throw new IOException("Called end() before exiting all opened elements"); 615 flush(); 616 } 617 @Override 618 public void exit() throws IOException { 619 checkStarted(); 620 if (levels.empty()) { 621 throw new IOException("Called exit one too many times"); 622 } else { 623 if (pendingClose) { 624 write("/>"); 625 writePendingComment(); 626 pendingClose = false; 627 } else { 628 if (levels.current().hasChildren()) 629 writePretty(); 630 write("</"); 631 if (levels.current().getNamespace() == null) 632 write(levels.current().getName()); 633 else 634 write(getNSAbbreviation(levels.current().getNamespace())+levels.current().getName()); 635 write('>'); 636 } 637 levels.pop(); 638 } 639 } 640 641 /* (non-Javadoc) 642 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#open(java.lang.String) 643 */ 644 @Override 645 public void enter(String name) throws IOException { 646 enter(null, name); 647 } 648 649 650 /* (non-Javadoc) 651 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, java.lang.String, boolean) 652 */ 653 @Override 654 public void element(String namespace, String name, String content, boolean onlyIfNotEmpty) throws IOException { 655 if (!onlyIfNotEmpty || content != null && !content.equals("")) 656 element(namespace, name, content); 657 } 658 659 /* (non-Javadoc) 660 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, java.lang.String, java.lang.String) 661 */ 662 @Override 663 public void element(String namespace, String name, String content, String comment) throws IOException { 664 if (!XMLUtil.isNMToken(name)) 665 throw new IOException("XML name "+name+" is not valid"); 666 enter(namespace, name, comment); 667 text(content); 668 exit(); 669 } 670 671 /* (non-Javadoc) 672 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, java.lang.String) 673 */ 674 @Override 675 public void element(String namespace, String name, String content) throws IOException { 676 if (!XMLUtil.isNMToken(name)) 677 throw new IOException("XML name "+name+" is not valid"); 678 enter(namespace, name); 679 text(content); 680 exit(); 681 } 682 683 /* (non-Javadoc) 684 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String, boolean) 685 */ 686 @Override 687 public void element(String name, String content, boolean onlyIfNotEmpty) throws IOException { 688 if (!onlyIfNotEmpty || content != null && !content.equals("")) 689 element(null, name, content); 690 } 691 692 /* (non-Javadoc) 693 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#element(java.lang.String, java.lang.String) 694 */ 695 @Override 696 public void element(String name, String content) throws IOException { 697 element(null, name, content); 698 } 699 700 @Override 701 public void element(String name) throws IOException { 702 element(null, name, null); 703 } 704 705 /* (non-Javadoc) 706 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#text(java.lang.String) 707 */ 708 @Override 709 public void text(String content) throws IOException { 710 text(content, false); 711 } 712 713 /* (non-Javadoc) 714 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#text(java.lang.String, boolean) 715 * 716 * Replace escapeText() 717 */ 718 @Override 719 public void text(String content, boolean dontEscape) throws IOException { 720 checkInElement(); 721 if (content != null) { 722 if (pendingClose) { 723 write(">"); 724 writePendingComment(); 725 pendingClose = false; 726 } 727 if (dontEscape) 728 write(content); 729 else 730 write(XMLUtil.escapeXML(content, charset, false)); 731 } 732 } 733 734 /* (non-Javadoc) 735 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#cData(java.lang.String) 736 */ 737 @Override 738 public void cData(String text) throws IOException { 739 text("<![CDATA["+text+"]]>"); 740 } 741 742 /* (non-Javadoc) 743 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#writeBytes(byte[]) 744 */ 745 @Override 746 public void writeBytes(byte[] bytes) throws IOException { 747 checkInElement(); 748 if (pendingClose) { 749 write(">"); 750 writePendingComment(); 751 pendingClose = false; 752 } 753 flush(); 754 stream.write(bytes); 755 } 756 757 758 /* (non-Javadoc) 759 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#isPretty() 760 */ 761 @Override 762 public boolean isPretty() throws IOException { 763 return (levels == null || levels.empty()) ? prettyBase : levels.current().isPretty(); 764 } 765 766 /* (non-Javadoc) 767 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#setPretty(boolean) 768 */ 769 @Override 770 public void setPretty(boolean pretty) throws IOException { 771 if (levels == null || levels.empty()) 772 this.prettyBase = pretty; 773 else 774 levels.current().setPretty(pretty); 775 } 776 777 /* (non-Javadoc) 778 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#startCommentBlock() 779 */ 780 @Override 781 public void startCommentBlock() throws IOException { 782 if (levels.inComment()) 783 throw new IOException("cannot nest comments"); 784 levels.current().setInComment(true); 785 if (isPretty()) 786 writePretty(); 787 write("<!--"); 788 if (isPretty()) 789 writePretty(); 790 } 791 792 /* (non-Javadoc) 793 * @see org.eclipse.ohf.utilities.xml.IXMLWriter#endCommentBlock() 794 */ 795 @Override 796 public void endCommentBlock() throws IOException { 797 if (!levels.inComment()) 798 throw new IOException("cannot close a comment block when it is open"); 799 if (!levels.current().isInComment()) 800 throw new IOException("cannot close a comment block when it is open"); 801 if (isPretty()) 802 writePretty(); 803 write("-->"); 804 if (isPretty()) 805 writePretty(); 806 levels.current().setInComment(false); 807 } 808 809 public boolean isSortAttributes() { 810 return sortAttributes; 811 } 812 813 public void setSortAttributes(boolean sortAttributes) { 814 this.sortAttributes = sortAttributes; 815 } 816 817 818 public boolean isPrettyHeader() { 819 return prettyHeader; 820 } 821 822 public void setPrettyHeader(boolean pretty) { 823 this.prettyHeader = pretty; 824 } 825 826 public int writePretty() throws IOException { 827 return writePretty(true); 828 } 829 830 public int writePretty(boolean eoln) throws IOException { 831 if (isPretty()) { 832 if (eoln) 833 write(lineType == LINE_UNIX ? "\n" : "\r\n"); 834 for (int i = 0; i < levels.size() - 1; i++) 835 write(" "); 836 return (levels.size() - 1) * 2; 837 } else 838 return 0; 839 } 840 841 public int getLineType() { 842 return lineType; 843 } 844 845 public void setLineType(int lineType) { 846 this.lineType = lineType; 847 } 848 849 public boolean isXmlHeader() { 850 return xmlHeader; 851 } 852 853 public void setXmlHeader(boolean xmlHeader) { 854 this.xmlHeader = xmlHeader; 855 } 856 857 public String[] getSpecialAttributeNames() { 858 return specialAttributeNames; 859 } 860 861 public void setSpecialAttributeNames(String[] specialAttributeNames) { 862 this.specialAttributeNames = specialAttributeNames; 863 } 864 865 public int getAttributeLineWrap() { 866 return attributeLineWrap; 867 } 868 869 public void setAttributeLineWrap(int attributeLineWrap) { 870 this.attributeLineWrap = attributeLineWrap; 871 } 872 873 @Override 874 public void escapedText(String content) throws IOException { 875 text(""); 876 int i = content.length(); 877 if (isPretty()) 878 while (i > 0 && (content.charAt(i-1) == '\r' || content.charAt(i-1) == '\n')) 879 i--; 880 write(content.substring(0, i)); 881 } 882 883 public void processingInstruction(String value) throws IOException { 884 write("<?"+value+"?>"); 885 if (isPrettyHeader()) 886 write("\r\n"); 887 } 888 889 @Override 890 public void link(String href) { 891 // ignore this 892 893 } 894 895 @Override 896 public void anchor(String name) { 897 // ignore this 898 } 899 900 @Override 901 public void decorate(ElementDecoration element) throws IOException { 902 // nothing... 903 } 904 @Override 905 public void setSchemaLocation(String ns, String loc) throws IOException { 906 namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi"); 907 attribute("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", ns+" "+loc); 908 909 } 910 911 912}