package gutenberg.pegdown; import com.google.common.base.Optional; import com.google.common.collect.Lists; import org.pegdown.ast.Node; import java.util.List; import java.util.Stack; /** * @author <a href="http://twitter.com/aloyer">@aloyer</a> */ public class TreeNavigation { private final Stack<Node> ancestorsStack; public TreeNavigation(List<Node> ancestorsStack) { this(); this.ancestorsStack.addAll(ancestorsStack); } public TreeNavigation() { this.ancestorsStack = new Stack<Node>(); } public TreeNavigation push(Node node) { ancestorsStack.push(node); return this; } public TreeNavigation pushChild(int nth) { return push(peek().getChildren().get(nth)); } public TreeNavigation pushChild() { return pushChild(0); } public Node pop() { return ancestorsStack.pop(); } public Node peek() { return ancestorsStack.peek(); } public boolean ancestorTreeMatches(Class<? extends Node>... ancestorTypes) { int len = ancestorsStack.size(); if (ancestorTypes.length > len) return false; for (int i = 0; i < ancestorTypes.length; i++) { Class<? extends Node> ancestor = ancestorTypes[i]; Node node = ancestorsStack.get(len - 1 - i); if (!ancestor.isInstance(node)) return false; } return true; } public List<Node> ancestorsStack() { return ancestorsStack; } private int numberOfAncestors() { return ancestorsStack.size(); } public static Node lookupChild(Node node, Class<? extends Node>... childClasses) { Node child = node; for (Class<? extends Node> childClass : childClasses) { if(child==null) return null; List<Node> children = child.getChildren(); if(children==null || children.isEmpty()) return null; child = children.get(0); if (!childClass.isInstance(child)) return null; } return child; } @SuppressWarnings({"unchecked", "UnusedParameters"}) public <T extends Node> T peek(Class<T> nodeType) { return (T) peek(); } public static abstract class Query { public abstract Optional<TreeNavigation> query(TreeNavigation nav); public Query then(final Query next) { final Query first = this; return new Query() { @Override public Optional<TreeNavigation> query(TreeNavigation nav) { Optional<TreeNavigation> res = first.query(nav); if (res.isPresent()) return next.query(res.get()); return res; } }; } } public static Ancestor ancestor(Class<? extends Node>... ancestorTypes) { return new Ancestor(ancestorTypes); } public static SiblingBefore siblingBefore() { return new SiblingBefore(); } public static FirstAncestorOfType firstAncestorOfType(Class<? extends Node> ancestorType) { return new FirstAncestorOfType(ancestorType); } public static OfType ofType(Class<? extends Node> nodeType) { return new OfType(nodeType); } public static class OfType extends Query { private final Class<? extends Node> nodeType; public OfType(Class<? extends Node> nodeType) { this.nodeType = nodeType; } public Optional<TreeNavigation> query(TreeNavigation nav) { Node node = nav.peek(); if (nodeType.isInstance(node)) return Optional.of(nav); return Optional.absent(); } } public static class SiblingBefore extends Query { public SiblingBefore() { } public Optional<TreeNavigation> query(TreeNavigation nav) { int len = nav.numberOfAncestors(); if (len <= 1) return Optional.absent(); List<Node> ancestors = nav.ancestorsStack(); Node n = ancestors.get(len - 1); Node p = ancestors.get(len - 2); Node s = null; for (Node sibling : p.getChildren()) { if (sibling == n) { if (s == null) { return Optional.absent(); } List<Node> nodes = Lists.newArrayList(ancestors.subList(0, len - 2)); nodes.add(s); return Optional.of(new TreeNavigation(nodes)); } s = sibling; } return Optional.absent(); } } public static class FirstAncestorOfType extends Query { private final Class<? extends Node> ancestorType; public FirstAncestorOfType(Class<? extends Node> ancestorType) { this.ancestorType = ancestorType; } public Optional<TreeNavigation> query(TreeNavigation nav) { List<Node> ancestors = nav.ancestorsStack(); for (int i = ancestors.size() - 1; i >= 0; i--) { Node ancestor = ancestors.get(i); if (ancestorType.isInstance(ancestor)) return Optional.of(new TreeNavigation(ancestors.subList(0, i + 1))); } return Optional.absent(); } } public static class Ancestor extends Query { private final Class<? extends Node>[] ancestorTypes; public Ancestor(Class<? extends Node>... ancestorTypes) { this.ancestorTypes = ancestorTypes; } public Optional<TreeNavigation> query(TreeNavigation nav) { int len = nav.numberOfAncestors(); if (ancestorTypes.length > len) return Optional.absent(); List<Node> ancestors = nav.ancestorsStack(); for (int i = 0; i < ancestorTypes.length; i++) { Class<? extends Node> ancestor = ancestorTypes[i]; Node node = ancestors.get(len - 1 - i); if (!ancestor.isInstance(node)) return Optional.absent(); } return Optional.of(new TreeNavigation(ancestors.subList(0, len - ancestorTypes.length + 1))); } } }