/**
* Modification from original by Gist Labs, LLC (http://gistlabs.com)
*
* Copyright (c) 2009-2012, Christer Sandberg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.gistlabs.mechanize.util.css_query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
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.PseudoContainsSpecifier;
import se.fishtank.css.selectors.specifier.PseudoNthSpecifier;
import se.fishtank.css.util.Assert;
public class NodeSelector<Node> {
private final Node root;
private final NodeHelper<Node> helper;
public NodeSelector(final NodeHelper<Node> helper, final Node node) {
Assert.notNull(node, "root is null!");
Assert.notNull(helper, "helper is null!");
this.root = node;
this.helper = helper;
}
public Node find(final String selector) {
List<Node> findAll = findAll(selector);
if (findAll.size()>2) // too many results
throw new RuntimeException(String.format("Too many resusts (%s) for selector: %s", findAll.size(), selector));
else if (findAll.isEmpty())
return null;
else
return findAll.iterator().next();
}
public List<Node> findAll(final String selector) {
Assert.notNull(selector, "selectors is null!");
List<List<Selector>> groups;
try {
Scanner scanner = new Scanner(selector);
groups = scanner.scan();
} catch (ScannerException e) {
throw new RuntimeException(e);
}
Collection<Node> results = new LinkedHashSet<Node>();
for (Collection<Selector> parts : groups) {
Collection<Node> result;
try {
result = check(parts);
} catch (NodeSelectorException e) {
throw new RuntimeException(e);
}
if (!result.isEmpty())
results.addAll(result);
}
return new ArrayList<Node>(results);
}
/**
* Check the list of selector <em>parts</em> and return a set of nodes with the result.
*
* @param parts A list of selector <em>parts</em>.
* @return A set of nodes.
* @throws NodeSelectorException In case of an error.
*/
private Collection<Node> check(final Collection<Selector> parts) throws NodeSelectorException {
Collection<Node> result = new LinkedHashSet<Node>();
result.add(root);
boolean initialPart=true;
for (Selector selector : parts) {
Checker<Node> checker = new TagChecker<Node>(helper, selector, initialPart);
result = checker.check(result);
if (selector.hasSpecifiers())
for (Specifier specifier : selector.getSpecifiers()) {
switch (specifier.getType()) {
case ATTRIBUTE:
checker = new AttributeSpecifierChecker<Node>(helper, (AttributeSpecifier) specifier);
break;
case PSEUDO:
if (specifier instanceof PseudoClassSpecifier)
checker = new PseudoClassSpecifierChecker<Node>(helper, (PseudoClassSpecifier) specifier);
else if (specifier instanceof PseudoNthSpecifier)
checker = new PseudoNthSpecifierChecker<Node>(helper, (PseudoNthSpecifier) specifier);
else if (specifier instanceof PseudoContainsSpecifier)
checker = new PseudoContainsSpecifierChecker<Node>(helper, (PseudoContainsSpecifier) specifier);
break;
case NEGATION:
final Collection<Node> negationNodes = checkNegationSpecifier((NegationSpecifier) specifier);
checker = new Checker<Node>() {
@Override
public List<Node> check(final Collection<Node> nodes) {
Collection<Node> set = new LinkedHashSet<Node>(nodes);
set.removeAll(negationNodes);
return new ArrayList<Node>(set);
}
};
break;
}
result = checker.check(result);
if (result.isEmpty())
// Bail out early.
return result;
}
initialPart=false;
}
return result;
}
/**
* Check the {@link NegationSpecifier}.
* <p/>
* This method will add the {@link Selector} from the specifier in
* a list and invoke {@link #check(List)} with that list as the argument.
*
* @param specifier The negation specifier.
* @return A set of nodes after invoking {@link #check(List)}.
* @throws NodeSelectorException In case of an error.
*/
private Collection<Node> checkNegationSpecifier(final NegationSpecifier specifier) throws NodeSelectorException {
Collection<Selector> parts = new LinkedHashSet<Selector>(1);
parts.add(specifier.getSelector());
return check(parts);
}
}