private void byAttribute() { TokenQueue cq = new TokenQueue(tq.chompBalanced('[', ']')); // content queue String key = cq.consumeToAny(AttributeEvals); // eq, not, start, end, contain, match, (no val) Validate.notEmpty(key); cq.consumeWhitespace(); if (cq.isEmpty()) { if (key.startsWith("^")) evals.add(new Evaluator.AttributeStarting(key.substring(1))); else evals.add(new Evaluator.Attribute(key)); } else { if (cq.matchChomp("=")) evals.add(new Evaluator.AttributeWithValue(key, cq.remainder())); else if (cq.matchChomp("!=")) evals.add(new Evaluator.AttributeWithValueNot(key, cq.remainder())); else if (cq.matchChomp("^=")) evals.add(new Evaluator.AttributeWithValueStarting(key, cq.remainder())); else if (cq.matchChomp("$=")) evals.add(new Evaluator.AttributeWithValueEnding(key, cq.remainder())); else if (cq.matchChomp("*=")) evals.add(new Evaluator.AttributeWithValueContaining(key, cq.remainder())); else if (cq.matchChomp("~=")) evals.add(new Evaluator.AttributeWithValueMatching(key, Pattern.compile(cq.remainder()))); else throw new Selector.SelectorParseException("Could not parse attribute query '%s': unexpected token at '%s'", query, cq.remainder()); } }
/** Add a character to the start of the queue (will be the next character retrieved). @param c character to add */ public void addFirst(Character c) { addFirst(c.toString()); }
void processResponseHeaders(Map<String, List<String>> resHeaders) { for (Map.Entry<String, List<String>> entry : resHeaders.entrySet()) { String name = entry.getKey(); if (name == null) continue; // http/1.1 line List<String> values = entry.getValue(); if (name.equalsIgnoreCase("Set-Cookie")) { for (String value : values) { if (value == null) continue; TokenQueue cd = new TokenQueue(value); String cookieName = cd.chompTo("=").trim(); String cookieVal = cd.consumeTo(";").trim(); // ignores path, date, domain, validateTLSCertificates et al. req'd? // name not blank, value not null if (cookieName.length() > 0) cookie(cookieName, cookieVal); } } for (String value : values) { addHeader(name, value); } } }
public String chompToIgnoreCase(String seq) { String data = consumeToIgnoreCase(seq); // case insensitive scan matchChomp(seq); return data; }
/** * Pulls a string off the queue (like consumeTo), and then pulls off the matched string (but does not return it). * <p> * If the queue runs out of characters before finding the seq, will return as much as it can (and queue will go * isEmpty() == true). * @param seq String to match up to, and not include in return, and to pull off queue. <b>Case sensitive.</b> * @return Data matched from queue. */ public String chompTo(String seq) { String data = consumeTo(seq); matchChomp(seq); return data; }
/** Consume an attribute key off the queue (letter, digit, -, _, :") @return attribute key */ public String consumeAttributeKey() { int start = pos; while (!isEmpty() && (matchesWord() || matchesAny('-', '_', ':'))) pos++; return queue.substring(start, pos); }
private Attribute parseAttribute() { whitespace(); String key = tq.consumeAttributeKey(); String value = ""; whitespace(); if (tq.matchChomp("=")) { whitespace(); if (tq.matchChomp(SQ)) { value = tq.chompTo(SQ); } else if (tq.matchChomp(DQ)) { value = tq.chompTo(DQ); } else { StringBuilder valueAccum = new StringBuilder(); // no ' or " to look for, so scan to end tag or space (or end of stream) while (!tq.matchesAny("<", "/>", ">") && !tq.matchesWhitespace() && !tq.isEmpty()) { valueAccum.append(tq.consume()); } value = valueAccum.toString(); } whitespace(); } if (!Strings.empty(key)) return Attribute.createFromEncoded(key, value); else { tq.consume(); // unknown char, keep popping so not get stuck return null; } }
private void parseStartTag() { tq.consume("<"); String tagName = tq.consumeTagName(); tq.addFirst("<"); parseTextNode(); return; while (!tq.matchesAny("<", "/>", ">") && !tq.isEmpty()) { Attribute attribute = parseAttribute(); if (attribute != null) if (tq.matchChomp("/>")) { // close empty element or tag isEmptyElement = true; } else { tq.matchChomp(">"); String data = tq.chompTo("</" + tagName); tq.chompTo(">");
private void parseXmlDecl() { tq.consume("<"); Character firstChar = tq.consume(); // <? or <!, from initial match. boolean procInstr = firstChar.toString().equals("!"); String data = tq.chompTo(">"); XmlDeclaration decl = new XmlDeclaration(data, baseUri, procInstr); annotate(decl); // TODO - should annotations even apply to declarations? lines(decl, data); add(decl); }
private String consumeSubQuery() { StringBuilder sq = new StringBuilder(); while (!tq.isEmpty()) { if (tq.matches("(")) sq.append("(").append(tq.chompBalanced('(', ')')).append(")"); else if (tq.matches("[")) sq.append("[").append(tq.chompBalanced('[', ']')).append("]"); else if (tq.matchesAny(combinators)) break; else sq.append(tq.consume()); } return sq.toString(); }
/** * Parse the query * @return Evaluator */ Evaluator parse() { tq.consumeWhitespace(); if (tq.matchesAny(combinators)) { // if starts with a combinator, use root as elements evals.add(new StructuralEvaluator.Root()); combinator(tq.consume()); } else { findElements(); } while (!tq.isEmpty()) { // hierarchy and extras boolean seenWhite = tq.consumeWhitespace(); if (tq.matchesAny(combinators)) { combinator(tq.consume()); } else if (seenWhite) { combinator(' '); } else { // E.class, E#id, E[attr] etc. AND findElements(); // take next el, #. etc off queue } } if (evals.size() == 1) return evals.get(0); return new CombiningEvaluator.And(evals); }
private void parseEndTag() { tq.consume("</"); String tagName = tq.consumeTagName(); tq.chompTo(">"); if (!Strings.empty(tagName)) { Tag tag = Tag.valueOf(tagName); popStackToClose(tag); } }
public String consumeToIgnoreCase(String seq) { int start = pos; String first = seq.substring(0, 1); boolean canScan = first.toLowerCase().equals(first.toUpperCase()); // if first is not cased, use index of while (!isEmpty()) { if (matches(seq)) break; if (canScan) { int skip = queue.indexOf(first, pos) - pos; if (skip == 0) // this char is the skip char, but not match, so force advance of pos pos++; else if (skip < 0) // no chance of finding, grab to end pos = queue.length(); else pos += skip; } else pos++; } return queue.substring(start, pos); }
private void findElements() { if (tq.matchChomp("#")) byId(); else if (tq.matchChomp(".")) byClass(); else if (tq.matchesWord() || tq.matches("*|")) byTag(); else if (tq.matches("[")) byAttribute(); else if (tq.matchChomp("*")) allElements(); else if (tq.matchChomp(":lt(")) indexLessThan(); else if (tq.matchChomp(":gt(")) indexGreaterThan(); else if (tq.matchChomp(":eq(")) indexEquals(); else if (tq.matches(":has(")) has(); else if (tq.matches(":contains(")) contains(false); else if (tq.matches(":containsOwn(")) contains(true); else if (tq.matches(":containsData(")) containsData(); else if (tq.matches(":matches(")) matches(false); else if (tq.matches(":matchesOwn(")) matches(true); else if (tq.matches(":not("))
private void whitespace() { if (tq.peek() == Character.LINE_SEPARATOR) linecount++; tq.consumeWhitespace(); }
/** Consumes to the first sequence provided, or to the end of the queue. Leaves the terminator on the queue. @param seq any number of terminators to consume to. <b>Case insensitive.</b> @return consumed string */ // todo: method name. not good that consumeTo cares for case, and consume to any doesn't. And the only use for this // is is a case sensitive time... public String consumeToAny(String... seq) { int start = pos; while (!isEmpty() && !matchesAny(seq)) { pos++; } return queue.substring(start, pos); }
private void containsData() { tq.consume(":containsData"); String searchText = TokenQueue.unescape(tq.chompBalanced('(', ')')); Validate.notEmpty(searchText, ":containsData(text) query must not be empty"); evals.add(new Evaluator.ContainsData(searchText)); }
private void matches(boolean own) { tq.consume(own ? ":matchesOwn" : ":matches"); String regex = tq.chompBalanced('(', ')'); // don't unescape, as regex bits will be escaped Validate.notEmpty(regex, ":matches(regex) query must not be empty"); if (own) evals.add(new Evaluator.MatchesOwn(Pattern.compile(regex))); else evals.add(new Evaluator.Matches(Pattern.compile(regex))); }
/** * Drops the next character off the queue. */ public void advance() { if (!isEmpty()) pos++; }