package org.nate.internal.dom4j; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import org.dom4j.Element; import org.dom4j.Node; import org.nate.encoder.NateDocument; import org.nate.encoder.NateNode; import org.nate.exception.BadCssExpressionException; import org.nate.internal.dom4j.cssselectors.Dom4jNodeSelector; import se.fishtank.css.selectors.NodeSelectorException; public class Dom4jBackedNateElement extends Dom4jBackedAbstractNode { private final Element element; public Dom4jBackedNateElement(Element element) { this.element = element; } @Override public NateNode copy() { verifyState(); return new Dom4jBackedNateElement(element.createCopy()); } @Override public void removeAttribute(String attributeName) { verifyState(); element.remove(element.attribute(attributeName)); } @Override public String render() { verifyState(); return element.asXML(); } @Override public void replaceChildren(NateDocument newChildrenSource) { verifyState(); removeChildren(); if (!(newChildrenSource instanceof Dom4jBackedDocumentFragment)) { throw new IllegalStateException( "Internal Error. Expected Dom4jBackedDocumentFragment, but got: " + newChildrenSource); } Collection<? extends Node> newChildren = ((Dom4jBackedAbstractNode) newChildrenSource).getDom4jNodes(); for (Node node : newChildren) { this.element.add((Node) node.clone()); } } @SuppressWarnings("unchecked") @Override public void replaceWith(List<NateNode> newNodes) { verifyState(); Element parent = this.element.getParent(); List<Node> newFamily = new ArrayList<Node>(); for (Node sibling : (List<Node>) parent.content()) { if (sibling == this.element) { newFamily.addAll(cloneAll(dom4jNodesOf(newNodes))); } else { newFamily.add(sibling); } } removeChildrenFrom(parent); for (Node node : newFamily) { parent.add(node); } // This element has been removed, and so no further operations will be valid. invalidate(); } @Override public void setAttribute(String name, String value) { verifyState(); element.addAttribute(name, value); } @Override public void setTextContent(String text) { verifyState(); removeChildren(); element.setText(text); } @SuppressWarnings("unchecked") protected Set findMatchingElements(String selector) { try { return new Dom4jNodeSelector(element).querySelectorAll(selector); } catch (NodeSelectorException e) { throw new BadCssExpressionException("Invalid CSS Expression: " + selector, e); } } private void removeChildren() { removeChildrenFrom(element); } private static List<Node> dom4jNodesOf(List<NateNode> nateNodes) { List<Node> dom4jNodes = new ArrayList<Node>(nateNodes.size()); for (NateNode nateNode : nateNodes) { dom4jNodes.addAll(((Dom4jBackedAbstractNode) nateNode).getDom4jNodes()); } return dom4jNodes; } @SuppressWarnings("unchecked") private static void removeChildrenFrom(Element element) { // TODO: Measure performance. I bet this is quadratic in number of children!!!! // Copy into new list to avoid ConcurrentModificationException List<Node> childNodes = new ArrayList<Node>(element.content()); for (Node childNode : childNodes) { element.remove(childNode); } } private static Collection<? extends Node> cloneAll(List<Node> originals) { Collection<Node> clones = new ArrayList<Node>(originals.size()); for (Node node : originals) { clones.add((Node) node.clone()); } return clones; } @Override protected Collection<? extends Node> getDom4jNodes() { return Collections.singletonList(element); } }