001package com.hfg.xml; 002 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.List; 007import java.util.regex.Pattern; 008 009import com.hfg.exception.ProgrammingException; 010import com.hfg.util.Case; 011import com.hfg.util.CompareUtil; 012import com.hfg.util.Recursion; 013import com.hfg.util.StringBuilderPlus; 014import com.hfg.util.collection.CollectionUtil; 015import com.hfg.util.io.GZIP; 016 017//------------------------------------------------------------------------------ 018/** 019 XML object that can contain sub-objects but not a name or attributes. 020 021 @author J. Alex Taylor, hairyfatguy.com 022 */ 023//------------------------------------------------------------------------------ 024// com.hfg XML/HTML Coding Library 025// 026// This library is free software; you can redistribute it and/or 027// modify it under the terms of the GNU Lesser General Public 028// License as published by the Free Software Foundation; either 029// version 2.1 of the License, or (at your option) any later version. 030// 031// This library is distributed in the hope that it will be useful, 032// but WITHOUT ANY WARRANTY; without even the implied warranty of 033// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 034// Lesser General Public License for more details. 035// 036// You should have received a copy of the GNU Lesser General Public 037// License along with this library; if not, write to the Free Software 038// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 039// 040// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 041// jataylor@hairyfatguy.com 042//------------------------------------------------------------------------------ 043 044public abstract class XMLContainerImpl implements XMLContainer 045{ 046 protected List mContentAndSubtagList; 047 private XMLContainer mParent; 048 049 private static final boolean ESCAPE = true; 050 private static final boolean NO_ESCAPE = false; 051 private static final boolean WITH_EMBEDDED_SUBTAGS = true; 052 private static final boolean WITHOUT_EMBEDDED_SUBTAGS = false; 053 054 protected static enum sMode { FIND, REMOVE }; 055 private static int sCompressionThreshold = 1024; 056 057 //--------------------------------------------------------------------------- 058 @Override 059 public XMLTag clone() 060 { 061 XMLTag cloneObj; 062 try 063 { 064 cloneObj = (XMLTag) super.clone(); 065 } 066 catch (CloneNotSupportedException e) 067 { 068 throw new ProgrammingException(e); 069 } 070 071 if (mContentAndSubtagList != null) 072 { 073 cloneObj.mContentAndSubtagList = new ArrayList(mContentAndSubtagList.size()); 074 for (Object content : mContentAndSubtagList) 075 { 076 if (content instanceof XMLizable) 077 { 078 XMLizable subtagClone = ((XMLizable)content).clone(); 079 cloneObj.mContentAndSubtagList.add(subtagClone); 080 if (subtagClone instanceof XMLContainer) 081 { 082 ((XMLContainer)subtagClone).setParentNode(cloneObj); 083 } 084 } 085 else 086 { 087 cloneObj.mContentAndSubtagList.add(content); 088 } 089 } 090 } 091 092 return cloneObj; 093 } 094 095 //--------------------------------------------------------------------------- 096 public boolean hasContent() 097 { 098 boolean returnValue = false; 099 100 if (mContentAndSubtagList != null) 101 { 102 for (Object content : mContentAndSubtagList) 103 { 104 if (content instanceof String 105 || content instanceof byte[]) // Compressed content is stored as a byte[] 106 { 107 returnValue = true; 108 break; 109 } 110 } 111 } 112 113 return returnValue; 114 } 115 116 //--------------------------------------------------------------------------- 117 public boolean hasContentOrSubtags() 118 { 119 return (CollectionUtil.hasValues(mContentAndSubtagList)); 120 } 121 122 //--------------------------------------------------------------------------- 123 public List getContentPlusSubtagList() 124 { 125 return mContentAndSubtagList; 126 } 127 128 129 //--------------------------------------------------------------------------- 130 public synchronized XMLContainer setContent(CharSequence inContent) 131 { 132 clearContent(); 133 134 addContent(inContent); 135 136 return this; 137 } 138 139 //--------------------------------------------------------------------------- 140 public synchronized XMLContainer setContent(int inContent) 141 { 142 return setContent(inContent + ""); 143 } 144 145 146 //--------------------------------------------------------------------------- 147 // Clears content (not subtags). 148 public synchronized void clearContent() 149 { 150 if (mContentAndSubtagList != null) 151 { 152 for (int i = 0; i < mContentAndSubtagList.size(); i++) 153 { 154 Object content = mContentAndSubtagList.get(i); 155 156 // Ignore subtags and remove compressed or uncompressed content. 157 if (content instanceof String 158 || content instanceof byte[]) 159 { 160 mContentAndSubtagList.remove(i--); 161 } 162 } 163 } 164 } 165 166 //--------------------------------------------------------------------------- 167 public XMLContainer addContent(CharSequence inContent) 168 { 169 addContent(inContent, ESCAPE); 170 171 return this; 172 } 173 174 //--------------------------------------------------------------------------- 175 public XMLContainer addContentWithoutEscaping(CharSequence inContent) 176 { 177 addContent(inContent, NO_ESCAPE); 178 179 return this; 180 } 181 182 //--------------------------------------------------------------------------- 183 public String getContent() 184 { 185 return getContent(WITHOUT_EMBEDDED_SUBTAGS); 186 } 187 188 //--------------------------------------------------------------------------- 189 /** 190 The returned content also contains any embedded subtags. 191 */ 192 private String getContentWithEmbeddedSubtags() 193 { 194 return getContent(WITH_EMBEDDED_SUBTAGS); 195 } 196 197 //---------------------------------------------------------------------------- 198 public String getUnescapedContent() 199 { 200 return XMLUtil.unescapeContent(getContent()); 201 } 202 203 //--------------------------------------------------------------------------- 204 public synchronized void addSubtag(XMLizable inSubtag) 205 { 206 if (null == inSubtag) 207 { 208 throw new RuntimeException("The added xml subTag cannot be null"); 209 } 210 211 if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2); 212 213 mContentAndSubtagList.add(inSubtag); 214 215 if (inSubtag instanceof XMLContainer) 216 { 217 ((XMLContainer)inSubtag).setParentNode(this); 218 } 219 } 220 221 //--------------------------------------------------------------------------- 222 /** 223 Added the specified subtag at the specified position in the list of this tag's 224 children. 225 @param inIndex the index at which the specified subtag should be added 226 @param inSubtag the subtag which should be added to this tag 227 */ 228 public synchronized void addSubtag(int inIndex, XMLizable inSubtag) 229 { 230 if (null == inSubtag) 231 { 232 throw new RuntimeException("The added xml subTag cannot be null"); 233 } 234 235 if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2); 236 237 if (inIndex > mContentAndSubtagList.size()) // Prevent trying to insert past the end 238 { 239 inIndex = mContentAndSubtagList.size(); 240 } 241 242 mContentAndSubtagList.add(inIndex, inSubtag); 243 244 if (inSubtag instanceof XMLContainer) 245 { 246 ((XMLContainer)inSubtag).setParentNode(this); 247 } 248 } 249 250 //--------------------------------------------------------------------------- 251 public synchronized void addSubtags(Collection<? extends XMLizable> inSubtags) 252 { 253 if (null == inSubtags) 254 { 255 throw new RuntimeException("The added Collection of xml subTags cannot be null"); 256 } 257 258 if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(inSubtags.size()); 259 260 for (XMLizable subtag : inSubtags) 261 { 262 addSubtag(subtag); 263 } 264 } 265 266 //--------------------------------------------------------------------------- 267 public synchronized <T extends XMLizable> void setSubtags(List<T> inSubtags) 268 { 269 clearSubtags(); 270 if (CollectionUtil.hasValues(inSubtags)) 271 { 272 addSubtags(inSubtags); 273 } 274 } 275 276 //--------------------------------------------------------------------------- 277 public <T extends XMLizable> List<T> getSubtags() 278 { 279 List<T> subtagList = new ArrayList<T>(5); 280 281 if (mContentAndSubtagList != null) 282 { 283 for (Object content : mContentAndSubtagList) 284 { 285 if (content instanceof XMLizable) 286 { 287 subtagList.add((T)content); 288 } 289 } 290 } 291 292 return subtagList; 293 } 294 295 //--------------------------------------------------------------------------- 296 public <T extends XMLNode> List<T> getXMLNodeSubtags() 297 { 298 List<T> subtagList = new ArrayList<>(5); 299 300 if (mContentAndSubtagList != null) 301 { 302 for (Object content : mContentAndSubtagList) 303 { 304 if (content instanceof XMLNode) 305 { 306 subtagList.add((T)content); 307 } 308 } 309 } 310 311 return subtagList; 312 } 313 314 //--------------------------------------------------------------------------- 315 /** 316 Returns the subtag with the specified attribute name and value. 317 @param inAttributeName the attribute name to match on. Allows null. 318 @param inAttributeValue the attribute value to match on 319 @return the subtag which matched the attribute criteria 320 @throws RuntimeException if multiple subtags match the specified attribute criteria 321 */ 322 public <T extends XMLNode> T getSubtagByAttribute(XMLName inAttributeName, String inAttributeValue) 323 { 324 return getSubtagByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue); 325 } 326 327 //--------------------------------------------------------------------------- 328 /** 329 Returns the subtag with the specified attribute name and value. 330 @param inAttributeName the attribute name to match on. Allows null. 331 @param inAttributeValue the attribute value to match on 332 @return the subtag which matched the attribute criteria 333 @throws RuntimeException if multiple subtags match the specified attribute criteria 334 */ 335 public <T extends XMLNode> T getSubtagByAttribute(String inAttributeName, String inAttributeValue) 336 { 337 T requestedSubtag = null; 338 339 List<T> subtags = getSubtagsByAttribute(inAttributeName, inAttributeValue); 340 if (CollectionUtil.hasValues(subtags)) 341 { 342 if (subtags.size() > 1) 343 { 344 throw new RuntimeException(subtags.size() + " subtags matching the requested attribute [" 345 + inAttributeName + "=" + inAttributeValue + "] instead of the required 1!"); 346 } 347 else 348 { 349 requestedSubtag = subtags.get(0); 350 } 351 } 352 353 return requestedSubtag; 354 } 355 356 //--------------------------------------------------------------------------- 357 /** 358 Returns a List of all subtags with the specified attribute name and value. 359 @param inAttributeName the attribute name to match on. Allows null. 360 @param inAttributeValue the attribute value to match on 361 @return the list of all subtags which matched the attribute criteria 362 */ 363 public <T extends XMLNode> List<T> getSubtagsByAttribute(XMLName inAttributeName, String inAttributeValue) 364 { 365 return getSubtagsByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue); 366 } 367 368 //--------------------------------------------------------------------------- 369 /** 370 Returns a List of all subtags with the specified attribute name and value. 371 @param inAttributeName the attribute name to match on. Allows null. 372 @param inAttributeValue the attribute value to match on 373 @return the list of all subtags which matched the attribute criteria 374 */ 375 public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, String inAttributeValue) 376 { 377 List<T> subtagList = null; 378 379 int index = inAttributeName.indexOf(":"); 380 if (index > 0) 381 { 382 subtagList = getSubtagsByAttribute(new XMLName(inAttributeName.substring(index + 1), XMLNamespace.getNamespaceViaPrefix(inAttributeName.substring(0, index))), inAttributeValue); 383 } 384 else 385 { 386 List<T> xmlNodes = getXMLNodeSubtags(); 387 if (CollectionUtil.hasValues(xmlNodes)) 388 { 389 subtagList = new ArrayList<>(5); 390 for (T node : xmlNodes) 391 { 392 if (inAttributeName != null) 393 { 394 395 String attributeValue = node.getAttributeValue(inAttributeName); 396 if (0 == CompareUtil.compare(attributeValue, inAttributeValue)) 397 { 398 subtagList.add(node); 399 } 400 } 401 else // Allow the attribute name to be null 402 { 403 for (XMLAttribute attr : node.getAttributes()) 404 { 405 if (0 == CompareUtil.compare(attr.getValue(), inAttributeValue)) 406 { 407 subtagList.add(node); 408 break; 409 } 410 } 411 } 412 } 413 } 414 } 415 416 return subtagList; 417 } 418 419 //--------------------------------------------------------------------------- 420 /** 421 Returns a List of all subtags with the specified attribute name and value. 422 @param inAttributeName the attribute name to match on. Allows null. 423 @param inAttributeValuePattern the Pattern to compare to attribute values via find(). Allows null. 424 @return the list of all subtags which matched the attribute criteria 425 */ 426 public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern) 427 { 428 List<T> subtagList = null; 429 430 List<T> xmlNodes = getXMLNodeSubtags(); 431 if (CollectionUtil.hasValues(xmlNodes)) 432 { 433 subtagList = new ArrayList<>(5); 434 for (T node : xmlNodes) 435 { 436 if (inAttributeName != null) 437 { 438 if (node.hasAttribute(inAttributeName)) 439 { 440 String attributeValue = node.getAttributeValue(inAttributeName); 441 if (null == inAttributeValuePattern 442 || (attributeValue != null 443 && inAttributeValuePattern.matcher(attributeValue).find())) 444 { 445 subtagList.add(node); 446 } 447 } 448 } 449 else // Allow the attribute name to be null 450 { 451 for (XMLAttribute attr : node.getAttributes()) 452 { 453 if (null == inAttributeValuePattern 454 || inAttributeValuePattern.matcher(attr.getValue()).find()) 455 { 456 subtagList.add(node); 457 break; 458 } 459 } 460 } 461 } 462 } 463 464 return subtagList; 465 } 466 467 //--------------------------------------------------------------------------- 468 /** 469 Returns a List of all subtags with the specified attribute name and value 470 (if recursion is off) or at any level below this object (if recursion is on). 471 @param inAttributeName the attribute name to match on. Allows null. 472 @param inAttributeValue the attribute value to match on 473 @param inRecursion flag to indicate whether or not recursion should be used 474 @return the list of all subtags which matched the attribute criteria 475 */ 476 public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, String inAttributeValue, Recursion inRecursion) 477 { 478 List<T> outList = new ArrayList<T>(); 479 480 if (inRecursion == Recursion.ON) 481 { 482 Pattern attributeValuePattern = null; 483 if (inAttributeValue != null) 484 { 485 attributeValuePattern = Pattern.compile("^" + Pattern.quote(inAttributeValue) + "$"); 486 } 487 488 recursivelyGetSubtagsByAttribute(inAttributeName, attributeValuePattern, this, outList, sMode.FIND); // Treat the attribute value as a literal pattern 489 } 490 else 491 { 492 outList = getSubtagsByAttribute(inAttributeName, inAttributeValue); 493 for (XMLNode subtag : outList) 494 { 495 removeSubtag(subtag); 496 } 497 } 498 499 return outList; 500 } 501 502 //--------------------------------------------------------------------------- 503 /** 504 Returns a List of all subtags with the specified attribute name and value. 505 @param inAttributeName the attribute name to match on. Allows null. 506 @param inAttributeValuePattern the Pattern to compare to attribute values via find(). Allows null. 507 @param inRecursion flag to indicate whether or not recursion should be used 508 @return the list of all subtags which matched the attribute criteria 509 */ 510 public <T extends XMLNode> List<T> getSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern, Recursion inRecursion) 511 { 512 List<T> outList = new ArrayList<T>(); 513 514 if (inRecursion == Recursion.ON) 515 { 516 recursivelyGetSubtagsByAttribute(inAttributeName, inAttributeValuePattern, this, outList, sMode.FIND); 517 } 518 else 519 { 520 outList = getSubtagsByAttribute(inAttributeName, inAttributeValuePattern); 521 } 522 523 return outList; 524 } 525 526 527 //--------------------------------------------------------------------------- 528 /** 529 Returns a List of all subtags of the specified Java Class on this XMLTag object 530 The same as getSubtagsByClass(inClassName, Recursion.OFF) 531 */ 532 public <T> List<T> getSubtagsByClass(Class<T> inClassType) 533 { 534 return getSubtagsByClass(inClassType, Recursion.OFF); 535 } 536 537 //--------------------------------------------------------------------------- 538 /** 539 Returns a List of all subtags of the specified Java Class on this XMLTag object 540 (if recursion is off) or at any level below this object (if recursion is on). 541 */ 542 public <T> List<T> getSubtagsByClass(Class<T> inClassType, Recursion inRecursion) 543 { 544 List<T> outList = new ArrayList<T>(); 545 546 if (inClassType != null) 547 { 548 if (inRecursion == Recursion.ON) 549 { 550 recursivelyGetSubtagsByClass(inClassType, this, outList, sMode.FIND); 551 } 552 else 553 { 554 if (mContentAndSubtagList != null) 555 { 556 for (Object content : mContentAndSubtagList) 557 { 558 if (inClassType.isInstance(content)) 559 { 560 outList.add((T)content); 561 } 562 } 563 } 564 } 565 } 566 567 return outList; 568 } 569 570 571 //--------------------------------------------------------------------------- 572 public synchronized void removeSubtag(XMLizable inSubtag) 573 { 574 if (mContentAndSubtagList != null) 575 { 576 // Remove all instances of the subtag in the list. 577 while (mContentAndSubtagList.remove(inSubtag)) 578 { 579 } 580 } 581 } 582 583 //--------------------------------------------------------------------------- 584 /** 585 Removes subtags of the specified Java Class on this object 586 The same as removeSubtagsByClass(inClassName, Recursion.OFF) 587 @return the List of removed objects 588 */ 589 public <T extends XMLContainer> List<T> removeSubtagsByClass(Class<T> inClassType) 590 { 591 return removeSubtagsByClass(inClassType, Recursion.OFF); 592 } 593 594 //--------------------------------------------------------------------------- 595 /** 596 Removes subtags of the specified Java Class on this XMLTag object 597 (if recursion is off) or at any level below this object (if recursion is on). 598 @return the List of removed objects 599 */ 600 public <T extends XMLContainer> List<T> removeSubtagsByClass(Class<T> inClassType, Recursion inRecursion) 601 { 602 List<T> outList = new ArrayList<T>(); 603 604 if (inClassType != null) 605 { 606 if (inRecursion == Recursion.ON) 607 { 608 recursivelyGetSubtagsByClass(inClassType, this, outList, sMode.REMOVE); 609 } 610 else 611 { 612 outList = getSubtagsByClass(inClassType); 613 for (T subtag : outList) 614 { 615 removeSubtag(subtag); 616 } 617 } 618 } 619 620 return outList; 621 } 622 623 //--------------------------------------------------------------------------- 624 public synchronized void clearSubtags() 625 { 626 if (mContentAndSubtagList != null) 627 { 628 for (int i = 0; i < mContentAndSubtagList.size(); i++) 629 { 630 Object content = mContentAndSubtagList.get(i); 631 632 try 633 { 634 XMLizable subtag = (XMLizable) content; 635 mContentAndSubtagList.remove(i--); 636 } 637 catch (ClassCastException e) 638 { 639 // Ignore. Content or Comment. 640 } 641 } 642 } 643 } 644 645 //--------------------------------------------------------------------------- 646 public synchronized int indexOf(XMLizable inSubtag) 647 { 648 int index = -1; 649 if (mContentAndSubtagList != null) 650 { 651 for (int i = 0; i < mContentAndSubtagList.size(); i++) 652 { 653 Object content = mContentAndSubtagList.get(i); 654 if (content.equals(inSubtag)) 655 { 656 index = i; 657 break; 658 } 659 } 660 } 661 662 return index; 663 } 664 665 666 //--------------------------------------------------------------------------- 667 public int getTotalTagCount() 668 { 669 int count = 1; 670 671 if (mContentAndSubtagList != null) 672 { 673 for (Object content : mContentAndSubtagList) 674 { 675 try 676 { 677 XMLNode subtag = (XMLNode) content; 678 count += subtag.getTotalTagCount(); 679 } 680 catch (ClassCastException e) 681 { 682 // Ignore. Content or Comment. 683 } 684 } 685 } 686 687 return count; 688 } 689 690 //--------------------------------------------------------------------------- 691 public <T extends XMLNode> T getRequiredSubtagByName(XMLName inTagName) 692 { 693 return getRequiredSubtagByName(inTagName.getLocalName()); 694 } 695 696 //--------------------------------------------------------------------------- 697 public <T extends XMLNode> T getRequiredSubtagByName(String inTagName) 698 { 699 List<T> subtags = getSubtagsByName(inTagName); 700 if (!CollectionUtil.hasValues(subtags)) 701 { 702 throw new XMLException("The required " + inTagName + " subtag was not found!"); 703 } 704 else if (subtags.size() > 1) 705 { 706 throw new XMLException(subtags.size() + " " + inTagName + " subtags found!"); 707 } 708 709 return subtags.get(0); 710 } 711 712 //--------------------------------------------------------------------------- 713 public <T extends XMLNode> T getRequiredSubtagById(String inId) 714 { 715 List<T> subtags = getSubtagsByAttribute("id", inId); 716 if (! CollectionUtil.hasValues(subtags)) 717 { 718 throw new XMLException("Subtag with the required id '" + inId + "' subtag was not found!"); 719 } 720 else if (subtags.size() > 1) 721 { 722 throw new XMLException(subtags.size() + " " + "subtags found with id '" + inId + "'!"); 723 } 724 725 return subtags.get(0); 726 } 727 728 //--------------------------------------------------------------------------- 729 public <T extends XMLNode> T getRequiredSubtagById(String inId, Recursion inRecursion) 730 { 731 List<T> subtags = getSubtagsByAttribute("id", inId, inRecursion); 732 if (! CollectionUtil.hasValues(subtags)) 733 { 734 throw new XMLException("Subtag with the required id '" + inId + "' subtag was not found!"); 735 } 736 else if (subtags.size() > 1) 737 { 738 throw new XMLException(subtags.size() + " " + "subtags found with id '" + inId + "'!"); 739 } 740 741 return subtags.get(0); 742 } 743 744 //--------------------------------------------------------------------------- 745 public <T extends XMLNode> T getOptionalSubtagByName(XMLName inTagName) 746 { 747 return getOptionalSubtagByName(inTagName.getLocalName(), Case.SENSITIVE); 748 } 749 750 //--------------------------------------------------------------------------- 751 public <T extends XMLNode> T getOptionalSubtagByName(XMLName inTagName, Case inCaseSensitivity) 752 { 753 return getOptionalSubtagByName(inTagName.getLocalName(), inCaseSensitivity); 754 } 755 756 //--------------------------------------------------------------------------- 757 public <T extends XMLNode> T getOptionalSubtagByName(String inTagName) 758 { 759 return getOptionalSubtagByName(inTagName, Case.SENSITIVE); 760 } 761 762 //--------------------------------------------------------------------------- 763 public <T extends XMLNode> T getOptionalSubtagByName(String inTagName, Case inCaseSensitivity) 764 { 765 List<T> subtags = getSubtagsByName(inTagName, inCaseSensitivity, Recursion.OFF); 766 if (subtags.size() > 1) 767 { 768 throw new XMLException(subtags.size() + " " + inTagName + " subtags found!"); 769 } 770 771 return subtags.size() == 1 ? subtags.get(0) : null; 772 } 773 774 //--------------------------------------------------------------------------- 775 public <T extends XMLNode> List<T> getSubtagsByName(XMLName inTagName) 776 { 777 return getSubtagsByName(inTagName.getLocalName(), Recursion.OFF); 778 } 779 780 //--------------------------------------------------------------------------- 781 public <T extends XMLNode> List<T> getSubtagsByName(String inTagName) 782 { 783 return getSubtagsByName(inTagName, Recursion.OFF); 784 } 785 786 //--------------------------------------------------------------------------- 787 public <T extends XMLNode> List<T> getSubtagsByName(XMLName inTagName, Recursion inRecursion) 788 { 789 return getSubtagsByName(inTagName.getLocalName(), inRecursion); 790 } 791 792 //--------------------------------------------------------------------------- 793 public <T extends XMLNode> List<T> getSubtagsByName(String inTagName, Recursion inRecursion) 794 { 795 return getSubtagsByName(inTagName, Case.SENSITIVE, inRecursion); 796 } 797 798 //--------------------------------------------------------------------------- 799 public <T extends XMLNode> List<T> getSubtagsByName(String inTagName, Case inCaseSensitivity, Recursion inRecursion) 800 { 801 List<T> outList = new ArrayList<T>(); 802 803 if (inTagName != null) 804 { 805 if (inRecursion == Recursion.ON) 806 { 807 recursivelyGetSubtagsByName(inTagName, this, outList, sMode.FIND); 808 } 809 else 810 { 811 if (mContentAndSubtagList != null) 812 { 813 for (Object content : mContentAndSubtagList) 814 { 815 if (content instanceof XMLNode) 816 { 817 String tagName = ((XMLNode) content).getTagName(); 818 if (tagName != null 819 && (inCaseSensitivity.equals(Case.SENSITIVE) ? tagName.equals(inTagName) : 820 tagName.equalsIgnoreCase(inTagName))) 821 { 822 outList.add((T) content); 823 } 824 } 825 } 826 } 827 } 828 } 829 830 return outList; 831 } 832 833 //--------------------------------------------------------------------------- 834 public synchronized <T extends XMLNode> List<T> removeSubtagsByName(XMLName inTagName) 835 { 836 return removeSubtagsByName(inTagName.getLocalName()); 837 } 838 839 //--------------------------------------------------------------------------- 840 public synchronized <T extends XMLNode> List<T> removeSubtagsByName(String inTagName) 841 { 842 return removeSubtagsByName(inTagName, Recursion.OFF); 843 } 844 845 //--------------------------------------------------------------------------- 846 /** 847 Removes all subtags with the specified tag name from this XMLTag object 848 (if recursion is off) or at any level below this object (if recursion is on). 849 @param inTagName the name of the subtags to remove 850 @param inRecursion flag to indicate whether or not recursion should be used 851 @return the list of removed subtags 852 */ 853 public synchronized <T extends XMLNode> List<T> removeSubtagsByName(String inTagName, Recursion inRecursion) 854 { 855 List<T> outList = new ArrayList<T>(); 856 if (inTagName != null) 857 { 858 if (inRecursion == Recursion.ON) 859 { 860 recursivelyGetSubtagsByName(inTagName, this, outList, sMode.REMOVE); 861 } 862 else 863 { 864 outList = getSubtagsByName(inTagName); 865 for (XMLNode subtag : outList) 866 { 867 removeSubtag(subtag); 868 } 869 } 870 } 871 872 return outList; 873 } 874 875 //--------------------------------------------------------------------------- 876 /** 877 Removes all subtags with the specified tag name from this XMLTag object 878 (if recursion is off) or at any level below this object (if recursion is on). 879 @param inAttributeName the attribute name to match on. Allows null. 880 @param inAttributeValue the attribute value to match on 881 @param inRecursion flag to indicate whether or not recursion should be used 882 @return the list of removed subtags 883 */ 884 public synchronized <T extends XMLNode> List<T> removeSubtagsByAttribute(XMLName inAttributeName, String inAttributeValue, Recursion inRecursion) 885 { 886 return removeSubtagsByAttribute(inAttributeName != null ? inAttributeName.getLocalName() : null, inAttributeValue, inRecursion); 887 } 888 889 //--------------------------------------------------------------------------- 890 /** 891 Removes all subtags with the specified tag name from this XMLTag object 892 (if recursion is off) or at any level below this object (if recursion is on). 893 @param inAttributeName the attribute name to match on. Allows null. 894 @param inAttributeValue the attribute value to match on 895 @param inRecursion flag to indicate whether or not recursion should be used 896 @return the list of removed subtags 897 */ 898 public synchronized <T extends XMLNode> List<T> removeSubtagsByAttribute(String inAttributeName, String inAttributeValue, Recursion inRecursion) 899 { 900 List<T> outList = new ArrayList<T>(); 901 902 if (inRecursion == Recursion.ON) 903 { 904 Pattern attributeValuePattern = null; 905 if (inAttributeValue != null) 906 { 907 attributeValuePattern = Pattern.compile(Pattern.quote(inAttributeValue)); // Treat the attribute value as a literal pattern 908 } 909 910 recursivelyGetSubtagsByAttribute(inAttributeName, attributeValuePattern, this, outList, sMode.REMOVE); 911 } 912 else 913 { 914 outList = getSubtagsByAttribute(inAttributeName, inAttributeValue); 915 for (XMLNode subtag : outList) 916 { 917 removeSubtag(subtag); 918 } 919 } 920 921 return outList; 922 } 923 924 //--------------------------------------------------------------------------- 925 /** 926 Removes all subtags with the specified tag name from this XMLTag object 927 (if recursion is off) or at any level below this object (if recursion is on). 928 @param inAttributeName the attribute name to match on. Allows null. 929 @param inAttributeValuePattern the attribute value pattern to match with 930 @param inRecursion flag to indicate whether or not recursion should be used 931 @return the list of removed subtags 932 */ 933 public synchronized <T extends XMLNode> List<T> removeSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern, Recursion inRecursion) 934 { 935 List<T> outList = new ArrayList<T>(); 936 937 if (inRecursion == Recursion.ON) 938 { 939 recursivelyGetSubtagsByAttribute(inAttributeName, inAttributeValuePattern, this, outList, sMode.REMOVE); 940 } 941 else 942 { 943 outList = getSubtagsByAttribute(inAttributeName, inAttributeValuePattern); 944 for (XMLNode subtag : outList) 945 { 946 removeSubtag(subtag); 947 } 948 } 949 950 return outList; 951 } 952 953 954 //--------------------------------------------------------------------------- 955 public void setParentNode(XMLContainer inParent) 956 { 957 mParent = inParent; 958 } 959 960 //--------------------------------------------------------------------------- 961 public XMLContainer getParentNode() 962 { 963 return mParent; 964 } 965 966 //--------------------------------------------------------------------------- 967 public XMLContainer getPreviousSibling() 968 { 969 XMLContainer requestedSibling = null; 970 if (getParentNode() != null) 971 { 972 List<XMLizable> siblings = getParentNode().getSubtags(); 973 int index = siblings.indexOf(this) - 1; 974 while (index >= 0) 975 { 976 XMLizable sibling = siblings.get(index); 977 if (sibling instanceof XMLContainer) 978 { 979 requestedSibling = (XMLContainer) sibling; 980 break; 981 } 982 index--; 983 } 984 } 985 986 return requestedSibling; 987 } 988 989 //--------------------------------------------------------------------------- 990 public XMLContainer getNextSibling() 991 { 992 XMLContainer requestedSibling = null; 993 if (getParentNode() != null) 994 { 995 List<XMLizable> siblings = getParentNode().getSubtags(); 996 int index = siblings.indexOf(this) + 1; 997 while (index < siblings.size()) 998 { 999 XMLizable sibling = siblings.get(index); 1000 if (sibling instanceof XMLContainer) 1001 { 1002 requestedSibling = (XMLContainer) sibling; 1003 break; 1004 } 1005 1006 index++; 1007 } 1008 } 1009 1010 return requestedSibling; 1011 } 1012 1013 //########################################################################### 1014 // PROTECTED METHODS 1015 //########################################################################### 1016 1017 //--------------------------------------------------------------------------- 1018 protected String innerHTML() 1019 { 1020 StringBuilderPlus html = new StringBuilderPlus(); 1021 1022 if (mContentAndSubtagList != null) 1023 { 1024 for (Object content : mContentAndSubtagList) 1025 { 1026 if (content instanceof String) 1027 { 1028 html.append(content.toString()); 1029 } 1030 else if (content instanceof byte[]) 1031 { 1032 html.append(GZIP.uncompressToString((byte[]) content)); 1033 } 1034 else 1035 { 1036 XMLizable subtag = (XMLizable) content; 1037 html.append(subtag.toXML()); 1038 } 1039 } 1040 } 1041 1042 return html.toString(); 1043 } 1044 1045 //########################################################################### 1046 // PRIVATE METHODS 1047 //########################################################################### 1048 1049 //-------------------------------------------------------------------------- 1050 /** 1051 Adds content with or without escaping. Large content is compressed to save space. 1052 */ 1053 private synchronized void addContent(CharSequence inContent, boolean escape) 1054 { 1055// if (null == inContent || inContent.length() == 0) return; 1056 if (null == inContent) return; 1057 1058 if (null == mContentAndSubtagList) mContentAndSubtagList = new ArrayList(2); 1059 1060 1061 String content = inContent.toString(); 1062 if (escape) 1063 { 1064 content = XMLUtil.escapeContentIfNecessary(inContent.toString()); 1065// content = XMLUtil.escapeContent(inContent); 1066 } 1067 1068 mContentAndSubtagList.add(content.length() > sCompressionThreshold ? (Object) GZIP.compress(content) : (Object) content); 1069 } 1070 1071 1072 //--------------------------------------------------------------------------- 1073 private String getContent(boolean includeEmbeddedSubtags) 1074 { 1075 if (null == mContentAndSubtagList) return ""; 1076 1077 StringBuilder outContent = new StringBuilder(); 1078 for (Object content : mContentAndSubtagList) 1079 { 1080 if (content instanceof String) 1081 { 1082 outContent.append(content); 1083 } 1084 else if (content instanceof byte[]) 1085 { 1086 outContent.append(GZIP.uncompressToString((byte[]) content)); 1087 } 1088 else if (content instanceof XMLizable) 1089 { 1090 if (includeEmbeddedSubtags) 1091 { 1092 XMLizable subTag = (XMLizable) content; 1093 outContent.append(subTag.toXML()); 1094 } 1095 } 1096 else 1097 { 1098 throw new XMLException("Unexpected content type: '" + content + "'."); 1099 } 1100 } 1101 1102 return outContent.toString(); 1103 } 1104 1105 //--------------------------------------------------------------------------- 1106 private static <T extends XMLNode> void recursivelyGetSubtagsByName(String inTagName, XMLContainer inRootTag, List<T> inList, sMode inMode) 1107 { 1108 List<? extends XMLContainerImpl> subtags = inRootTag.getSubtags(); 1109 if (CollectionUtil.hasValues(subtags)) 1110 { 1111 for (int i = 0; i < subtags.size(); i++) 1112 { 1113 XMLizable subtag = subtags.get(i); 1114 if (subtag instanceof XMLContainer) 1115 { 1116 if (subtag instanceof XMLNode) 1117 { 1118 if (((XMLNode) subtag).getTagName().equals(inTagName)) 1119 { 1120 inList.add((T) subtag); 1121 if (inMode == sMode.REMOVE) 1122 { 1123 inRootTag.removeSubtag(subtag); 1124 subtags.remove(i); 1125 i--; 1126 } 1127 } 1128 } 1129 1130 recursivelyGetSubtagsByName(inTagName, (XMLContainer) subtag, inList, inMode); 1131 } 1132 } 1133 } 1134 } 1135 1136 //--------------------------------------------------------------------------- 1137 private static <T extends XMLNode> void recursivelyGetSubtagsByAttribute(String inAttributeName, Pattern inAttributeValuePattern, XMLContainerImpl inRootTag, List<T> inList, sMode inMode) 1138 { 1139 List<? extends XMLContainerImpl> subtags = inRootTag.getSubtags(); 1140 if (CollectionUtil.hasValues(subtags)) 1141 { 1142 for (int i = 0; i < subtags.size(); i++) 1143 { 1144 XMLizable subtag = subtags.get(i); 1145 if (! (subtag instanceof XMLContainerImpl)) 1146 { 1147 // Skip comments and CDATA 1148 continue; 1149 } 1150 1151 if (subtag instanceof XMLNode) 1152 { 1153 if (inAttributeName != null) 1154 { 1155 String attributeValue = ((XMLNode) subtag).getAttributeValue(inAttributeName); 1156 if (null == inAttributeValuePattern 1157 || (attributeValue != null 1158 && inAttributeValuePattern.matcher(attributeValue).find())) 1159 { 1160 inList.add((T)subtag); 1161 if (inMode == sMode.REMOVE) 1162 { 1163 inRootTag.removeSubtag(subtag); 1164 subtags.remove(i); 1165 i--; 1166 } 1167 } 1168 } 1169 else // Allow the attribute name to be null 1170 { 1171 for (XMLAttribute attr : ((XMLTag)subtag).getAttributes()) 1172 { 1173 if (null == inAttributeValuePattern 1174 || (attr.getValue() != null 1175 && inAttributeValuePattern.matcher(attr.getValue()).find())) 1176 { 1177 inList.add((T)subtag); 1178 if (inMode == sMode.REMOVE) 1179 { 1180 inRootTag.removeSubtag(subtag); 1181 subtags.remove(i); 1182 i--; 1183 } 1184 } 1185 } 1186 } 1187 } 1188 1189 recursivelyGetSubtagsByAttribute(inAttributeName, inAttributeValuePattern, (XMLContainerImpl) subtag, inList, inMode); 1190 } 1191 } 1192 } 1193 1194 //--------------------------------------------------------------------------- 1195 private static <T> void recursivelyGetSubtagsByClass(Class<T> inClassType, XMLContainerImpl inRootTag, List<T> inList, sMode inMode) 1196 { 1197 List<? extends XMLContainerImpl> subtags = inRootTag.getSubtags(); 1198 if (CollectionUtil.hasValues(subtags)) 1199 { 1200 for (int i = 0; i < subtags.size(); i++) 1201 { 1202 XMLContainerImpl subtag = subtags.get(i); 1203 1204 if (inClassType.isInstance(subtag)) 1205 { 1206 inList.add((T)subtag); 1207 1208 if (inMode == sMode.REMOVE) 1209 { 1210 inRootTag.removeSubtag(subtag); 1211 subtags.remove(i); 1212 i--; 1213 } 1214 } 1215 1216 recursivelyGetSubtagsByClass(inClassType, subtag, inList, inMode); 1217 } 1218 } 1219 } 1220 1221}