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