001package com.hfg.xml; 002 003import java.io.*; 004import java.util.*; 005 006import org.xml.sax.InputSource; 007 008import com.hfg.util.Case; 009import com.hfg.util.CompareUtil; 010import com.hfg.util.collection.OrderedMap; 011import com.hfg.util.io.GZIP; 012import com.hfg.util.io.NoClosePrintWriter; 013import com.hfg.util.StringUtil; 014import com.hfg.util.collection.CollectionUtil; 015import com.hfg.xml.parser.XMLTagReader; 016 017//------------------------------------------------------------------------------ 018/** 019 XMLTag is a generic container for XML. It can be used both to construct XML or 020 to deconstruct XML into a DOM-like structure. 021 022 @author J. Alex Taylor, hairyfatguy.com 023 */ 024//------------------------------------------------------------------------------ 025// com.hfg XML/HTML Coding Library 026// 027// This library is free software; you can redistribute it and/or 028// modify it under the terms of the GNU Lesser General Public 029// License as published by the Free Software Foundation; either 030// version 2.1 of the License, or (at your option) any later version. 031// 032// This library is distributed in the hope that it will be useful, 033// but WITHOUT ANY WARRANTY; without even the implied warranty of 034// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 035// Lesser General Public License for more details. 036// 037// You should have received a copy of the GNU Lesser General Public 038// License along with this library; if not, write to the Free Software 039// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 040// 041// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 042// jataylor@hairyfatguy.com 043//------------------------------------------------------------------------------ 044 045public class XMLTag extends XMLContainerImpl implements XMLNode, Comparable 046{ 047 048 //########################################################################### 049 // PRIVATE FIELDS 050 //########################################################################### 051 052 private String mName; 053 private Map<String, XMLAttribute> mAttributes; 054 private XMLNamespace mNamespace; 055 // Attribute sorting is preferred for the consistency of attribute order 056 private boolean mSortAttributesBeforeWriting = true; 057 058 private static char sQuoteChar = '\''; // Not final since it can be changed 059 private static final String NL = System.getProperty("line.separator"); 060 061 //########################################################################### 062 // CONSTRUCTORS 063 //########################################################################### 064 065 //--------------------------------------------------------------------------- 066 /** 067 @param inName the tag name 068 */ 069 public XMLTag(String inName) 070 { 071 setTagName(inName); 072 } 073 074 //--------------------------------------------------------------------------- 075 /** 076 @param inName the tag name 077 */ 078 public XMLTag(XMLName inName) 079 { 080 if (inName != null) 081 { 082 setTagName(inName.getLocalName()); 083 setNamespace(inName.getNamespace()); 084 } 085 } 086 087 //--------------------------------------------------------------------------- 088 /** 089 @param inName the tag name 090 @param inAttributes a Collection of XMLAttribute objects 091 */ 092 public XMLTag(String inName, Collection<XMLAttribute> inAttributes) 093 { 094 setTagName(inName); 095 setAttributes(inAttributes); 096 } 097 098 //--------------------------------------------------------------------------- 099 /** 100 @param inName the tag name 101 @param inAttributes a Map of attribute (names and values as String objects) 102 */ 103 public XMLTag(String inName, Map<String,String> inAttributes) 104 { 105 if (CollectionUtil.hasValues(inAttributes)) 106 { 107 List<XMLAttribute> attributes = new ArrayList<>(); 108 109 for (String name : inAttributes.keySet()) 110 { 111 attributes.add(new XMLAttribute(name, inAttributes.get(name))); 112 } 113 114 setAttributes(attributes); 115 } 116 setTagName(inName); 117 } 118 119 //--------------------------------------------------------------------------- 120 /** 121 @param inName the tag name 122 @param inAttributes a Collection of XMLAttribute objects 123 @param inContent the tag's content 124 */ 125 public XMLTag(String inName, Collection inAttributes, String inContent) 126 { 127 this(inName, inAttributes); 128 setContent(inContent); 129 } 130 131 //--------------------------------------------------------------------------- 132 /** 133 @param inName the tag name 134 @param inAttributes a ImageMap of attribute (names and values as String objects) 135 @param inContent the tag's content 136 */ 137 public XMLTag(String inName, Map inAttributes, String inContent) 138 { 139 this(inName, inAttributes); 140 setContent(inContent); 141 } 142 143 //---------------------------------------------------------------------- 144 /** 145 @param inXML an InputStream containing XML text 146 */ 147 public XMLTag(InputStream inXML) 148 throws XMLException, IOException 149 { 150 construct(inXML); 151 } 152 153 //---------------------------------------------------------------------- 154 /** 155 @param inXML a Reader containing XML text 156 */ 157 public XMLTag(Reader inXML) 158 throws XMLException, IOException 159 { 160 construct(inXML); 161 } 162 163 //--------------------------------------------------------------------------- 164 /** 165 @param inXMLFile a File containing XML text 166 */ 167 public XMLTag(File inXMLFile) 168 throws XMLException, IOException 169 { 170 if (null == inXMLFile) 171 { 172 throw new XMLException("null File passed to XMLTag()!"); 173 } 174 175 construct(new FileReader(inXMLFile)); 176 } 177 178 179 //########################################################################### 180 // PUBLIC METHODS 181 //########################################################################### 182 183 //--------------------------------------------------------------------------- 184 public static void useDoubleQuotes(boolean inValue) 185 { 186 if (inValue) 187 { 188 sQuoteChar = '\"'; 189 } 190 else 191 { 192 sQuoteChar = '\''; 193 } 194 } 195 196 //--------------------------------------------------------------------------- 197 public XMLTag setSortAttributesBeforeWriting(boolean inValue) 198 { 199 mSortAttributesBeforeWriting = inValue; 200 return this; 201 } 202 203 //--------------------------------------------------------------------------- 204 @Override 205 public XMLTag clone() 206 { 207 XMLTag cloneObj = super.clone(); 208 209 if (mAttributes != null) 210 { 211 cloneObj.mAttributes = new OrderedMap<>(); 212 for (String key : mAttributes.keySet()) 213 { 214 cloneObj.mAttributes.put(key, mAttributes.get(key).clone()); 215 } 216 } 217 218 return cloneObj; 219 } 220 221 //--------------------------------------------------------------------------- 222 @Override 223 public boolean equals(Object inObj2) 224 { 225 return (0 == compareTo(inObj2)); 226 } 227 228 //-------------------------------------------------------------------------- 229 @Override 230 public int compareTo(Object inObj2) 231 { 232 int result = -1; 233 234 if (inObj2 instanceof XMLTag) 235 { 236 XMLTag tag2 = (XMLTag) inObj2; 237 238 result = CompareUtil.compare(getTagName(), tag2.getTagName()); 239 240 if (0 == result) 241 { 242 // Compare attributes 243 result = CompareUtil.compare(getAttributes(), tag2.getAttributes()); 244 245 if (0 == result) 246 { 247 // Compare content & subtags 248 result = CompareUtil.compare(getContentPlusSubtagList(), tag2.getContentPlusSubtagList()); 249 } 250 } 251 } 252 253 return result; 254 } 255 256 //--------------------------------------------------------------------------- 257 @Override 258 public XMLNode setContent(CharSequence inContent) 259 { 260 return (XMLNode) super.setContent(inContent); 261 } 262 263 264 //-------------------------------------------------------------------------- 265 public XMLTag addSubtag(String inTagName) 266 { 267 XMLTag subtag = new XMLTag(inTagName); 268 addSubtag(subtag); 269 270 return subtag; 271 } 272 273 //-------------------------------------------------------------------------- 274 public XMLTag addSubtag(XMLName inTagName) 275 { 276 XMLTag subtag = new XMLTag(inTagName); 277 addSubtag(subtag); 278 279 return subtag; 280 } 281 282 //-------------------------------------------------------------------------- 283 public String getStartTag() 284 { 285 Collection attributes = null; 286 if (mAttributes != null) attributes = mAttributes.values(); 287 288 return XMLUtil.composeStartTag(getTagName(), attributes, false); 289 } 290 291 //-------------------------------------------------------------------------- 292 public String getEndTag() 293 { 294 return XMLUtil.composeEndTag(getTagName()); 295 } 296 297 //-------------------------------------------------------------------------- 298 @Override 299 public boolean isEmptyTag() 300 { 301 boolean returnValue = false; 302 303 if (null == mContentAndSubtagList 304 || 0 == mContentAndSubtagList.size()) 305 { 306 returnValue = true; 307 } 308 309 return returnValue; 310 } 311 312 313 //--------------------------------------------------------------------------- 314 @Override 315 public XMLTag setTagName(XMLName inName) 316 { 317 // Check the name for XML validity. 318 XMLUtil.checkXMLNameValidity(inName.getLocalName()); 319 320 mName = inName.getLocalName(); 321 setNamespace(inName.getNamespace()); 322 323 return this; 324 } 325 326 //--------------------------------------------------------------------------- 327 @Override 328 public XMLTag setTagName(String inName) 329 { 330 // Check the name for XML validity. 331 XMLUtil.checkXMLNameValidity(inName); 332 333 mName = inName; 334 335 return this; 336 } 337 338 //--------------------------------------------------------------------------- 339 @Override 340 public String getTagName() 341 { 342 return mName; 343 } 344 345 //--------------------------------------------------------------------------- 346 public String getQualifiedTagName() 347 { 348 return (mNamespace != null && mNamespace.getPrefix() != null ? mNamespace.getPrefix() + ":" : "") + mName; 349 } 350 351 //--------------------------------------------------------------------------- 352 @Override 353 public void verifyTagName(XMLName inTagName) 354 { 355 verifyTagName(inTagName, Case.SENSITIVE); 356 } 357 358 //--------------------------------------------------------------------------- 359 @Override 360 public void verifyTagName(XMLName inTagName, Case inCaseSensitivity) 361 { 362 try 363 { 364 verifyTagName(inTagName.getLocalName(), inCaseSensitivity); 365 } 366 catch (XMLException e) 367 { 368 // Try the qualified tag name 369 verifyTagName(inTagName.getQualifiedName(), inCaseSensitivity); 370 } 371 } 372 373 //--------------------------------------------------------------------------- 374 @Override 375 public void verifyTagName(String inTagName) 376 { 377 verifyTagName(inTagName, Case.SENSITIVE); 378 } 379 380 //--------------------------------------------------------------------------- 381 @Override 382 public void verifyTagName(String inTagName, Case inCaseSensitivity) 383 { 384 boolean verified = false; 385 if (Case.INSENSITIVE.equals(inCaseSensitivity)) 386 { 387 if (getTagName().equalsIgnoreCase(inTagName)) 388 { 389 verified = true; 390 } 391 } 392 else if (getTagName().equals(inTagName)) 393 { 394 verified = true; 395 } 396 397 if (! verified) 398 { 399 throw new XMLException("Expected a tag name of " + StringUtil.singleQuote(inTagName) 400 + " but was given a tag with name " + StringUtil.singleQuote(getTagName()) + "!"); 401 } 402 } 403 404 405 //--------------------------------------------------------------------------- 406 @Override 407 public XMLTag setAttribute(String inAttributeName, Object inValue) 408 { 409 String stringValue = (inValue != null ? inValue.toString() : ""); 410 XMLAttribute attribute = new XMLAttribute(inAttributeName, stringValue); 411 setAttribute(attribute); 412 413 return this; 414 } 415 416 //--------------------------------------------------------------------------- 417 @Override 418 public XMLTag setAttribute(XMLName inAttributeName, Object inValue) 419 { 420 String stringValue = (inValue != null ? inValue.toString() : ""); 421 XMLAttribute attribute = new XMLAttribute(inAttributeName, stringValue); 422 setAttribute(attribute); 423 424 return this; 425 } 426 427 //--------------------------------------------------------------------------- 428 @Override 429 public XMLTag setAttribute(XMLAttribute inAttribute) 430 { 431 if (inAttribute != null) 432 { 433 if (null == mAttributes) mAttributes = new OrderedMap<>(4); 434 435 // Avoid overstriking the attribute's element when the attribute was 436 // reused without cloning. 437 if (inAttribute.getElement() != null) 438 { 439 inAttribute = inAttribute.clone(); 440 } 441 442 mAttributes.put(inAttribute.getName(), inAttribute); 443 444 inAttribute.setElement(this); 445 } 446 447 return this; 448 } 449 450 //--------------------------------------------------------------------------- 451 /** 452 Takes a Collection of XMLAttribute objects as input. 453 */ 454 @Override 455 public void setAttributes(Collection<XMLAttribute> inAttributes) 456 { 457 if (inAttributes != null) 458 { 459 for (XMLAttribute attribute : inAttributes) 460 { 461 setAttribute(attribute); 462 } 463 } 464 } 465 466 //--------------------------------------------------------------------------- 467 @Override 468 public XMLAttribute getAttribute(XMLName inAttributeName) 469 { 470 return getAttribute(inAttributeName.getLocalName()); 471 } 472 473 //--------------------------------------------------------------------------- 474 @Override 475 public XMLAttribute getAttribute(String inAttributeName) 476 { 477 XMLAttribute attribute = null; 478 479 if (mAttributes != null) 480 { 481 attribute = mAttributes.get(inAttributeName); 482 } 483 484 return attribute; 485 } 486 487 //--------------------------------------------------------------------------- 488 @Override 489 public boolean hasAttributes() 490 { 491 return (CollectionUtil.hasValues(mAttributes)); 492 } 493 494 //--------------------------------------------------------------------------- 495 @Override 496 public boolean hasAttribute(XMLName inAttributeName) 497 { 498 return (getAttribute(inAttributeName) != null); 499 } 500 501 //--------------------------------------------------------------------------- 502 @Override 503 public boolean hasAttribute(String inAttributeName) 504 { 505 return (getAttribute(inAttributeName) != null); 506 } 507 508 //--------------------------------------------------------------------------- 509 @Override 510 public String getAttributeValue(XMLName inAttributeName) 511 { 512 return getAttributeValue(inAttributeName.getLocalName()); 513 } 514 515 //--------------------------------------------------------------------------- 516 @Override 517 public String getAttributeValue(String inAttributeName) 518 { 519 XMLAttribute attribute = getAttribute(inAttributeName); 520 521 String value = null; 522 523 if (attribute != null) 524 { 525 value = attribute.getUnscapedValue(); 526 } 527 528 return value; 529 } 530 531 //--------------------------------------------------------------------------- 532 /** 533 Returns a Collection of XMLAttribute objects. 534 */ 535 @Override 536 public Collection<XMLAttribute> getAttributes() 537 { 538 Collection<XMLAttribute> attributes = null; 539 540 if (mAttributes != null) 541 { 542 attributes = mAttributes.values(); 543 } 544 545 return attributes; 546 } 547 548 //--------------------------------------------------------------------------- 549 @Override 550 public XMLAttribute removeAttribute(String inAttributeName) 551 { 552 XMLAttribute attribute = null; 553 554 if (mAttributes != null) 555 { 556 attribute = mAttributes.remove(inAttributeName); 557 } 558 559 return attribute; 560 } 561 562 //--------------------------------------------------------------------------- 563 @Override 564 public XMLAttribute removeAttribute(XMLName inAttributeName) 565 { 566 return removeAttribute(inAttributeName.getLocalName()); 567 } 568 569 570 571 //--------------------------------------------------------------------------- 572 @Override 573 public XMLNamespace getNamespace() 574 { 575 return mNamespace; 576 } 577 578 //--------------------------------------------------------------------------- 579 @Override 580 public XMLTag setNamespace(XMLNamespace inValue) 581 { 582 mNamespace = inValue; 583 584 return this; 585 } 586 587 //--------------------------------------------------------------------------- 588 public XMLTag setDefaultXMLNamespaceDeclaration(XMLNamespace inValue) 589 { 590 return setAttribute("xmlns", inValue.getURI()); 591 } 592 593 //--------------------------------------------------------------------------- 594 public XMLTag addXMLNamespaceDeclaration(XMLNamespace inValue) 595 { 596 return setAttribute("xmlns" + (StringUtil.isSet(inValue.getPrefix()) ? ":" + inValue.getPrefix() : ""), inValue.getURI()); 597 } 598 599 //--------------------------------------------------------------------------- 600 /** 601 Returns a List of all nodes with the specified attribute name and specified attribute value. 602 */ 603 @Override 604 public List<? extends XMLNode> findNodesByAttributeValue(XMLName inAttribute, String inValue) 605 { 606 return findNodesByAttributeValue(inAttribute.getLocalName(), inValue); 607 } 608 609 //--------------------------------------------------------------------------- 610 /** 611 Returns a List of all nodes with the specified attribute name and specified attribute value. 612 */ 613 @Override 614 public List<? extends XMLNode> findNodesByAttributeValue(String inAttribute, String inValue) 615 { 616 List<XMLNode> outList = new ArrayList<>(); 617 618 recursiveFindNodesByAttributeValue(inAttribute, inValue, outList); 619 620 return outList; 621 } 622 623 //--------------------------------------------------------------------------- 624 @Override 625 public String toString() 626 { 627 return "<" + mName + ">"; 628 } 629 630 //--------------------------------------------------------------------------- 631 @Override 632 public String toXML() 633 { 634 ByteArrayOutputStream outStream = null; 635 try 636 { 637 outStream = new ByteArrayOutputStream(2048); 638 toXML(outStream); 639 outStream.close(); 640 } 641 catch (Exception e) 642 { 643 throw new XMLException(e); 644 } 645 646 return outStream.toString(); 647 } 648 649 //--------------------------------------------------------------------------- 650 @Override 651 public void toXML(OutputStream inStream) 652 { 653 PrintWriter writer = new PrintWriter(inStream); 654 655 toXML(writer); 656 657 writer.flush(); 658 } 659 660 //--------------------------------------------------------------------------- 661 @Override 662 public synchronized void toXML(Writer inWriter) 663 { 664 toXML(inWriter, null); 665 } 666 667 //--------------------------------------------------------------------------- 668 protected void toXML(Writer inWriter, XMLNamespaceSet inDeclaredNamespaces) 669 { 670 try 671 { 672 XMLNamespaceSet declaredNamespaces = updateDeclaredNamespaces(inDeclaredNamespaces); 673 674 String tagName = getTagName(); 675 if (tagName != null) 676 { 677 String nsPrefix = null; 678 boolean addedNamespaceAttr = false; 679 if (mNamespace != null) 680 { 681 // Namespace already declared? 682 if (declaredNamespaces != null 683 && declaredNamespaces.contains(mNamespace) 684 && !mNamespace.equals(declaredNamespaces.getDefault())) 685 { 686 nsPrefix = mNamespace.getPrefix(); 687 } 688 else if (null == declaredNamespaces 689 || ! declaredNamespaces.contains(mNamespace)) 690 { 691 // Need to declare the namespace 692 setDefaultXMLNamespaceDeclaration(mNamespace); 693 addedNamespaceAttr = true; 694 695 if (null == declaredNamespaces) 696 { 697 declaredNamespaces = new XMLNamespaceSet(4); 698 } 699 700 declaredNamespaces.setDefault(mNamespace); 701 } 702 } 703 704 if (nsPrefix != null) 705 { 706 tagName = nsPrefix + ":" + tagName; 707 } 708 709 writeStartTag(inWriter, declaredNamespaces, tagName); 710 711 if (addedNamespaceAttr) 712 { 713 // We shouldn't permanently alter the tag. 714 removeAttribute("xmlns"); 715 } 716 } 717 718 if (mContentAndSubtagList != null) 719 { 720 for (Object content : mContentAndSubtagList) 721 { 722 if (content instanceof String) 723 { 724 inWriter.write(content.toString()); 725 } 726 else if (content instanceof byte[]) 727 { 728 inWriter.write(GZIP.uncompressToString((byte[]) content)); 729 } 730 else 731 { 732 XMLizable subtag = (XMLizable) content; 733 if (subtag instanceof XMLTag) 734 { 735 ((XMLTag)subtag).toXML(inWriter, declaredNamespaces); 736 } 737 else 738 { 739 subtag.toXML(inWriter); 740 } 741 } 742 } 743 } 744 745 if (tagName != null) 746 { 747 // Closing tag? 748 if (!isEmptyTag()) 749 { 750 inWriter.write(XMLUtil.composeEndTag(tagName)); 751 } 752 } 753 } 754 catch (IOException e) 755 { 756 throw new RuntimeException(e); 757 } 758 } 759 760 //--------------------------------------------------------------------------- 761 @Override 762 public String toIndentedXML(int inInitialIndentLevel, int inIndentSize) 763 { 764 ByteArrayOutputStream outStream; 765 try 766 { 767 outStream = new ByteArrayOutputStream(); 768 PrintWriter writer = new PrintWriter(outStream); 769 toIndentedXML(writer, inInitialIndentLevel, inIndentSize); 770 writer.close(); 771 } 772 catch (Exception e) 773 { 774 throw new XMLException(e); 775 } 776 777 return outStream.toString(); 778 } 779 780 //--------------------------------------------------------------------------- 781 /** 782 Writes out the tag (and any subtags) to te specified OutputStream. Note that 783 the OutputStream is not closed by this method. 784 * @param inOutputStream OutputStream to which the XML is written. 785 */ 786 @Override 787 public void toIndentedXML(OutputStream inOutputStream, int inInitialIndentLevel, int inIndentSize) 788 { 789 PrintWriter writer = null; 790 try 791 { 792 writer = new NoClosePrintWriter(inOutputStream); 793 toIndentedXML(writer, inInitialIndentLevel, inIndentSize); 794 } 795 finally 796 { 797 if (writer != null) writer.close(); 798 } 799 } 800 801 //-------------------------------------------------------------------------- 802 @Override 803 public synchronized void toIndentedXML(Writer inWriter, int inInitialIndentLevel, int inIndentSize) 804 { 805 toIndentedXML(inWriter, inInitialIndentLevel, inIndentSize, null); 806 } 807 808 //-------------------------------------------------------------------------- 809 @Override 810 public synchronized void toIndentedXML(Writer inWriter, int inInitialIndentLevel, int inIndentSize, 811 XMLNamespaceSet inDeclaredNamespaces) 812 { 813 try 814 { 815 boolean emptyTag = isEmptyTag(); 816 boolean contentPresent = false; 817 String indent = StringUtil.polyChar(' ', inInitialIndentLevel * inIndentSize); 818 819 if (inInitialIndentLevel > 0) 820 { 821 inWriter.write(NL); 822 inWriter.write(indent); 823 } 824 825 XMLNamespaceSet declaredNamespaces = updateDeclaredNamespaces(inDeclaredNamespaces); 826 827 String tagName = getTagName(); 828 if (tagName != null) 829 { 830 String nsPrefix = null; 831 boolean addedNamespaceAttr = false; 832 if (mNamespace != null) 833 { 834 // Namespace already declared? 835 if (declaredNamespaces != null 836 && ! mNamespace.equals(declaredNamespaces.getDefault())) 837 { 838 nsPrefix = mNamespace.getPrefix(); 839 } 840 else if (null == declaredNamespaces 841 || ! declaredNamespaces.contains(mNamespace)) 842 { 843 // Need to declare the namespace 844 setDefaultXMLNamespaceDeclaration(mNamespace); 845 addedNamespaceAttr = true; 846 847 if (null == declaredNamespaces) 848 { 849 declaredNamespaces = new XMLNamespaceSet(4); 850 } 851 852 declaredNamespaces.setDefault(mNamespace); 853 } 854 } 855 856 if (nsPrefix != null) 857 { 858 tagName = nsPrefix + ":" + tagName; 859 } 860 861 writeStartTag(inWriter, declaredNamespaces, tagName); 862 863 if (addedNamespaceAttr) 864 { 865 // We shouldn't permanently alter the tag. 866 removeAttribute("xmlns"); 867 } 868 } 869 870 if (mContentAndSubtagList != null) 871 { 872 for (int i = 0; i < mContentAndSubtagList.size(); i++) 873 { 874 Object content = mContentAndSubtagList.get(i); 875 876 if (content instanceof XMLNode) 877 { 878 ((XMLNode) content).toIndentedXML(inWriter, (contentPresent ? 0 : inInitialIndentLevel + 1), inIndentSize, 879 declaredNamespaces); 880 } 881 else if (content instanceof String) 882 { 883 inWriter.write(content.toString()); 884 contentPresent = true; 885 } 886 else if (content instanceof byte[]) 887 { 888 byte[] bytes = (byte[]) content; 889 String contentValue = GZIP.uncompressToString(bytes); 890 contentPresent = true; 891 892 inWriter.write(contentValue); 893 } 894 else if (content instanceof XMLComment) 895 { 896 if (!contentPresent) 897 { 898 inWriter.write(NL); 899 inWriter.write(StringUtil.polyChar(' ', (inInitialIndentLevel + 1) * inIndentSize)); 900 } 901 inWriter.write(((XMLComment) content).toXML()); 902// if (!contentPresent) 903// { 904// inWriter.println(); 905// } 906 } 907 else if (content instanceof XMLizable) 908 { 909 inWriter.write(((XMLizable) content).toXML()); 910 } 911 } 912 } 913 914 if (tagName != null) 915 { 916 if (!emptyTag) 917 { 918 if (!contentPresent) 919 { 920 inWriter.write(NL); 921 inWriter.write(indent); 922 } 923 inWriter.write(XMLUtil.composeEndTag(tagName)); 924 } 925 } 926 927 if (inInitialIndentLevel == 0) inWriter.flush(); 928 } 929 catch (IOException e) 930 { 931 throw new RuntimeException(e); 932 } 933 } 934 935 //-------------------------------------------------------------------------- 936 @Override 937 public void replaceCharacterEntities() 938 { 939 // Check the attribute values 940 if (mAttributes != null 941 && mAttributes.size() > 0) 942 { 943 for (XMLAttribute attr : mAttributes.values()) 944 { 945 String newValue = XMLUtil.convertCharacterEntitiesToNumeric(attr.getValue()); 946 if (! newValue.equals(attr.getValue())) 947 { 948 attr.setValue(newValue); 949 } 950 } 951 } 952 953 if (mContentAndSubtagList != null) 954 { 955 for (int i = 0; i < mContentAndSubtagList.size(); i++) 956 { 957 Object content = mContentAndSubtagList.get(i); 958 959 if (content instanceof XMLNode) 960 { 961 ((XMLNode)content).replaceCharacterEntities(); 962 } 963 else if (content instanceof String) 964 { 965 String newValue = XMLUtil.convertCharacterEntitiesToNumeric((String)content); 966 if (! newValue.equals(content)) 967 { 968 mContentAndSubtagList.set(i, newValue); 969 } 970 } 971 else if (content instanceof byte[]) 972 { 973 byte[] bytes = (byte[]) content; 974 String contentValue = GZIP.uncompressToString(bytes); 975 976 String newValue = XMLUtil.convertCharacterEntitiesToNumeric(contentValue); 977 if (! newValue.equals(contentValue)) 978 { 979 mContentAndSubtagList.set(i, GZIP.compress(newValue)); 980 } 981 } 982 } 983 } 984 } 985 986 //########################################################################## 987 // PROTECTED METHODS 988 //########################################################################## 989 990 //--------------------------------------------------------------------------- 991 protected void sortAttributes(List<XMLAttribute> inAttributes) 992 { 993 if (inAttributes != null) 994 { 995 Collections.sort(inAttributes); 996 } 997 } 998 999 //########################################################################## 1000 // PRIVATE METHODS 1001 //########################################################################## 1002 1003 //---------------------------------------------------------------------- 1004 private void construct(InputStream inXML) 1005 throws XMLException, IOException 1006 { 1007 construct(new InputStreamReader(inXML)); 1008 } 1009 1010 //---------------------------------------------------------------------- 1011 private void construct(Reader inXML) 1012 throws XMLException, IOException 1013 { 1014 XMLTag rootTag = createFromInputSource(new InputSource(inXML)); 1015 setTagName(rootTag.getTagName()); 1016 setAttributes(rootTag.getAttributes()); 1017 mNamespace = rootTag.mNamespace; 1018 mContentAndSubtagList = rootTag.mContentAndSubtagList; 1019 } 1020 1021 //---------------------------------------------------------------------- 1022 private static synchronized XMLTag createFromInputSource(InputSource inXML) 1023 throws XMLException, IOException 1024 { 1025 XMLTagReader tagReader = new XMLTagReader(); 1026 tagReader.parse(inXML); 1027 return tagReader.getRootNode(); 1028 1029/* try 1030 { 1031 XMLReader parser = getParser(); 1032 parser.setContentHandler(getDocHandler()); 1033 parser.setProperty("http://xml.org/sax/properties/lexical-handler", getDocHandler()); 1034 parser.parse(inXML); 1035 } 1036 catch (Exception e) 1037 { 1038 throw new XMLException(e); 1039 } 1040 1041 return getDocHandler().getRootTag(); 1042*/ 1043 } 1044 1045 1046 //--------------------------------------------------------------------------- 1047 private void recursiveFindNodesByAttributeValue(String inAttribute, String inValue, List<XMLNode> inList) 1048 { 1049 if (inAttribute != null) 1050 { 1051 if (hasAttribute(inAttribute) 1052 && (null == inValue 1053 || getAttributeValue(inAttribute).equals(inValue))) 1054 { 1055 inList.add(this); 1056 } 1057 } 1058 else 1059 { 1060 for (XMLAttribute attr : getAttributes()) 1061 { 1062 if (null == inValue 1063 || attr.getValue().equals(inValue)) 1064 { 1065 inList.add(this); 1066 } 1067 } 1068 } 1069 1070 if (mContentAndSubtagList != null) 1071 { 1072 for (Object content : mContentAndSubtagList) 1073 { 1074 if (content instanceof XMLTag) 1075 { 1076 ((XMLTag)content).recursiveFindNodesByAttributeValue(inAttribute, inValue, inList); 1077 } 1078 } 1079 } 1080 } 1081 1082 //--------------------------------------------------------------------------- 1083 /** 1084 Composes an xml start tag (ex: "<inName att1='value1' att2='value2'>"). 1085 If the isEmptyTag parameter is true, the output tag will end with "/>". 1086 ex: "<inName att1='value1' att2='value2'/>". Attributes appear in 1087 alphabetical order for consistency. 1088 */ 1089 private void writeStartTag(Writer inWriter, XMLNamespaceSet inDeclaredNamespaces, String inTagName) 1090 throws IOException 1091 { 1092 inWriter.write("<"); 1093 inWriter.write(inTagName); 1094 1095 1096 // Write attributes. 1097 if (mAttributes != null 1098 && mAttributes.size() > 0) 1099 { 1100 List<XMLAttribute> sordidAttributes = new ArrayList<>(mAttributes.values()); 1101 if (mSortAttributesBeforeWriting) 1102 { 1103 sortAttributes(sordidAttributes); 1104 } 1105 1106 for (XMLAttribute attribute : sordidAttributes) 1107 { 1108 inWriter.write(" "); 1109 1110 XMLNamespace namespace = attribute.getNamespace(); 1111 if (namespace != null 1112 && namespace.getPrefix() != null 1113 && (null == inDeclaredNamespaces 1114 || null == inDeclaredNamespaces.getDefault() 1115 || ! namespace.equals(inDeclaredNamespaces.getDefault()))) 1116 { 1117 inWriter.write(namespace.getPrefix()); 1118 inWriter.write(":"); 1119 } 1120 inWriter.write(attribute.getName()); 1121 1122 String value = attribute.getValue(); 1123 if (null == value) value = ""; 1124 inWriter.write("="); 1125 1126 String safeValue = "''"; 1127 if (value != null) 1128 { 1129 if (sQuoteChar == '\'') 1130 { 1131 safeValue = "'" + XMLUtil.escapeAttributeValue(value) + "'"; 1132 } 1133 else 1134 { 1135 safeValue = "\"" + XMLUtil.escapeDoubleQuotedAttributeValue(value) + "\""; 1136 } 1137 } 1138 inWriter.write(safeValue); 1139 } 1140 } 1141 1142 if (isEmptyTag()) inWriter.write(" /"); 1143 inWriter.write(">"); 1144 } 1145 1146 //--------------------------------------------------------------------------- 1147 private XMLNamespaceSet updateDeclaredNamespaces(XMLNamespaceSet inDeclaredNamespaces) 1148 { 1149 XMLNamespaceSet expandedNamespaceSet = inDeclaredNamespaces; 1150 if (getAttributes() != null) 1151 { 1152 boolean cloned = false; 1153 for (XMLAttribute attr : getAttributes()) 1154 { 1155 String qualifiedAttrName = attr.getQualifiedName(); 1156 if (qualifiedAttrName.startsWith("xmlns")) 1157 { 1158 if (! cloned) 1159 { 1160 expandedNamespaceSet = new XMLNamespaceSet((inDeclaredNamespaces != null ? inDeclaredNamespaces.size() : 0) + 5); 1161 if (CollectionUtil.hasValues(inDeclaredNamespaces)) 1162 { 1163 expandedNamespaceSet.addAll(inDeclaredNamespaces); 1164 } 1165 cloned = true; 1166 } 1167 1168 if (qualifiedAttrName.equals("xmlns")) 1169 { 1170 expandedNamespaceSet.setDefault(XMLNamespace.getNamespace(attr.getValue()).clone()); 1171 } 1172 else if (qualifiedAttrName.startsWith("xmlns:")) 1173 { 1174 expandedNamespaceSet.add(XMLNamespace.getNamespace(qualifiedAttrName.substring(6), attr.getValue()).clone()); 1175 if (inDeclaredNamespaces != null) 1176 { 1177 // Propagate the previous default 1178 expandedNamespaceSet.setDefault(inDeclaredNamespaces.getDefault()); 1179 } 1180 } 1181 } 1182 } 1183 } 1184 1185 return expandedNamespaceSet; 1186 } 1187 1188 //########################################################################### 1189 // INNER CLASS 1190 //########################################################################### 1191 1192 private class ProducerThread extends Thread 1193 { 1194 OutputStream mOutputStream; 1195 1196 //---------------------------------------------------------------------- 1197 public ProducerThread(OutputStream inOutputStream) 1198 { 1199 mOutputStream = inOutputStream; 1200 } 1201 1202 //---------------------------------------------------------------------- 1203 public void run() 1204 { 1205 toXML(mOutputStream); 1206 try 1207 { 1208 mOutputStream.close(); 1209 } 1210 catch (IOException e) 1211 { 1212 e.printStackTrace(); 1213 } 1214 } 1215 1216 1217 } 1218 1219}