gt:         return queue.substring(start, pos);
context: package org.grails.plugins.zkui.jsoup.parser;
  
  
  import org.grails.plugins.zkui.jsoup.helper.Validate;
  
  /**
   * A character queue with parsing helpers.
   *
   * @author Jonathan Hedley
   */
  public class TokenQueue {
      private String queue;
      private int pos = 0;
  
      private static final char ESC = '\\'; // escape char for chomp balanced.
  
      /**
       * Create a new TokenQueue.
       *
       * @param data string of data to back queue.
       */
      public TokenQueue(String data) {
          Validate.notNull(data);
          queue = data;
      }
  
      /**
       * Is the queue empty?
       *
       * @return true if no data left in queue.
       */
      public boolean isEmpty() {
          return remainingLength() == 0;
      }
  
      private int remainingLength() {
          return queue.length() - pos;
      }
  
      /**
       * Retrieves but does not remove the first character from the queue.
       *
       * @return First character, or 0 if empty.
       */
      public char peek() {
          return isEmpty() ? 0 : queue.charAt(pos);
      }
  
      /**
       * 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());
      }
  
      /**
       * Add a string to the start of the queue.
       *
       * @param seq string to add.
       */
      public void addFirst(String seq) {
          // not very performant, but an edge case
          queue = seq + queue.substring(pos);
          pos = 0;
      }
  
      /**
       * Tests if the next characters on the queue match the sequence. Case insensitive.
       *
       * @param seq String to check queue for.
       * @return true if the next characters match.
       */
      public boolean matches(String seq) {
          return queue.regionMatches(true, pos, seq, 0, seq.length());
      }
  
      /**
       * Case sensitive match test.
       *
       * @param seq
       * @return
       */
      public boolean matchesCS(String seq) {
          return queue.startsWith(seq, pos);
      }
  
  
      /**
       * Tests if the next characters match any of the sequences. Case insensitive.
       *
       * @param seq
       * @return
       */
      public boolean matchesAny(String... seq) {
          for (String s : seq) {
              if (matches(s))
                  return true;
          }
          return false;
      }
  
      public boolean matchesAny(char... seq) {
          if (isEmpty())
              return false;
  
          for (char c : seq) {
              if (queue.charAt(pos) == c)
                  return true;
          }
          return false;
      }
  
      public boolean matchesStartTag() {
          // micro opt for matching "<x"
          return (remainingLength() >= 2 && queue.charAt(pos) == '<' && Character.isLetter(queue.charAt(pos + 1)));
      }
  
      /**
       * Tests if the queue matches the sequence (as with match), and if they do, removes the matched string from the
       * queue.
       *
       * @param seq String to search for, and if found, remove from queue.
       * @return true if found and removed, false if not found.
       */
      public boolean matchChomp(String seq) {
          if (matches(seq)) {
              pos += seq.length();
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * Tests if queue starts with a whitespace character.
       *
       * @return if starts with whitespace
       */
      public boolean matchesWhitespace() {
          return !isEmpty() && Character.isWhitespace(queue.charAt(pos));
      }
  
      /**
       * Test if the queue matches a word character (letter or digit).
       *
       * @return if matches a word character
       */
      public boolean matchesWord() {
          return !isEmpty() && Character.isLetterOrDigit(queue.charAt(pos));
      }
  
      /**
       * Drops the next character off the queue.
       */
      public void advance() {
          if (!isEmpty()) pos++;
      }
  
      /**
       * Consume one character off queue.
       *
       * @return first character on queue.
       */
      public char consume() {
          return queue.charAt(pos++);
      }
  
      /**
       * Consumes the supplied sequence of the queue. If the queue does not start with the supplied sequence, will
       * throw an illegal state exception -- but you should be running match() against that condition.
       * <p/>
       * Case insensitive.
       *
       * @param seq sequence to remove from head of queue.
       */
      public void consume(String seq) {
          if (!matches(seq))
              throw new IllegalStateException("Queue did not match expected sequence");
          int len = seq.length();
          if (len > remainingLength())
              throw new IllegalStateException("Queue not long enough to consume sequence");
  
          pos += len;
      }
  
      /**
       * Pulls a string off the queue, up to but exclusive of the match sequence, or to the queue running out.
       *
       * @param seq String to end on (and not include in return, but leave on queue). <b>Case sensitive.</b>
       * @return The matched data consumed from queue.
       */
      public String consumeTo(String seq) {
          int offset = queue.indexOf(seq, pos);
          if (offset != -1) {
              String consumed = queue.substring(pos, offset);
              pos += consumed.length();
              return consumed;
          } else {
              return remainder();
          }
      }
  
      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++;
          }
  
          String data = queue.substring(start, pos);
          return data;
      }
  
      /**
       * 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++;
          }
  
          String data = queue.substring(start, pos);
          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;
      }
  
      public String chompToIgnoreCase(String seq) {
          String data = consumeToIgnoreCase(seq); // case insensitive scan
          matchChomp(seq);
          return data;
      }
  
      /**
       * Pulls a balanced string off the queue. E.g. if queue is "(one (two) three) four", (,) will return "one (two) three",
       * and leave " four" on the queue. Unbalanced openers and closers can be escaped (with \). Those escapes will be left
       * in the returned string, which is suitable for regexes (where we need to preserve the escape), but unsuitable for
       * contains text strings; use unescape for that.
       *
       * @param open  opener
       * @param close closer
       * @return data matched from the queue
       */
      public String chompBalanced(char open, char close) {
          StringBuilder accum = new StringBuilder();
          int depth = 0;
          char last = 0;
  
          do {
              if (isEmpty()) break;
              Character c = consume();
              if (last == 0 || last != ESC) {
                  if (c.equals(open))
                      depth++;
                  else if (c.equals(close))
                      depth--;
              }
  
              if (depth > 0 && last != 0)
                  accum.append(c); // don't include the outer match pair in the return
              last = c;
          } while (depth > 0);
          return accum.toString();
      }
  
      /**
       * Unescaped a \ escaped string.
       *
       * @param in backslash escaped string
       * @return unescaped string
       */
      public static String unescape(String in) {
          StringBuilder out = new StringBuilder();
          char last = 0;
          for (char c : in.toCharArray()) {
              if (c == ESC) {
                  if (last != 0 && last == ESC)
                      out.append(c);
              } else
                  out.append(c);
              last = c;
          }
          return out.toString();
      }
  
      /**
       * Pulls the next run of whitespace characters of the queue.
       */
      public boolean consumeWhitespace() {
          boolean seen = false;
          while (matchesWhitespace()) {
              pos++;
              seen = true;
          }
          return seen;
      }
  
      /**
       * Retrieves the next run of word type (letter or digit) off the queue.
       *
       * @return String of word characters from queue, or empty string if none.
       */
      public String consumeWord() {
          int start = pos;
          while (matchesWord())
              pos++;
          return queue.substring(start, pos);
      }
  
      /**
       * Consume an tag name off the queue (word or :, _, -)
       *
       * @return tag name
       */
      public String consumeTagName() {
          int start = pos;
          while (!isEmpty() && (matchesWord() || matchesAny(':', '_', '-')))
              pos++;
  
          return queue.substring(start, pos);
      }
  
      /**
       * Consume a CSS element selector (tag name, but | instead of : for namespaces, to not conflict with :pseudo selects).
       *
       * @return tag name
       */
      public String consumeElementSelector() {
          int start = pos;
          while (!isEmpty() && (matchesWord() || matchesAny('|', '_', '-')))
              pos++;
  
  