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