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