/** * Remove a child. * <p> * If child is not a child of this parent, then false is returned. * * @param child The child. * @return {@code true} if child was removed, {@code false} otherwise. */ public default boolean removeChild(Child child) { if (child != null && child.getParent() == this) { getModifiableChildren().remove(child); child.setParent(null); return true; } else { return false; } }
@Override public Element getRootElement() { for (final Child child : children) { if (child.getType() == NodeType.ELEMENT) { return (Element) child; } } return null; }
/** * Merges all consecutive texts locally. */ public default void mergeTexts() { if (getChildrenCount() > 1) { Child ref = getChildAt(0); int index = 1; while (index < getChildrenCount()) { final Child next = getChildAt(index); if (ref.getType() == NodeType.TEXT && next.getType() == NodeType.TEXT) { // ref and next are both texts: merge them ((Text) ref).appendContent(((Text) next).getContent()); // Remove next next.detach(); // Do not change index and ref } else { ref = next; index++; } } } }
/** * Returns the highest ancestor of this child that is a child. * <p> * This may be this child. * * @return The root child of this child. */ public default Child getRootChild() { Child index = this; while (index.getParent() instanceof Child) { index = (AbstractChild) index.getParent(); } return index; }
@Override public Element clone(boolean recurse) { final Element result = new Element(this); if (recurse) { for (final Child child : getChildren()) { result.addChild(child.clone(true)); } } return result; }
/** * Detach this child from its parent. * <p> * Equivalent to {@code setParent(null)}. */ public default void detach() { setParent(null); } }
@Override public boolean deepEquals(Node node) { if (this == node) { return true; } if (!(node instanceof Document)) { return false; } final Document other = (Document) node; if (getChildrenCount() == other.getChildrenCount()) { for (int index = 0; index < getChildrenCount(); index++) { if (!getChildAt(index).deepEquals(other.getChildAt(index))) { return false; } } } else { return false; } return true; }
/** * Merges all consecutive comments locally. */ public default void mergeComments() { if (getChildrenCount() > 1) { Child ref = getChildAt(0); int index = 1; while (index < getChildrenCount()) { final Child next = getChildAt(index); if (ref.getType() == NodeType.COMMENT && next.getType() == NodeType.COMMENT) { // ref and next are both texts: merge them ((Comment) ref).appendContent(((Comment) next).getContent()); // Remove next next.detach(); // Do not change index and ref } else { ref = next; index++; } } } }
/** * Returns the parent of this child, cast to a target class. * * @param <E> The target type. * @param parentClass The target class. * @return The parent of this child as a {@code parentClass} object. */ public default <E extends Parent> E getParent(Class<E> parentClass) { return parentClass.cast(getParent()); }
@Override public Document clone(boolean recurse) { final Document result = new Document(); if (recurse) { for (final Child child : getChildren()) { result.addChild(child.clone(true)); } } return result; }
/** * Adds a child to this parent. * <p> * If possible, child current parent is changed to this node. * * @param <C> Type of the child. * @param child The child. <em>MUST NOT</em> be null. * @return The input {@code child}. * @throws IllegalArgumentException If {@code child} is {@code null}. * @throws IllegalStateException If {@code child}'s parent can not be set. */ public default <C extends Child> C addChild(C child) { Checks.isNotNull(child, "child"); // setParent calls canAddChild() child.setParent(this); return child; }
@Override public boolean deepEquals(Node node) { if (this == node) { return true; } if (!(node instanceof Element)) { return false; } final Element other = (Element) node; if (getChildrenCount() == other.getChildrenCount() && localEquals(other)) { for (int index = 0; index < getChildrenCount(); index++) { if (!getChildAt(index).deepEquals(other.getChildAt(index))) { return false; } } return true; } else { return false; } }
public static boolean hasAncestorMatching(Node node, Predicate<Node> predicate) { Node index = node; while (index != null) { if (predicate.test(index)) { return true; } if (index instanceof Child) { index = ((Child) index).getParent(); } else { index = null; } } return false; }
@Override public boolean canAddChild(Child child) { if (child == null) { return false; } else if (child.getType() == NodeType.ELEMENT) { // TODO check recursion return true; } else { return true; } }
@Override public boolean canAddChild(Child child) { if (child == null) { return false; } else if (child.getType() == NodeType.ELEMENT) { return getRootElement() == null; } else { return true; } }
/** * Set the index of this child. * * @param to The new index of this child. * @throws IllegalArgumentException When this child has no parent,<br> * or {@code to < 0 or to >= getParent().getChildrenCount()}. */ public default void setIndex(int to) { CollectionsUtil.setIndex(getParent().getChildren(), this, to); }
@Override public void endElement(String uri, String localName, String qName) throws SAXException { LOGGER.trace("endElement()"); if (top() == Action.KEEP) { final Element current = (Element) currentParent; checkActiveText(true); currentParent = ((Child) currentParent).getParent(); resetActiveText(); if (!acceptsElementPost(current.getParent(), current)) { current.detach(); } } popAction(); }
/** * Adds a text as last child. * * @param content The text content. * @param merge If {@code true}, then if last child exists and is a text, * {@code content} is added to this last child.<br> * Otherwise, a text child is created and added to last position. * @return The modified or created text. */ public Text addText(String content, boolean merge) { if (merge) { final Child last = getLastChild(); if (last != null && last.getType() == NodeType.TEXT) { ((Text) last).appendContent(content); return (Text) last; } } final Text result = new Text(content); addChild(result); return result; }