001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.dom; 014 015import org.apache.tapestry5.func.Predicate; 016import org.apache.tapestry5.internal.TapestryInternalUtils; 017import org.apache.tapestry5.internal.util.PrintOutCollector; 018import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 019import org.apache.tapestry5.ioc.internal.util.InternalUtils; 020import org.apache.tapestry5.ioc.util.Stack; 021 022import java.io.PrintWriter; 023import java.util.*; 024 025/** 026 * An element that will render with a begin tag and attributes, a body, and an end tag. Also acts as a factory for 027 * enclosed Element, Text and Comment nodes. 028 * 029 * TODO: Support for CDATA nodes. Do we need Entity nodes? 030 */ 031public final class Element extends Node 032{ 033 034 private final String name; 035 036 private Node firstChild; 037 038 private Node lastChild; 039 040 private Attribute firstAttribute; 041 042 private final Document document; 043 044 /** 045 * URI of the namespace which contains the element. A quirk in XML is that the element may be in a namespace it 046 * defines itself, so resolving the namespace to a prefix must wait until render time (since the Element is created 047 * before the namespaces for it are defined). 048 */ 049 private final String namespace; 050 051 private Map<String, String> namespaceToPrefix; 052 053 /** 054 * Constructor for a root element. 055 */ 056 Element(Document container, String namespace, String name) 057 { 058 super(null); 059 060 document = container; 061 this.namespace = namespace; 062 this.name = name; 063 } 064 065 /** 066 * Constructor for a nested element. 067 */ 068 Element(Element parent, String namespace, String name) 069 { 070 super(parent); 071 072 this.namespace = namespace; 073 this.name = name; 074 075 document = null; 076 } 077 078 @Override 079 public Document getDocument() 080 { 081 return document != null ? document : super.getDocument(); 082 } 083 084 /** 085 * Adds an attribute to the element, but only if the attribute name does not already exist. 086 * The "class" attribute is treated specially: the new value is appended, after a space, to the 087 * existing value. 088 * 089 * @param name 090 * the name of the attribute to add 091 * @param value 092 * the value for the attribute. A value of null is allowed, and no attribute will be added to the 093 * element. 094 */ 095 public Element attribute(String name, String value) 096 { 097 return attribute(null, name, value); 098 } 099 100 /** 101 * Adds a namespaced attribute to the element, but only if the attribute name does not already exist. 102 * The "class" attribute of the default namespace is treated specially: the new value 103 * is appended, after a space, to the existing value. 104 * 105 * @param namespace 106 * the namespace to contain the attribute, or null for the default namespace 107 * @param name 108 * the name of the attribute to add 109 * @param value 110 * the value for the attribute. A value of null is allowed, and no attribute will be added to the 111 * element. 112 */ 113 public Element attribute(String namespace, String name, String value) 114 { 115 assert InternalUtils.isNonBlank(name); 116 117 updateAttribute(namespace, name, value, false); 118 119 return this; 120 } 121 122 private void updateAttribute(String namespace, String name, String value, boolean force) 123 { 124 if (!force && value == null) 125 { 126 return; 127 } 128 129 // TAP5-2660: handle empty namespace as the null one, since both represent no namespace 130 if ("".equals(namespace)) 131 { 132 namespace = null; 133 } 134 135 Attribute prior = null; 136 Attribute cursor = firstAttribute; 137 138 while (cursor != null) 139 { 140 if (cursor.matches(namespace, name)) 141 { 142 boolean isClass = namespace == null && name.equals("class"); 143 144 if (!(force || isClass)) 145 { 146 return; 147 } 148 149 if (value != null) 150 { 151 if (!force && isClass) 152 { 153 cursor.value += (" " + value); 154 } else 155 { 156 cursor.value = value; 157 } 158 159 return; 160 } 161 162 // Remove this Attribute node from the linked list 163 164 if (prior == null) 165 { 166 firstAttribute = cursor.nextAttribute; 167 } else 168 { 169 prior.nextAttribute = cursor.nextAttribute; 170 } 171 172 return; 173 } 174 175 prior = cursor; 176 cursor = cursor.nextAttribute; 177 } 178 179 // Don't add an Attribute if the value is null. 180 181 if (value != null) 182 { 183 firstAttribute = new Attribute(this, namespace, name, value, firstAttribute); 184 } 185 } 186 187 /** 188 * Convenience for invoking {@link #attribute(String, String)} multiple times. 189 * 190 * @param namesAndValues 191 * alternating attribute names and attribute values 192 */ 193 public Element attributes(String... namesAndValues) 194 { 195 int i = 0; 196 while (i < namesAndValues.length) 197 { 198 String name = namesAndValues[i++]; 199 String value = namesAndValues[i++]; 200 201 attribute(name, value); 202 } 203 204 return this; 205 } 206 207 /** 208 * Forces changes to a number of attributes. The new attributes <em>overwrite</em> previous values. Overriding an 209 * attribute's value to null will remove the attribute entirely. 210 * 211 * @param namesAndValues 212 * alternating attribute names and attribute values 213 * @return this element 214 */ 215 public Element forceAttributes(String... namesAndValues) 216 { 217 return forceAttributesNS(null, namesAndValues); 218 } 219 220 /** 221 * Forces changes to a number of attributes in the global namespace. The new attributes <em>overwrite</em> previous 222 * values (event for the "class" attribute). Overriding attribute's value to null will remove the attribute entirely. 223 * TAP5-708: don't use element namespace for attributes 224 * 225 * @param namespace 226 * the namespace or null 227 * @param namesAndValues 228 * alternating attribute name and value 229 * @return this element 230 */ 231 public Element forceAttributesNS(String namespace, String... namesAndValues) 232 { 233 int i = 0; 234 235 while (i < namesAndValues.length) 236 { 237 String name = namesAndValues[i++]; 238 String value = namesAndValues[i++]; 239 240 updateAttribute(namespace, name, value, true); 241 } 242 243 return this; 244 } 245 246 /** 247 * Creates and returns a new Element node as a child of this node. 248 * 249 * @param name 250 * the name of the element to create 251 * @param namesAndValues 252 * alternating attribute names and attribute values 253 */ 254 public Element element(String name, String... namesAndValues) 255 { 256 assert InternalUtils.isNonBlank(name); 257 Element child = newChild(new Element(this, null, name)); 258 259 child.attributes(namesAndValues); 260 261 return child; 262 } 263 264 /** 265 * Inserts a new element before this element. 266 * 267 * @param name 268 * element name 269 * @param namesAndValues 270 * attribute names and values 271 * @return the new element 272 * @since 5.3 273 */ 274 public Element elementBefore(String name, String... namesAndValues) 275 { 276 assert InternalUtils.isNonBlank(name); 277 278 Element sibling = container.element(name, namesAndValues); 279 280 sibling.moveBefore(this); 281 282 return sibling; 283 } 284 285 286 /** 287 * Creates and returns a new Element within a namespace as a child of this node. 288 * 289 * @param namespace 290 * namespace to contain the element, or null 291 * @param name 292 * element name to create within the namespace 293 * @return the newly created element 294 */ 295 public Element elementNS(String namespace, String name) 296 { 297 assert InternalUtils.isNonBlank(name); 298 return newChild(new Element(this, namespace, name)); 299 } 300 301 /** 302 * Creates a new element, as a child of the current index, at the indicated index. 303 * 304 * @param index 305 * to insert at 306 * @param name 307 * element name 308 * @param namesAndValues 309 * attribute name / attribute value pairs 310 * @return the new element 311 */ 312 public Element elementAt(int index, String name, String... namesAndValues) 313 { 314 assert InternalUtils.isNonBlank(name); 315 Element child = new Element(this, null, name); 316 child.attributes(namesAndValues); 317 318 insertChildAt(index, child); 319 320 return child; 321 } 322 323 /** 324 * Adds the comment and returns this element for further construction. 325 */ 326 public Element comment(String text) 327 { 328 newChild(new Comment(this, text)); 329 330 return this; 331 } 332 333 /** 334 * Adds the raw text and returns this element for further construction. 335 */ 336 public Element raw(String text) 337 { 338 newChild(new Raw(this, text)); 339 340 return this; 341 } 342 343 /** 344 * Adds and returns a new text node (the text node is returned so that {@link Text#write(String)} or [@link 345 * {@link Text#writef(String, Object[])} may be invoked . 346 * 347 * @param text 348 * initial text for the node 349 * @return the new Text node 350 */ 351 public Text text(String text) 352 { 353 return newChild(new Text(this, text)); 354 } 355 356 /** 357 * Adds and returns a new CDATA node. 358 * 359 * @param content 360 * the content to be rendered by the node 361 * @return the newly created node 362 */ 363 public CData cdata(String content) 364 { 365 return newChild(new CData(this, content)); 366 } 367 368 private <T extends Node> T newChild(T child) 369 { 370 addChild(child); 371 372 return child; 373 } 374 375 @Override 376 void toMarkup(Document document, PrintWriter writer, Map<String, String> containerNamespacePrefixToURI) 377 { 378 Map<String, String> localNamespacePrefixToURI = createNamespaceURIToPrefix(containerNamespacePrefixToURI); 379 380 MarkupModel markupModel = document.getMarkupModel(); 381 382 StringBuilder builder = new StringBuilder(); 383 384 String prefixedElementName = toPrefixedName(localNamespacePrefixToURI, namespace, name); 385 386 builder.append('<').append(prefixedElementName); 387 388 // Output order used to be alpha sorted, but now it tends to be the inverse 389 // of the order in which attributes were added. 390 391 for (Attribute attr = firstAttribute; attr != null; attr = attr.nextAttribute) 392 { 393 attr.render(markupModel, builder, localNamespacePrefixToURI); 394 } 395 396 // Next, emit namespace declarations for each namespace. 397 398 List<String> namespaces = InternalUtils.sortedKeys(namespaceToPrefix); 399 400 for (String namespace : namespaces) 401 { 402 if (namespace.equals(Document.XML_NAMESPACE_URI)) 403 continue; 404 405 String prefix = namespaceToPrefix.get(namespace); 406 407 builder.append(" xmlns"); 408 409 if (!prefix.equals("")) 410 { 411 builder.append(':').append(prefix); 412 } 413 414 builder.append('='); 415 builder.append(markupModel.getAttributeQuote()); 416 417 markupModel.encodeQuoted(namespace, builder); 418 419 builder.append(markupModel.getAttributeQuote()); 420 } 421 422 EndTagStyle style = markupModel.getEndTagStyle(name); 423 424 boolean hasChildren = hasChildren(); 425 426 String close = (!hasChildren && style == EndTagStyle.ABBREVIATE) ? "/>" : ">"; 427 428 builder.append(close); 429 430 writer.print(builder.toString()); 431 432 if (hasChildren) 433 writeChildMarkup(document, writer, localNamespacePrefixToURI); 434 435 if (hasChildren || style == EndTagStyle.REQUIRE) 436 { 437 // TAP5-471: Avoid use of printf(). 438 writer.print("</"); 439 writer.print(prefixedElementName); 440 writer.print(">"); 441 } 442 } 443 444 String toPrefixedName(Map<String, String> namespaceURIToPrefix, String namespace, String name) 445 { 446 if (namespace == null || namespace.equals("")) 447 return name; 448 449 if (namespace.equals(Document.XML_NAMESPACE_URI)) 450 return "xml:" + name; 451 452 String prefix = namespaceURIToPrefix.get(namespace); 453 454 // This should never happen, because namespaces are automatically defined as needed. 455 456 if (prefix == null) 457 throw new IllegalArgumentException(String.format("No prefix has been defined for namespace '%s'.", 458 namespace)); 459 460 // The empty string indicates the default namespace which doesn't use a prefix. 461 462 if (prefix.equals("")) 463 return name; 464 465 return prefix + ":" + name; 466 } 467 468 /** 469 * Tries to find an element under this element (including itself) whose id is specified. 470 * Performs a width-first 471 * search of the document tree. 472 * 473 * @param id 474 * the value of the id attribute of the element being looked for 475 * @return the element if found. null if not found. 476 */ 477 public Element getElementById(final String id) 478 { 479 return getElementByAttributeValue("id", id); 480 } 481 482 /** 483 * Tries to find an element under this element (including itself) whose given attribute has a given value. 484 * 485 * @param attributeName 486 * the name of the attribute of the element being looked for 487 * @param attributeValue 488 * the value of the attribute of the element being looked for 489 * @return the element if found. null if not found. 490 * @since 5.2.3 491 */ 492 public Element getElementByAttributeValue(final String attributeName, final String attributeValue) 493 { 494 assert attributeName != null; 495 assert attributeValue != null; 496 497 return getElement(new Predicate<Element>() 498 { 499 public boolean accept(Element e) 500 { 501 String elementId = e.getAttribute(attributeName); 502 return attributeValue.equals(elementId); 503 } 504 }); 505 } 506 507 /** 508 * Tries to find an element under this element (including itself) accepted by the given predicate. 509 * 510 * @param predicate 511 * Predicate to accept the element 512 * @return the element if found. null if not found. 513 * @since 5.2.3 514 */ 515 public Element getElement(Predicate<Element> predicate) 516 { 517 LinkedList<Element> queue = CollectionFactory.newLinkedList(); 518 519 queue.add(this); 520 521 while (!queue.isEmpty()) 522 { 523 Element e = queue.removeFirst(); 524 525 if (predicate.accept(e)) 526 return e; 527 528 for (Element child : e.childElements()) 529 { 530 queue.addLast(child); 531 } 532 } 533 534 // Exhausted the entire tree 535 536 return null; 537 } 538 539 /** 540 * Searchs for a child element with a particular name below this element. The path parameter is a slash separated 541 * series of element names. 542 */ 543 public Element find(String path) 544 { 545 assert InternalUtils.isNonBlank(path); 546 Element search = this; 547 548 for (String name : TapestryInternalUtils.splitPath(path)) 549 { 550 search = search.findChildWithElementName(name); 551 552 if (search == null) 553 break; 554 } 555 556 return search; 557 } 558 559 private Element findChildWithElementName(String name) 560 { 561 for (Element child : childElements()) 562 { 563 if (child.getName().equals(name)) 564 return child; 565 } 566 567 // Not found. 568 569 return null; 570 } 571 572 private Iterable<Element> childElements() 573 { 574 return new Iterable<Element>() 575 { 576 public Iterator<Element> iterator() 577 { 578 return new Iterator<Element>() 579 { 580 private Node cursor = firstChild; 581 582 { 583 advance(); 584 } 585 586 private void advance() 587 { 588 while (cursor != null) 589 { 590 if (cursor instanceof Element) 591 return; 592 593 cursor = cursor.nextSibling; 594 } 595 } 596 597 public boolean hasNext() 598 { 599 return cursor != null; 600 } 601 602 public Element next() 603 { 604 Element result = (Element) cursor; 605 606 cursor = cursor.nextSibling; 607 608 advance(); 609 610 return result; 611 } 612 613 public void remove() 614 { 615 throw new UnsupportedOperationException("remove() not supported."); 616 } 617 }; 618 } 619 }; 620 } 621 622 public String getAttribute(String attributeName) 623 { 624 for (Attribute attr = firstAttribute; attr != null; attr = attr.nextAttribute) 625 { 626 if (attr.getName().equalsIgnoreCase(attributeName)) 627 return attr.value; 628 } 629 630 return null; 631 } 632 633 public String getName() 634 { 635 return name; 636 } 637 638 /** 639 * Adds one or more CSS class names to the "class" attribute. 640 * 641 * @param classNames 642 * one or more CSS class names 643 * @return the element for further configuration 644 * @deprecated Deprecated in 5.4, as this is now special behavior for the "class" attribute. 645 */ 646 public Element addClassName(String... classNames) 647 { 648 for (String name : classNames) 649 { 650 attribute("class", name); 651 } 652 653 return this; 654 } 655 656 /** 657 * Defines a namespace for this element, mapping a URI to a prefix. This will affect how namespaced elements and 658 * attributes nested within the element are rendered, and will also cause <code>xmlns:</code> attributes (to define 659 * the namespace and prefix) to be rendered. 660 * 661 * @param namespace 662 * URI of the namespace 663 * @param namespacePrefix 664 * prefix 665 * @return this element 666 */ 667 public Element defineNamespace(String namespace, String namespacePrefix) 668 { 669 assert namespace != null; 670 assert namespacePrefix != null; 671 if (namespace.equals(Document.XML_NAMESPACE_URI)) 672 return this; 673 674 if (namespaceToPrefix == null) 675 namespaceToPrefix = CollectionFactory.newMap(); 676 677 namespaceToPrefix.put(namespace, namespacePrefix); 678 679 return this; 680 } 681 682 /** 683 * Returns the namespace for this element (which is typically a URL). The namespace may be null. 684 */ 685 public String getNamespace() 686 { 687 return namespace; 688 } 689 690 /** 691 * Removes an element; the element's children take the place of the node within its container. 692 */ 693 public void pop() 694 { 695 // Have to be careful because we'll be modifying the underlying list of children 696 // as we work, so we need a copy of the children. 697 698 List<Node> childrenCopy = CollectionFactory.newList(getChildren()); 699 700 for (Node child : childrenCopy) 701 { 702 child.moveBefore(this); 703 } 704 705 remove(); 706 } 707 708 /** 709 * Removes all children from this element. 710 * 711 * @return the element, for method chaining 712 */ 713 public Element removeChildren() 714 { 715 firstChild = null; 716 lastChild = null; 717 718 return this; 719 } 720 721 /** 722 * Creates the URI to namespace prefix map for this element, which reflects namespace mappings from containing 723 * elements. In addition, automatic namespaces are defined for any URIs that are not explicitly mapped (this occurs 724 * sometimes in Ajax partial render scenarios). 725 * 726 * @return a mapping from namespace URI to namespace prefix 727 */ 728 private Map<String, String> createNamespaceURIToPrefix(Map<String, String> containerNamespaceURIToPrefix) 729 { 730 MapHolder holder = new MapHolder(containerNamespaceURIToPrefix); 731 732 holder.putAll(namespaceToPrefix); 733 734 // result now contains all the mappings, including this element's. 735 736 // Add a mapping for the element's namespace. 737 738 if (InternalUtils.isNonBlank(namespace)) 739 { 740 741 // Add the namespace for the element as the default namespace. 742 743 if (!holder.getResult().containsKey(namespace)) 744 { 745 defineNamespace(namespace, ""); 746 holder.put(namespace, ""); 747 } 748 } 749 750 // And for any attributes that have a namespace. 751 752 for (Attribute attr = firstAttribute; attr != null; attr = attr.nextAttribute) 753 addMappingIfNeeded(holder, attr.getNamespace()); 754 755 return holder.getResult(); 756 } 757 758 private void addMappingIfNeeded(MapHolder holder, String namespace) 759 { 760 if (InternalUtils.isBlank(namespace)) 761 return; 762 763 Map<String, String> current = holder.getResult(); 764 765 if (current.containsKey(namespace)) 766 return; 767 768 // A missing namespace. 769 770 Set<String> prefixes = CollectionFactory.newSet(holder.getResult().values()); 771 772 // A clumsy way to find a unique id for the new namespace. 773 774 int i = 0; 775 while (true) 776 { 777 String prefix = "ns" + i; 778 779 if (!prefixes.contains(prefix)) 780 { 781 defineNamespace(namespace, prefix); 782 holder.put(namespace, prefix); 783 return; 784 } 785 786 i++; 787 } 788 } 789 790 @Override 791 protected Map<String, String> getNamespaceURIToPrefix() 792 { 793 MapHolder holder = new MapHolder(); 794 795 List<Element> elements = CollectionFactory.newList(this); 796 797 Element cursor = container; 798 799 while (cursor != null) 800 { 801 elements.add(cursor); 802 cursor = cursor.container; 803 } 804 805 // Reverse the list, so that later elements will overwrite earlier ones. 806 807 Collections.reverse(elements); 808 809 for (Element e : elements) 810 holder.putAll(e.namespaceToPrefix); 811 812 return holder.getResult(); 813 } 814 815 /** 816 * Returns true if the element has no children, or has only text children that contain only whitespace. 817 * 818 * @since 5.1.0.0 819 */ 820 public boolean isEmpty() 821 { 822 List<Node> children = getChildren(); 823 824 if (children.isEmpty()) 825 return true; 826 827 for (Node n : children) 828 { 829 if (n instanceof Text) 830 { 831 Text t = (Text) n; 832 833 if (t.isEmpty()) 834 continue; 835 } 836 837 // Not a text node, or a non-empty text node, then the element isn't empty. 838 return false; 839 } 840 841 return true; 842 } 843 844 /** 845 * Depth-first visitor traversal of this Element and its Element children. The traversal order is the same as render 846 * order. 847 * 848 * @param visitor 849 * callback 850 * @since 5.1.0.0 851 */ 852 public void visit(Visitor visitor) 853 { 854 Stack<Element> queue = CollectionFactory.newStack(); 855 856 queue.push(this); 857 858 while (!queue.isEmpty()) 859 { 860 Element e = queue.pop(); 861 862 visitor.visit(e); 863 864 e.queueChildren(queue); 865 } 866 } 867 868 private void queueChildren(Stack<Element> queue) 869 { 870 if (firstChild == null) 871 return; 872 873 List<Element> childElements = CollectionFactory.newList(); 874 875 for (Node cursor = firstChild; cursor != null; cursor = cursor.nextSibling) 876 { 877 if (cursor instanceof Element) 878 childElements.add((Element) cursor); 879 } 880 881 Collections.reverse(childElements); 882 883 for (Element e : childElements) 884 queue.push(e); 885 } 886 887 void addChild(Node child) 888 { 889 child.container = this; 890 891 if (lastChild == null) 892 { 893 firstChild = child; 894 lastChild = child; 895 return; 896 } 897 898 lastChild.nextSibling = child; 899 lastChild = child; 900 } 901 902 void insertChildAt(int index, Node newChild) 903 { 904 newChild.container = this; 905 906 if (index < 1) 907 { 908 newChild.nextSibling = firstChild; 909 firstChild = newChild; 910 } else 911 { 912 Node cursor = firstChild; 913 for (int i = 1; i < index; i++) 914 { 915 cursor = cursor.nextSibling; 916 } 917 918 newChild.nextSibling = cursor.nextSibling; 919 cursor.nextSibling = newChild; 920 } 921 922 if (index < 1) 923 firstChild = newChild; 924 925 if (newChild.nextSibling == null) 926 lastChild = newChild; 927 } 928 929 boolean hasChildren() 930 { 931 return firstChild != null; 932 } 933 934 void writeChildMarkup(Document document, PrintWriter writer, Map<String, String> namespaceURIToPrefix) 935 { 936 Node cursor = firstChild; 937 938 while (cursor != null) 939 { 940 cursor.toMarkup(document, writer, namespaceURIToPrefix); 941 942 cursor = cursor.nextSibling; 943 } 944 } 945 946 /** 947 * @return the concatenation of the String representations {@link #toString()} of its children. 948 */ 949 public final String getChildMarkup() 950 { 951 PrintOutCollector collector = new PrintOutCollector(); 952 953 writeChildMarkup(getDocument(), collector.getPrintWriter(), null); 954 955 return collector.getPrintOut(); 956 } 957 958 /** 959 * Returns an unmodifiable list of children for this element. Only {@link org.apache.tapestry5.dom.Element}s will 960 * have children. Also, note that unlike W3C DOM, attributes are not represented as 961 * {@link org.apache.tapestry5.dom.Node}s. 962 * 963 * @return unmodifiable list of children nodes 964 */ 965 public List<Node> getChildren() 966 { 967 List<Node> result = CollectionFactory.newList(); 968 Node cursor = firstChild; 969 970 while (cursor != null) 971 { 972 result.add(cursor); 973 cursor = cursor.nextSibling; 974 } 975 976 return result; 977 } 978 979 void remove(Node node) 980 { 981 Node prior = null; 982 Node cursor = firstChild; 983 984 while (cursor != null) 985 { 986 if (cursor == node) 987 { 988 Node afterNode = node.nextSibling; 989 990 if (prior != null) 991 prior.nextSibling = afterNode; 992 else 993 firstChild = afterNode; 994 995 // If node was the final node in the element then handle deletion. 996 // It's even possible node was the only node in the container. 997 998 if (lastChild == node) 999 { 1000 lastChild = prior != null ? prior : null; 1001 } 1002 1003 node.nextSibling = null; 1004 1005 return; 1006 } 1007 1008 prior = cursor; 1009 cursor = cursor.nextSibling; 1010 } 1011 1012 throw new IllegalArgumentException("Node to remove was not present as a child of this element."); 1013 } 1014 1015 void insertChildBefore(Node existing, Node node) 1016 { 1017 int index = indexOfNode(existing); 1018 1019 node.container = this; 1020 1021 insertChildAt(index, node); 1022 } 1023 1024 void insertChildAfter(Node existing, Node node) 1025 { 1026 Node oldAfter = existing.nextSibling; 1027 1028 existing.nextSibling = node; 1029 node.nextSibling = oldAfter; 1030 1031 if (oldAfter == null) 1032 lastChild = node; 1033 1034 node.container = this; 1035 } 1036 1037 int indexOfNode(Node node) 1038 { 1039 int index = 0; 1040 Node cursor = firstChild; 1041 1042 while (cursor != null) 1043 { 1044 if (node == cursor) 1045 return index; 1046 1047 cursor = cursor.nextSibling; 1048 index++; 1049 } 1050 1051 throw new IllegalArgumentException("Node not a child of this element."); 1052 } 1053 1054 /** 1055 * Returns the attributes for this Element as a (often empty) collection of 1056 * {@link org.apache.tapestry5.dom.Attribute}s. The order of the attributes within the collection is not specified. 1057 * Modifying the collection will not affect the attributes (use {@link #forceAttributes(String[])} to change 1058 * existing attribute values, and {@link #attribute(String, String, String)} to add new attribute values. 1059 * 1060 * @return attribute collection 1061 */ 1062 public Collection<Attribute> getAttributes() 1063 { 1064 Collection<Attribute> result = CollectionFactory.newList(); 1065 1066 for (Attribute a = firstAttribute; a != null; a = a.nextAttribute) 1067 { 1068 result.add(a); 1069 } 1070 1071 return result; 1072 } 1073}