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