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