package org.nate.internal.dom4j.cssselectors; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.dom4j.Branch; import org.nate.internal.dom4j.cssselectors.internal.AttributeSpecifierChecker; import org.nate.internal.dom4j.cssselectors.internal.NodeTraversalChecker; import org.nate.internal.dom4j.cssselectors.internal.PseudoClassSpecifierChecker; import org.nate.internal.dom4j.cssselectors.internal.PseudoNthSpecifierChecker; import org.nate.internal.dom4j.cssselectors.internal.TagChecker; import se.fishtank.css.selectors.NodeSelector; import se.fishtank.css.selectors.NodeSelectorException; import se.fishtank.css.selectors.Selector; import se.fishtank.css.selectors.Specifier; import se.fishtank.css.selectors.scanner.Scanner; import se.fishtank.css.selectors.scanner.ScannerException; import se.fishtank.css.selectors.specifier.AttributeSpecifier; import se.fishtank.css.selectors.specifier.NegationSpecifier; import se.fishtank.css.selectors.specifier.PseudoClassSpecifier; import se.fishtank.css.selectors.specifier.PseudoNthSpecifier; import se.fishtank.css.util.Assert; /** * Simple port of Christer Sandberg's CSS selectors to Dom4j (https://github.com/chrsan/css-selectors) */ public class Dom4jNodeSelector implements NodeSelector<Branch> { private Branch root; public Dom4jNodeSelector(Branch root) { Assert.notNull(root, "root is null!"); this.root = root; } @Override public Branch querySelector(String selectors) throws NodeSelectorException { Set<Branch> result = querySelectorAll(selectors); if (result.isEmpty()) { return null; } return result.iterator().next(); } @Override public Set<Branch> querySelectorAll(String selectors) throws NodeSelectorException { Assert.notNull(selectors, "selectors is null!"); List<List<Selector>> groups; try { Scanner scanner = new Scanner(selectors); groups = scanner.scan(); } catch (ScannerException e) { throw new NodeSelectorException(e); } Set<Branch> results = new LinkedHashSet<Branch>(); for (List<Selector> parts : groups) { Set<Branch> result = check(parts); if (!result.isEmpty()) { results.addAll(result); } } return results; } private Set<Branch> check(List<Selector> parts) throws NodeSelectorException { Set<Branch> result = new LinkedHashSet<Branch>(); result.add(root); for (Selector selector : parts) { NodeTraversalChecker checker = new TagChecker(selector); result = checker.check(result); if (result.isEmpty()) { // Bail out early. return result; } if (selector.hasSpecifiers()) { for (Specifier specifier : selector.getSpecifiers()) { switch (specifier.getType()) { case ATTRIBUTE: checker = new AttributeSpecifierChecker((AttributeSpecifier) specifier); break; case PSEUDO: if (specifier instanceof PseudoClassSpecifier) { checker = new PseudoClassSpecifierChecker((PseudoClassSpecifier) specifier); } else if (specifier instanceof PseudoNthSpecifier) { checker = new PseudoNthSpecifierChecker((PseudoNthSpecifier) specifier); } break; case NEGATION: final Set<Branch> negationNodes = checkNegationSpecifier((NegationSpecifier) specifier); checker = new NodeTraversalChecker() { @Override public Set<Branch> check(Set<Branch> nodes) throws NodeSelectorException { Set<Branch> set = new LinkedHashSet<Branch>(nodes); set.removeAll(negationNodes); return set; } }; break; } result = checker.check(result); if (result.isEmpty()) { // Bail out early. return result; } } } } return result; } private Set<Branch> checkNegationSpecifier(NegationSpecifier specifier) throws NodeSelectorException { List<Selector> parts = new ArrayList<Selector>(1); parts.add(specifier.getSelector()); return check(parts); } }