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