// This file is (approximately) the concatenation of all the source // files in the src/gnu/regexp directory of GNU Regexp, version 1.1.4 // Although doing this is a huge hack, it simplifies the deployment of // applications that depend on org.ibex.js. package org.ibex.js; import java.util.*; import java.io.*; import java.text.*; public class GnuRegexp { /* * gnu/regexp/CharIndexed.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * Defines the interface used internally so that different types of source * text can be accessed in the same way. Built-in concrete classes provide * support for String, StringBuffer, InputStream and char[] types. * A class that is CharIndexed supports the notion of a cursor within a * block of text. The cursor must be able to be advanced via the move() * method. The charAt() method returns the character at the cursor position * plus a given offset. * * @author Wes Biggs */ public static interface CharIndexed { /** * Defines a constant (0xFFFF was somewhat arbitrarily chosen) * that can be returned by the charAt() function indicating that * the specified index is out of range. */ char OUT_OF_BOUNDS = '\uFFFF'; /** * Returns the character at the given offset past the current cursor * position in the input. The index of the current position is zero. * It is possible for this method to be called with a negative index. * This happens when using the '^' operator in multiline matching mode * or the '\b' or '\<' word boundary operators. In any case, the lower * bound is currently fixed at -2 (for '^' with a two-character newline). * * @param index the offset position in the character field to examine * @return the character at the specified index, or the OUT_OF_BOUNDS * character defined by this interface. */ char charAt(int index); /** * Shifts the input buffer by a given number of positions. Returns * true if the new cursor position is valid. */ boolean move(int index); /** * Returns true if the most recent move() operation placed the cursor * position at a valid position in the input. */ boolean isValid(); } /* * gnu/regexp/CharIndexedCharArray.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ static class CharIndexedCharArray implements CharIndexed, Serializable { private char[] s; private int anchor; CharIndexedCharArray(char[] str, int index) { s = str; anchor = index; } public char charAt(int index) { int pos = anchor + index; return ((pos < s.length) && (pos >= 0)) ? s[pos] : OUT_OF_BOUNDS; } public boolean isValid() { return (anchor < s.length); } public boolean move(int index) { return ((anchor += index) < s.length); } } /* * gnu/regexp/CharIndexedReader.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ // TODO: move(x) shouldn't rely on calling next() x times static class CharIndexedInputStream implements CharIndexed { private static final int BUFFER_INCREMENT = 1024; private static final int UNKNOWN = Integer.MAX_VALUE; // value for end private BufferedInputStream br; // so that we don't try to reset() right away private int index = -1; private int bufsize = BUFFER_INCREMENT; private int end = UNKNOWN; private char cached = OUT_OF_BOUNDS; // Big enough for a \r\n pair // lookBehind[0] = most recent // lookBehind[1] = second most recent private char[] lookBehind = new char[] { OUT_OF_BOUNDS, OUT_OF_BOUNDS }; CharIndexedInputStream(InputStream str, int index) { if (str instanceof BufferedInputStream) br = (BufferedInputStream) str; else br = new BufferedInputStream(str,BUFFER_INCREMENT); next(); if (index > 0) move(index); } private boolean next() { if (end == 1) return false; end--; // closer to end try { if (index != -1) { br.reset(); } int i = br.read(); br.mark(bufsize); if (i == -1) { end = 1; cached = OUT_OF_BOUNDS; return false; } cached = (char) i; index = 1; } catch (IOException e) { e.printStackTrace(); cached = OUT_OF_BOUNDS; return false; } return true; } public char charAt(int index) { if (index == 0) { return cached; } else if (index >= end) { return OUT_OF_BOUNDS; } else if (index == -1) { return lookBehind[0]; } else if (index == -2) { return lookBehind[1]; } else if (index < -2) { return OUT_OF_BOUNDS; } else if (index >= bufsize) { // Allocate more space in the buffer. try { while (bufsize <= index) bufsize += BUFFER_INCREMENT; br.reset(); br.mark(bufsize); br.skip(index-1); } catch (IOException e) { } } else if (this.index != index) { try { br.reset(); br.skip(index-1); } catch (IOException e) { } } char ch = OUT_OF_BOUNDS; try { int i = br.read(); this.index = index+1; // this.index is index of next pos relative to charAt(0) if (i == -1) { // set flag that next should fail next time? end = index; return ch; } ch = (char) i; } catch (IOException ie) { } return ch; } public boolean move(int index) { // move read position [index] clicks from 'charAt(0)' boolean retval = true; while (retval && (index-- > 0)) retval = next(); return retval; } public boolean isValid() { return (cached != OUT_OF_BOUNDS); } } /* * gnu/regexp/CharIndexedReader.java * Copyright (C) 2001 Lee Sau Dan * Based on gnu.regexp.CharIndexedInputStream by Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ // TODO: move(x) shouldn't rely on calling next() x times static class CharIndexedReader implements CharIndexed { private static final int BUFFER_INCREMENT = 1024; private static final int UNKNOWN = Integer.MAX_VALUE; // value for end private final BufferedReader br; // so that we don't try to reset() right away private int index = -1; private int bufsize = BUFFER_INCREMENT; private int end = UNKNOWN; private char cached = OUT_OF_BOUNDS; // Big enough for a \r\n pair // lookBehind[0] = most recent // lookBehind[1] = second most recent private char[] lookBehind = new char[] { OUT_OF_BOUNDS, OUT_OF_BOUNDS }; CharIndexedReader(Reader reader, int index) { if (reader instanceof BufferedReader) { br = (BufferedReader) reader; } else { br = new BufferedReader(reader,BUFFER_INCREMENT); } next(); if (index > 0) move(index); } private boolean next() { lookBehind[1] = lookBehind[0]; lookBehind[0] = cached; if (end == 1) { cached = OUT_OF_BOUNDS; return false; } end--; // closer to end try { if (index != -1) { br.reset(); } int i = br.read(); br.mark(bufsize); if (i == -1) { end = 1; cached = OUT_OF_BOUNDS; return false; } // convert the byte read into a char cached = (char) i; index = 1; } catch (IOException e) { e.printStackTrace(); cached = OUT_OF_BOUNDS; return false; } return true; } public char charAt(int index) { if (index == 0) { return cached; } else if (index >= end) { return OUT_OF_BOUNDS; } else if (index >= bufsize) { // Allocate more space in the buffer. try { while (bufsize <= index) bufsize += BUFFER_INCREMENT; br.reset(); br.mark(bufsize); br.skip(index-1); } catch (IOException e) { } } else if (this.index != index) { try { br.reset(); br.skip(index-1); } catch (IOException e) { } } else if (index == -1) { return lookBehind[0]; } else if (index == -2) { return lookBehind[1]; } else if (index < -2) { return OUT_OF_BOUNDS; } char ch = OUT_OF_BOUNDS; try { int i = br.read(); this.index = index+1; // this.index is index of next pos relative to charAt(0) if (i == -1) { // set flag that next should fail next time? end = index; return ch; } ch = (char) i; } catch (IOException ie) { } return ch; } public boolean move(int index) { // move read position [index] clicks from 'charAt(0)' boolean retval = true; while (retval && (index-- > 0)) retval = next(); return retval; } public boolean isValid() { return (cached != OUT_OF_BOUNDS); } } /* * gnu/regexp/CharIndexedString.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ static class CharIndexedString implements CharIndexed, Serializable { private String s; private int anchor; private int len; CharIndexedString(String str, int index) { s = str; len = s.length(); anchor = index; } public char charAt(int index) { int pos = anchor + index; return ((pos < len) && (pos >= 0)) ? s.charAt(pos) : OUT_OF_BOUNDS; } public boolean isValid() { return (anchor < len); } public boolean move(int index) { return ((anchor += index) < len); } } /* * gnu/regexp/CharIndexedStringBuffer.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ static class CharIndexedStringBuffer implements CharIndexed, Serializable { private StringBuffer s; private int anchor; CharIndexedStringBuffer(StringBuffer str, int index) { s = str; anchor = index; } public char charAt(int index) { int pos = anchor + index; return ((pos < s.length()) && (pos >= 0)) ? s.charAt(pos) : OUT_OF_BOUNDS; } public boolean isValid() { return (anchor < s.length()); } public boolean move(int index) { return ((anchor += index) < s.length()); } } /* * gnu/regexp/RE.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ static class IntPair implements Serializable { public int first, second; } static class CharUnit implements Serializable { public char ch; public boolean bk; } /** * RE provides the user interface for compiling and matching regular * expressions. *

* A regular expression object (class RE) is compiled by constructing it * from a String, StringBuffer or character array, with optional * compilation flags (below) * and an optional syntax specification (see RESyntax; if not specified, * RESyntax.RE_SYNTAX_PERL5 is used). *

* Various methods attempt to match input text against a compiled * regular expression. These methods are: *

  • isMatch: returns true if the input text in its entirety * matches the regular expression pattern. *
  • getMatch: returns the first match found in the input text, * or null if no match is found. *
  • getAllMatches: returns an array of all non-overlapping * matches found in the input text. If no matches are found, the array is * zero-length. *
  • substitute: substitute the first occurence of the pattern * in the input text with a replacement string (which may include * metacharacters $0-$9, see REMatch.substituteInto). *
  • substituteAll: same as above, but repeat for each match * before returning. *
  • getMatchEnumeration: returns an REMatchEnumeration object * that allows iteration over the matches (see REMatchEnumeration for some * reasons why you may want to do this instead of using getAllMatches. *

    * * These methods all have similar argument lists. The input can be a * String, a character array, a StringBuffer, a Reader or an * InputStream of some sort. Note that when using a Reader or * InputStream, the stream read position cannot be guaranteed after * attempting a match (this is not a bug, but a consequence of the way * regular expressions work). Using an REMatchEnumeration can * eliminate most positioning problems. * *

    * * The optional index argument specifies the offset from the beginning * of the text at which the search should start (see the descriptions * of some of the execution flags for how this can affect positional * pattern operators). For a Reader or InputStream, this means an * offset from the current read position, so subsequent calls with the * same index argument on a Reader or an InputStream will not * necessarily access the same position on the stream, whereas * repeated searches at a given index in a fixed string will return * consistent results. * *

    * You can optionally affect the execution environment by using a * combination of execution flags (constants listed below). * *

    * All operations on a regular expression are performed in a * thread-safe manner. * * @author Wes Biggs * @version 1.1.4-dev, to be released */ public static class RE extends REToken { // This String will be returned by getVersion() private static final String VERSION = "1.1.4-dev"; // The localized strings are kept in a separate file private static ResourceBundle messages = new ListResourceBundle() { public Object[][] getContents() { return stuff; } private Object[][] stuff = new Object[][] { { "error.prefix", "At position {0} in regular expression pattern:" }, { "repeat.assertion", "repeated token is zero-width assertion" }, { "repeat.chained", "attempted to repeat a token that is already repeated" }, { "repeat.no.token", "quantifier (?*+{}) without preceding token" }, { "repeat.empty.token", "repeated token may be empty" }, { "unmatched.brace", "unmatched brace" }, { "unmatched.bracket", "unmatched bracket" }, { "unmatched.paren", "unmatched parenthesis" }, { "interval.no.end", "expected end of interval" }, { "class.no.end", "expected end of character class" }, { "subexpr.no.end", "expected end of subexpression" }, { "interval.order", "interval minimum is greater than maximum" }, { "interval.error", "interval is empty or contains illegal chracters" }, { "ends.with.backslash", "backslash at end of pattern" }, { "syntax.final", "Syntax has been declared final and cannot be modified" } }; }; // These are, respectively, the first and last tokens in our linked list // If there is only one token, firstToken == lastToken private REToken firstToken, lastToken; // This is the number of subexpressions in this regular expression, // with a minimum value of zero. Returned by getNumSubs() private int numSubs; /** Minimum length, in characters, of any possible match. */ private int minimumLength; /** * Compilation flag. Do not differentiate case. Subsequent * searches using this RE will be case insensitive. */ public static final int REG_ICASE = 2; /** * Compilation flag. The match-any-character operator (dot) * will match a newline character. When set this overrides the syntax * bit RE_DOT_NEWLINE (see RESyntax for details). This is equivalent to * the "/s" operator in Perl. */ public static final int REG_DOT_NEWLINE = 4; /** * Compilation flag. Use multiline mode. In this mode, the ^ and $ * anchors will match based on newlines within the input. This is * equivalent to the "/m" operator in Perl. */ public static final int REG_MULTILINE = 8; /** * Execution flag. * The match-beginning operator (^) will not match at the beginning * of the input string. Useful for matching on a substring when you * know the context of the input is such that position zero of the * input to the match test is not actually position zero of the text. *

    * This example demonstrates the results of various ways of matching on * a substring. *

    * * String s = "food bar fool";
    * RE exp = new RE("^foo.");
    * REMatch m0 = exp.getMatch(s);
    * REMatch m1 = exp.getMatch(s.substring(8));
    * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL);
    * REMatch m3 = exp.getMatch(s,8);
    * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX);
    *

    * // Results:
    * // m0 = "food"
    * // m1 = "fool"
    * // m2 = null
    * // m3 = null
    * // m4 = "fool"
    *
    */ public static final int REG_NOTBOL = 16; /** * Execution flag. * The match-end operator ($) does not match at the end * of the input string. Useful for matching on substrings. */ public static final int REG_NOTEOL = 32; /** * Execution flag. * When a match method is invoked that starts matching at a non-zero * index into the input, treat the input as if it begins at the index * given. The effect of this flag is that the engine does not "see" * any text in the input before the given index. This is useful so * that the match-beginning operator (^) matches not at position 0 * in the input string, but at the position the search started at * (based on the index input given to the getMatch function). See * the example under REG_NOTBOL. It also affects the use of the \< * and \b operators. */ public static final int REG_ANCHORINDEX = 64; /** * Execution flag. * The substitute and substituteAll methods will not attempt to * interpolate occurrences of $1-$9 in the replacement text with * the corresponding subexpressions. For example, you may want to * replace all matches of "one dollar" with "$1". */ public static final int REG_NO_INTERPOLATE = 128; /** Returns a string representing the version of the gnu.regexp package. */ public static final String version() { return VERSION; } // Retrieves a message from the ResourceBundle static final String getLocalizedMessage(String key) { return messages.getString(key); } /** * Constructs a regular expression pattern buffer without any compilation * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). * * @param pattern A regular expression pattern, in the form of a String, * StringBuffer or char[]. Other input types will be converted to * strings using the toString() method. * @exception REException The input pattern could not be parsed. * @exception NullPointerException The pattern was null. */ public RE(Object pattern) throws REException { this(pattern,0,RESyntax.RE_SYNTAX_PERL5,0,0); } /** * Constructs a regular expression pattern buffer using the specified * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). * * @param pattern A regular expression pattern, in the form of a String, * StringBuffer, or char[]. Other input types will be converted to * strings using the toString() method. * @param cflags The logical OR of any combination of the compilation flags listed above. * @exception REException The input pattern could not be parsed. * @exception NullPointerException The pattern was null. */ public RE(Object pattern, int cflags) throws REException { this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5,0,0); } /** * Constructs a regular expression pattern buffer using the specified * compilation flags and regular expression syntax. * * @param pattern A regular expression pattern, in the form of a String, * StringBuffer, or char[]. Other input types will be converted to * strings using the toString() method. * @param cflags The logical OR of any combination of the compilation flags listed above. * @param syntax The type of regular expression syntax to use. * @exception REException The input pattern could not be parsed. * @exception NullPointerException The pattern was null. */ public RE(Object pattern, int cflags, RESyntax syntax) throws REException { this(pattern,cflags,syntax,0,0); } // internal constructor used for alternation private RE(REToken first, REToken last,int subs, int subIndex, int minLength) { super(subIndex); firstToken = first; lastToken = last; numSubs = subs; minimumLength = minLength; addToken(new RETokenEndSub(subIndex)); } private RE(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException { super(myIndex); // Subexpression index of this token. initialize(patternObj, cflags, syntax, myIndex, nextSub); } // For use by subclasses protected RE() { super(0); } // The meat of construction protected void initialize(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException { char[] pattern; if (patternObj instanceof String) { pattern = ((String) patternObj).toCharArray(); } else if (patternObj instanceof char[]) { pattern = (char[]) patternObj; } else if (patternObj instanceof StringBuffer) { pattern = new char [((StringBuffer) patternObj).length()]; ((StringBuffer) patternObj).getChars(0,pattern.length,pattern,0); } else { pattern = patternObj.toString().toCharArray(); } int pLength = pattern.length; numSubs = 0; // Number of subexpressions in this token. Vector branches = null; // linked list of tokens (sort of -- some closed loops can exist) firstToken = lastToken = null; // Precalculate these so we don't pay for the math every time we // need to access them. boolean insens = ((cflags & REG_ICASE) > 0); // Parse pattern into tokens. Does anyone know if it's more efficient // to use char[] than a String.charAt()? I'm assuming so. // index tracks the position in the char array int index = 0; // this will be the current parse character (pattern[index]) CharUnit unit = new CharUnit(); // This is used for {x,y} calculations IntPair minMax = new IntPair(); // Buffer a token so we can create a TokenRepeated, etc. REToken currentToken = null; char ch; while (index < pLength) { // read the next character unit (including backslash escapes) index = getCharUnit(pattern,index,unit); // ALTERNATION OPERATOR // \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT) // not available if RE_LIMITED_OPS is set // TODO: the '\n' literal here should be a test against REToken.newline, // which unfortunately may be more than a single character. if ( ( (unit.ch == '|' && (syntax.get(RESyntax.RE_NO_BK_VBAR) ^ unit.bk)) || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !unit.bk) ) && !syntax.get(RESyntax.RE_LIMITED_OPS)) { // make everything up to here be a branch. create vector if nec. addToken(currentToken); RE theBranch = new RE(firstToken, lastToken, numSubs, subIndex, minimumLength); minimumLength = 0; if (branches == null) { branches = new Vector(); } branches.addElement(theBranch); firstToken = lastToken = currentToken = null; } // INTERVAL OPERATOR: // {x} | {x,} | {x,y} (RE_INTERVALS && RE_NO_BK_BRACES) // \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES) // // OPEN QUESTION: // what is proper interpretation of '{' at start of string? else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) { int newIndex = getMinMax(pattern,index,minMax,syntax); if (newIndex > index) { if (minMax.first > minMax.second) throw new REException(getLocalizedMessage("interval.order"),REException.REG_BADRPT,newIndex); if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,newIndex); if (currentToken instanceof RETokenRepeated) throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,newIndex); if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,newIndex); if ((currentToken.getMinimumLength() == 0) && (minMax.second == Integer.MAX_VALUE)) throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,newIndex); index = newIndex; currentToken = setRepeated(currentToken,minMax.first,minMax.second,index); } else { addToken(currentToken); currentToken = new RETokenChar(subIndex,unit.ch,insens); } } // LIST OPERATOR: // [...] | [^...] else if ((unit.ch == '[') && !unit.bk) { Vector options = new Vector(); boolean negative = false; char lastChar = 0; if (index == pLength) throw new REException(getLocalizedMessage("unmatched.bracket"),REException.REG_EBRACK,index); // Check for initial caret, negation if ((ch = pattern[index]) == '^') { negative = true; if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); ch = pattern[index]; } // Check for leading right bracket literal if (ch == ']') { lastChar = ch; if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); } while ((ch = pattern[index++]) != ']') { if ((ch == '-') && (lastChar != 0)) { if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); if ((ch = pattern[index]) == ']') { options.addElement(new RETokenChar(subIndex,lastChar,insens)); lastChar = '-'; } else { options.addElement(new RETokenRange(subIndex,lastChar,ch,insens)); lastChar = 0; index++; } } else if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) { if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); int posixID = -1; boolean negate = false; char asciiEsc = 0; if (("dswDSW".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) { switch (pattern[index]) { case 'D': negate = true; case 'd': posixID = RETokenPOSIX.DIGIT; break; case 'S': negate = true; case 's': posixID = RETokenPOSIX.SPACE; break; case 'W': negate = true; case 'w': posixID = RETokenPOSIX.ALNUM; break; } } else if ("nrt".indexOf(pattern[index]) != -1) { switch (pattern[index]) { case 'n': asciiEsc = '\n'; break; case 't': asciiEsc = '\t'; break; case 'r': asciiEsc = '\r'; break; } } if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens)); if (posixID != -1) { options.addElement(new RETokenPOSIX(subIndex,posixID,insens,negate)); } else if (asciiEsc != 0) { lastChar = asciiEsc; } else { lastChar = pattern[index]; } ++index; } else if ((ch == '[') && (syntax.get(RESyntax.RE_CHAR_CLASSES)) && (index < pLength) && (pattern[index] == ':')) { StringBuffer posixSet = new StringBuffer(); index = getPosixSet(pattern,index+1,posixSet); int posixId = RETokenPOSIX.intValue(posixSet.toString()); if (posixId != -1) options.addElement(new RETokenPOSIX(subIndex,posixId,insens,false)); } else { if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens)); lastChar = ch; } if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); } // while in list // Out of list, index is one past ']' if (lastChar != 0) options.addElement(new RETokenChar(subIndex,lastChar,insens)); // Create a new RETokenOneOf addToken(currentToken); options.trimToSize(); currentToken = new RETokenOneOf(subIndex,options,negative); } // SUBEXPRESSIONS // (...) | \(...\) depending on RE_NO_BK_PARENS else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) { boolean pure = false; boolean comment = false; boolean lookAhead = false; boolean negativelh = false; if ((index+1 < pLength) && (pattern[index] == '?')) { switch (pattern[index+1]) { case '!': if (syntax.get(RESyntax.RE_LOOKAHEAD)) { pure = true; negativelh = true; lookAhead = true; index += 2; } break; case '=': if (syntax.get(RESyntax.RE_LOOKAHEAD)) { pure = true; lookAhead = true; index += 2; } break; case ':': if (syntax.get(RESyntax.RE_PURE_GROUPING)) { pure = true; index += 2; } break; case '#': if (syntax.get(RESyntax.RE_COMMENTS)) { comment = true; } break; default: throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index); } } if (index >= pLength) { throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index); } // find end of subexpression int endIndex = index; int nextIndex = index; int nested = 0; while ( ((nextIndex = getCharUnit(pattern,endIndex,unit)) > 0) && !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) ) if ((endIndex = nextIndex) >= pLength) throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) nested++; else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) nested--; // endIndex is now position at a ')','\)' // nextIndex is end of string or position after ')' or '\)' if (comment) index = nextIndex; else { // not a comment // create RE subexpression as token. addToken(currentToken); if (!pure) { numSubs++; } int useIndex = (pure || lookAhead) ? 0 : nextSub + numSubs; currentToken = new RE(String.valueOf(pattern,index,endIndex-index).toCharArray(),cflags,syntax,useIndex,nextSub + numSubs); numSubs += ((RE) currentToken).getNumSubs(); if (lookAhead) { currentToken = new RETokenLookAhead(currentToken,negativelh); } index = nextIndex; } // not a comment } // subexpression // UNMATCHED RIGHT PAREN // ) or \) throw exception if // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) else if (!syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) && ((unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))) { throw new REException(getLocalizedMessage("unmatched.paren"),REException.REG_EPAREN,index); } // START OF LINE OPERATOR // ^ else if ((unit.ch == '^') && !unit.bk) { addToken(currentToken); currentToken = null; addToken(new RETokenStart(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null)); } // END OF LINE OPERATOR // $ else if ((unit.ch == '$') && !unit.bk) { addToken(currentToken); currentToken = null; addToken(new RETokenEnd(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null)); } // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null) // . else if ((unit.ch == '.') && !unit.bk) { addToken(currentToken); currentToken = new RETokenAny(subIndex,syntax.get(RESyntax.RE_DOT_NEWLINE) || ((cflags & REG_DOT_NEWLINE) > 0),syntax.get(RESyntax.RE_DOT_NOT_NULL)); } // ZERO-OR-MORE REPEAT OPERATOR // * else if ((unit.ch == '*') && !unit.bk) { if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); if (currentToken instanceof RETokenRepeated) throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); if (currentToken.getMinimumLength() == 0) throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,index); currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index); } // ONE-OR-MORE REPEAT OPERATOR // + | \+ depending on RE_BK_PLUS_QM // not available if RE_LIMITED_OPS is set else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) { if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); if (currentToken instanceof RETokenRepeated) throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); if (currentToken.getMinimumLength() == 0) throw new REException(getLocalizedMessage("repeat.empty.token"),REException.REG_BADRPT,index); currentToken = setRepeated(currentToken,1,Integer.MAX_VALUE,index); } // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR // ? | \? depending on RE_BK_PLUS_QM // not available if RE_LIMITED_OPS is set // stingy matching if RE_STINGY_OPS is set and it follows a quantifier else if ((unit.ch == '?') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) { if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); // Check for stingy matching on RETokenRepeated if (currentToken instanceof RETokenRepeated) { if (syntax.get(RESyntax.RE_STINGY_OPS) && !((RETokenRepeated)currentToken).isStingy()) ((RETokenRepeated)currentToken).makeStingy(); else throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); } else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); else currentToken = setRepeated(currentToken,0,1,index); } // BACKREFERENCE OPERATOR // \1 \2 ... \9 // not available if RE_NO_BK_REFS is set else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) { addToken(currentToken); currentToken = new RETokenBackRef(subIndex,Character.digit(unit.ch,10),insens); } // START OF STRING OPERATOR // \A if RE_STRING_ANCHORS is set else if (unit.bk && (unit.ch == 'A') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { addToken(currentToken); currentToken = new RETokenStart(subIndex,null); } // WORD BREAK OPERATOR // \b if ???? else if (unit.bk && (unit.ch == 'b') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { addToken(currentToken); currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, false); } // WORD BEGIN OPERATOR // \< if ???? else if (unit.bk && (unit.ch == '<')) { addToken(currentToken); currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN, false); } // WORD END OPERATOR // \> if ???? else if (unit.bk && (unit.ch == '>')) { addToken(currentToken); currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.END, false); } // NON-WORD BREAK OPERATOR // \B if ???? else if (unit.bk && (unit.ch == 'B') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { addToken(currentToken); currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, true); } // DIGIT OPERATOR // \d if RE_CHAR_CLASS_ESCAPES is set else if (unit.bk && (unit.ch == 'd') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { addToken(currentToken); currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,false); } // NON-DIGIT OPERATOR // \D else if (unit.bk && (unit.ch == 'D') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { addToken(currentToken); currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,true); } // NEWLINE ESCAPE // \n else if (unit.bk && (unit.ch == 'n')) { addToken(currentToken); currentToken = new RETokenChar(subIndex,'\n',false); } // RETURN ESCAPE // \r else if (unit.bk && (unit.ch == 'r')) { addToken(currentToken); currentToken = new RETokenChar(subIndex,'\r',false); } // WHITESPACE OPERATOR // \s if RE_CHAR_CLASS_ESCAPES is set else if (unit.bk && (unit.ch == 's') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { addToken(currentToken); currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,false); } // NON-WHITESPACE OPERATOR // \S else if (unit.bk && (unit.ch == 'S') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { addToken(currentToken); currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,true); } // TAB ESCAPE // \t else if (unit.bk && (unit.ch == 't')) { addToken(currentToken); currentToken = new RETokenChar(subIndex,'\t',false); } // ALPHANUMERIC OPERATOR // \w else if (unit.bk && (unit.ch == 'w') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { addToken(currentToken); currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,false); } // NON-ALPHANUMERIC OPERATOR // \W else if (unit.bk && (unit.ch == 'W') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { addToken(currentToken); currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,true); } // END OF STRING OPERATOR // \Z else if (unit.bk && (unit.ch == 'Z') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { addToken(currentToken); currentToken = new RETokenEnd(subIndex,null); } // NON-SPECIAL CHARACTER (or escape to make literal) // c | \* for example else { // not a special character addToken(currentToken); currentToken = new RETokenChar(subIndex,unit.ch,insens); } } // end while // Add final buffered token and an EndSub marker addToken(currentToken); if (branches != null) { branches.addElement(new RE(firstToken,lastToken,numSubs,subIndex,minimumLength)); branches.trimToSize(); // compact the Vector minimumLength = 0; firstToken = lastToken = null; addToken(new RETokenOneOf(subIndex,branches,false)); } else addToken(new RETokenEndSub(subIndex)); } private static int getCharUnit(char[] input, int index, CharUnit unit) throws REException { unit.ch = input[index++]; if (unit.bk = (unit.ch == '\\')) if (index < input.length) unit.ch = input[index++]; else throw new REException(getLocalizedMessage("ends.with.backslash"),REException.REG_ESCAPE,index); return index; } /** * Checks if the regular expression matches the input in its entirety. * * @param input The input text. */ public boolean isMatch(Object input) { return isMatch(input,0,0); } /** * Checks if the input string, starting from index, is an exact match of * this regular expression. * * @param input The input text. * @param index The offset index at which the search should be begin. */ public boolean isMatch(Object input,int index) { return isMatch(input,index,0); } /** * Checks if the input, starting from index and using the specified * execution flags, is an exact match of this regular expression. * * @param input The input text. * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. */ public boolean isMatch(Object input,int index,int eflags) { return isMatchImpl(makeCharIndexed(input,index),index,eflags); } private boolean isMatchImpl(CharIndexed input, int index, int eflags) { if (firstToken == null) // Trivial case return (input.charAt(0) == CharIndexed.OUT_OF_BOUNDS); REMatch m = new REMatch(numSubs, index, eflags); if (firstToken.match(input, m)) { while (m != null) { if (input.charAt(m.index) == CharIndexed.OUT_OF_BOUNDS) { return true; } m = m.next; } } return false; } /** * Returns the maximum number of subexpressions in this regular expression. * If the expression contains branches, the value returned will be the * maximum subexpressions in any of the branches. */ public int getNumSubs() { return numSubs; } // Overrides REToken.setUncle void setUncle(REToken uncle) { if (lastToken != null) { lastToken.setUncle(uncle); } else super.setUncle(uncle); // to deal with empty subexpressions } // Overrides REToken.chain boolean chain(REToken next) { super.chain(next); setUncle(next); return true; } /** * Returns the minimum number of characters that could possibly * constitute a match of this regular expression. */ public int getMinimumLength() { return minimumLength; } /** * Returns an array of all matches found in the input. * * If the regular expression allows the empty string to match, it will * substitute matches at all positions except the end of the input. * * @param input The input text. * @return a non-null (but possibly zero-length) array of matches */ public REMatch[] getAllMatches(Object input) { return getAllMatches(input,0,0); } /** * Returns an array of all matches found in the input, * beginning at the specified index position. * * If the regular expression allows the empty string to match, it will * substitute matches at all positions except the end of the input. * * @param input The input text. * @param index The offset index at which the search should be begin. * @return a non-null (but possibly zero-length) array of matches */ public REMatch[] getAllMatches(Object input, int index) { return getAllMatches(input,index,0); } /** * Returns an array of all matches found in the input string, * beginning at the specified index position and using the specified * execution flags. * * If the regular expression allows the empty string to match, it will * substitute matches at all positions except the end of the input. * * @param input The input text. * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. * @return a non-null (but possibly zero-length) array of matches */ public REMatch[] getAllMatches(Object input, int index, int eflags) { return getAllMatchesImpl(makeCharIndexed(input,index),index,eflags); } // this has been changed since 1.03 to be non-overlapping matches private REMatch[] getAllMatchesImpl(CharIndexed input, int index, int eflags) { Vector all = new Vector(); REMatch m = null; while ((m = getMatchImpl(input,index,eflags,null)) != null) { all.addElement(m); index = m.getEndIndex(); if (m.end[0] == 0) { // handle pathological case of zero-length match index++; input.move(1); } else { input.move(m.end[0]); } if (!input.isValid()) break; } REMatch[] mset = new REMatch[all.size()]; all.copyInto(mset); return mset; } /* Implements abstract method REToken.match() */ boolean match(CharIndexed input, REMatch mymatch) { if (firstToken == null) return next(input, mymatch); // Note the start of this subexpression mymatch.start[subIndex] = mymatch.index; return firstToken.match(input, mymatch); } /** * Returns the first match found in the input. If no match is found, * null is returned. * * @param input The input text. * @return An REMatch instance referencing the match, or null if none. */ public REMatch getMatch(Object input) { return getMatch(input,0,0); } /** * Returns the first match found in the input, beginning * the search at the specified index. If no match is found, * returns null. * * @param input The input text. * @param index The offset within the text to begin looking for a match. * @return An REMatch instance referencing the match, or null if none. */ public REMatch getMatch(Object input, int index) { return getMatch(input,index,0); } /** * Returns the first match found in the input, beginning * the search at the specified index, and using the specified * execution flags. If no match is found, returns null. * * @param input The input text. * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. * @return An REMatch instance referencing the match, or null if none. */ public REMatch getMatch(Object input, int index, int eflags) { return getMatch(input,index,eflags,null); } /** * Returns the first match found in the input, beginning the search * at the specified index, and using the specified execution flags. * If no match is found, returns null. If a StringBuffer is * provided and is non-null, the contents of the input text from the * index to the beginning of the match (or to the end of the input, * if there is no match) are appended to the StringBuffer. * * @param input The input text. * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. * @param buffer The StringBuffer to save pre-match text in. * @return An REMatch instance referencing the match, or null if none. */ public REMatch getMatch(Object input, int index, int eflags, StringBuffer buffer) { return getMatchImpl(makeCharIndexed(input,index),index,eflags,buffer); } REMatch getMatchImpl(CharIndexed input, int anchor, int eflags, StringBuffer buffer) { // Create a new REMatch to hold results REMatch mymatch = new REMatch(numSubs, anchor, eflags); do { // Optimization: check if anchor + minimumLength > length if (minimumLength == 0 || input.charAt(minimumLength-1) != CharIndexed.OUT_OF_BOUNDS) { if (match(input, mymatch)) { // Find longest match of them all to observe leftmost longest REMatch longest = mymatch; while ((mymatch = mymatch.next) != null) { if (mymatch.index > longest.index) { longest = mymatch; } } longest.end[0] = longest.index; longest.finish(input); return longest; } } mymatch.clear(++anchor); // Append character to buffer if needed if (buffer != null && input.charAt(0) != CharIndexed.OUT_OF_BOUNDS) { buffer.append(input.charAt(0)); } } while (input.move(1)); return null; } /** * Returns an REMatchEnumeration that can be used to iterate over the * matches found in the input text. * * @param input The input text. * @return A non-null REMatchEnumeration instance. */ public REMatchEnumeration getMatchEnumeration(Object input) { return getMatchEnumeration(input,0,0); } /** * Returns an REMatchEnumeration that can be used to iterate over the * matches found in the input text. * * @param input The input text. * @param index The offset index at which the search should be begin. * @return A non-null REMatchEnumeration instance, with its input cursor * set to the index position specified. */ public REMatchEnumeration getMatchEnumeration(Object input, int index) { return getMatchEnumeration(input,index,0); } /** * Returns an REMatchEnumeration that can be used to iterate over the * matches found in the input text. * * @param input The input text. * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. * @return A non-null REMatchEnumeration instance, with its input cursor * set to the index position specified. */ public REMatchEnumeration getMatchEnumeration(Object input, int index, int eflags) { return new REMatchEnumeration(this,makeCharIndexed(input,index),index,eflags); } /** * Substitutes the replacement text for the first match found in the input. * * @param input The input text. * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). * @return A String interpolating the substituted text. * @see REMatch#substituteInto */ public String substitute(Object input,String replace) { return substitute(input,replace,0,0); } /** * Substitutes the replacement text for the first match found in the input * beginning at the specified index position. Specifying an index * effectively causes the regular expression engine to throw away the * specified number of characters. * * @param input The input text. * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). * @param index The offset index at which the search should be begin. * @return A String containing the substring of the input, starting * at the index position, and interpolating the substituted text. * @see REMatch#substituteInto */ public String substitute(Object input,String replace,int index) { return substitute(input,replace,index,0); } /** * Substitutes the replacement text for the first match found in the input * string, beginning at the specified index position and using the * specified execution flags. * * @param input The input text. * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. * @return A String containing the substring of the input, starting * at the index position, and interpolating the substituted text. * @see REMatch#substituteInto */ public String substitute(Object input,String replace,int index,int eflags) { return substituteImpl(makeCharIndexed(input,index),replace,index,eflags); } private String substituteImpl(CharIndexed input,String replace,int index,int eflags) { StringBuffer buffer = new StringBuffer(); REMatch m = getMatchImpl(input,index,eflags,buffer); if (m==null) return buffer.toString(); buffer.append( ((eflags & REG_NO_INTERPOLATE) > 0) ? replace : m.substituteInto(replace) ); if (input.move(m.end[0])) { do { buffer.append(input.charAt(0)); } while (input.move(1)); } return buffer.toString(); } /** * Substitutes the replacement text for each non-overlapping match found * in the input text. * * @param input The input text. * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). * @return A String interpolating the substituted text. * @see REMatch#substituteInto */ public String substituteAll(Object input,String replace) { return substituteAll(input,replace,0,0); } /** * Substitutes the replacement text for each non-overlapping match found * in the input text, starting at the specified index. * * If the regular expression allows the empty string to match, it will * substitute matches at all positions except the end of the input. * * @param input The input text. * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). * @param index The offset index at which the search should be begin. * @return A String containing the substring of the input, starting * at the index position, and interpolating the substituted text. * @see REMatch#substituteInto */ public String substituteAll(Object input,String replace,int index) { return substituteAll(input,replace,index,0); } /** * Substitutes the replacement text for each non-overlapping match found * in the input text, starting at the specified index and using the * specified execution flags. * * @param input The input text. * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). * @param index The offset index at which the search should be begin. * @param eflags The logical OR of any execution flags above. * @return A String containing the substring of the input, starting * at the index position, and interpolating the substituted text. * @see REMatch#substituteInto */ public String substituteAll(Object input,String replace,int index,int eflags) { return substituteAllImpl(makeCharIndexed(input,index),replace,index,eflags); } private String substituteAllImpl(CharIndexed input,String replace,int index,int eflags) { StringBuffer buffer = new StringBuffer(); REMatch m; while ((m = getMatchImpl(input,index,eflags,buffer)) != null) { buffer.append( ((eflags & REG_NO_INTERPOLATE) > 0) ? replace : m.substituteInto(replace) ); index = m.getEndIndex(); if (m.end[0] == 0) { char ch = input.charAt(0); if (ch != CharIndexed.OUT_OF_BOUNDS) buffer.append(ch); input.move(1); } else { input.move(m.end[0]); } if (!input.isValid()) break; } return buffer.toString(); } /* Helper function for constructor */ private void addToken(REToken next) { if (next == null) return; minimumLength += next.getMinimumLength(); if (firstToken == null) { lastToken = firstToken = next; } else { // if chain returns false, it "rejected" the token due to // an optimization, and next was combined with lastToken if (lastToken.chain(next)) { lastToken = next; } } } private static REToken setRepeated(REToken current, int min, int max, int index) throws REException { if (current == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); return new RETokenRepeated(current.subIndex,current,min,max); } private static int getPosixSet(char[] pattern,int index,StringBuffer buf) { // Precondition: pattern[index-1] == ':' // we will return pos of closing ']'. int i; for (i=index; i<(pattern.length-1); i++) { if ((pattern[i] == ':') && (pattern[i+1] == ']')) return i+2; buf.append(pattern[i]); } return index; // didn't match up } private int getMinMax(char[] input,int index,IntPair minMax,RESyntax syntax) throws REException { // Precondition: input[index-1] == '{', minMax != null boolean mustMatch = !syntax.get(RESyntax.RE_NO_BK_BRACES); int startIndex = index; if (index == input.length) { if (mustMatch) throw new REException(getLocalizedMessage("unmatched.brace"),REException.REG_EBRACE,index); else return startIndex; } int min,max=0; CharUnit unit = new CharUnit(); StringBuffer buf = new StringBuffer(); // Read string of digits do { index = getCharUnit(input,index,unit); if (Character.isDigit(unit.ch)) buf.append(unit.ch); } while ((index != input.length) && Character.isDigit(unit.ch)); // Check for {} tomfoolery if (buf.length() == 0) { if (mustMatch) throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); else return startIndex; } min = Integer.parseInt(buf.toString()); if ((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) max = min; else if (index == input.length) if (mustMatch) throw new REException(getLocalizedMessage("interval.no.end"),REException.REG_EBRACE,index); else return startIndex; else if ((unit.ch == ',') && !unit.bk) { buf = new StringBuffer(); // Read string of digits while (((index = getCharUnit(input,index,unit)) != input.length) && Character.isDigit(unit.ch)) buf.append(unit.ch); if (!((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk))) if (mustMatch) throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); else return startIndex; // This is the case of {x,} if (buf.length() == 0) max = Integer.MAX_VALUE; else max = Integer.parseInt(buf.toString()); } else if (mustMatch) throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); else return startIndex; // We know min and max now, and they are valid. minMax.first = min; minMax.second = max; // return the index following the '}' return index; } /** * Return a human readable form of the compiled regular expression, * useful for debugging. */ public String toString() { StringBuffer sb = new StringBuffer(); dump(sb); return sb.toString(); } void dump(StringBuffer os) { os.append('('); if (subIndex == 0) os.append("?:"); if (firstToken != null) firstToken.dumpAll(os); os.append(')'); } // Cast input appropriately or throw exception private static CharIndexed makeCharIndexed(Object input, int index) { // We could let a String fall through to final input, but since // it's the most likely input type, we check it first. if (input instanceof String) return new CharIndexedString((String) input,index); else if (input instanceof char[]) return new CharIndexedCharArray((char[]) input,index); else if (input instanceof StringBuffer) return new CharIndexedStringBuffer((StringBuffer) input,index); else if (input instanceof InputStream) return new CharIndexedInputStream((InputStream) input,index); else if (input instanceof Reader) return new CharIndexedReader((Reader) input, index); else if (input instanceof CharIndexed) return (CharIndexed) input; // do we lose index info? else return new CharIndexedString(input.toString(), index); } } /* * gnu/regexp/REException.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * This is the regular expression exception class. An exception of this type * defines the three attributes: *

      *
    1. A descriptive message of the error. *
    2. An integral type code equivalent to one of the statically * defined symbols listed below. *
    3. The approximate position in the input string where the error * occurred. *
    * * @author Wes Biggs */ public static class REException extends Exception { private int type; private int pos; // Error conditions from GNU regcomp(3) manual /** * Error flag. * Invalid use of repetition operators such as using * `*' as the first character. */ public static final int REG_BADRPT = 1; /** * Error flag. * Invalid use of back reference operator. */ public static final int REG_BADBR = 2; /** * Error flag. * Un-matched brace interval operators. */ public static final int REG_EBRACE = 3; /** * Error flag. * Un-matched bracket list operators. */ public static final int REG_EBRACK = 4; /** * Error flag. * Invalid use of the range operator, eg. the ending * point of the range occurs prior to the starting * point. */ public static final int REG_ERANGE = 5; /** * Error flag. * Unknown character class name. Not implemented. */ public static final int REG_ECTYPE = 6; /** * Error flag. * Un-matched parenthesis group operators. */ public static final int REG_EPAREN = 7; /** * Error flag. * Invalid back reference to a subexpression. */ public static final int REG_ESUBREG = 8; /** * Error flag. * Non specific error. Not implemented. */ public static final int REG_EEND = 9; /** * Error flag. * Invalid escape sequence. Not implemented. */ public static final int REG_ESCAPE = 10; /** * Error flag. * Invalid use of pattern operators such as group or list. */ public static final int REG_BADPAT = 11; /** * Error flag. * Compiled regular expression requires a pattern * buffer larger than 64Kb. Not implemented. */ public static final int REG_ESIZE = 12; /** * Error flag. * The regex routines ran out of memory. Not implemented. */ public static final int REG_ESPACE = 13; REException(String msg, int type, int position) { super(msg); this.type = type; this.pos = position; } /** * Returns the type of the exception, one of the constants listed above. */ public int getType() { return type; } /** * Returns the position, relative to the string or character array being * compiled, where the error occurred. This position is generally the point * where the error was detected, not necessarily the starting index of * a bad subexpression. */ public int getPosition() { return pos; } /** * Reports the descriptive message associated with this exception * as well as its index position in the string or character array * being compiled. */ public String getMessage() { Object[] args = {new Integer(pos)}; StringBuffer sb = new StringBuffer(); String prefix = RE.getLocalizedMessage("error.prefix"); sb.append(MessageFormat.format(prefix, args)); sb.append('\n'); sb.append(super.getMessage()); return sb.toString(); } } /* * gnu/regexp/REFilterInputStream.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * Replaces instances of a given RE found within an InputStream * with replacement text. The replacements are interpolated into the * stream when a match is found. * * @author Wes Biggs * @deprecated This class cannot properly handle all character * encodings. For proper handling, use the REFilterReader * class instead. */ public static class REFilterInputStream extends FilterInputStream { private RE expr; private String replace; private String buffer; private int bufpos; private int offset; private CharIndexedInputStream stream; /** * Creates an REFilterInputStream. When reading from this stream, * occurrences of patterns matching the supplied regular expression * will be replaced with the supplied replacement text (the * metacharacters $0 through $9 may be used to refer to the full * match or subexpression matches). * * @param stream The InputStream to be filtered. * @param expr The regular expression to search for. * @param replace The text pattern to replace matches with. */ public REFilterInputStream(InputStream stream, RE expr, String replace) { super(stream); this.stream = new CharIndexedInputStream(stream,0); this.expr = expr; this.replace = replace; } /** * Reads the next byte from the stream per the general contract of * InputStream.read(). Returns -1 on error or end of stream. */ public int read() { // If we have buffered replace data, use it. if ((buffer != null) && (bufpos < buffer.length())) { return (int) buffer.charAt(bufpos++); } // check if input is at a valid position if (!stream.isValid()) return -1; REMatch mymatch = new REMatch(expr.getNumSubs(),offset,0); if (expr.match(stream, mymatch)) { mymatch.end[0] = mymatch.index; mymatch.finish(stream); stream.move(mymatch.toString().length()); offset += mymatch.toString().length(); buffer = mymatch.substituteInto(replace); bufpos = 1; // This is prone to infinite loops if replace string turns out empty. if (buffer.length() > 0) { return buffer.charAt(0); } } char ch = stream.charAt(0); if (ch == CharIndexed.OUT_OF_BOUNDS) return -1; stream.move(1); offset++; return ch; } /** * Returns false. REFilterInputStream does not support mark() and * reset() methods. */ public boolean markSupported() { return false; } /** Reads from the stream into the provided array. */ public int read(byte[] b, int off, int len) { int i; int ok = 0; while (len-- > 0) { i = read(); if (i == -1) return (ok == 0) ? -1 : ok; b[off++] = (byte) i; ok++; } return ok; } /** Reads from the stream into the provided array. */ public int read(byte[] b) { return read(b,0,b.length); } } /* * gnu/regexp/REFilterReader.java * Copyright (C) 2001 Lee Sau Dan * Based on gnu.regexp.REFilterInputStream by Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * Replaces instances of a given RE with replacement text. * * @author Lee Sau Dan * @since gnu.regexp 1.1.0 */ public static class REFilterReader extends FilterReader { private RE expr; private String replace; private String buffer; private int bufpos; private int offset; private CharIndexedReader stream; /** * Creates an REFilterReader. When reading from this stream, * occurrences of patterns matching the supplied regular expression * will be replaced with the supplied replacement text (the * metacharacters $0 through $9 may be used to refer to the full * match or subexpression matches. * * @param stream The Reader to be filtered. * @param expr The regular expression to search for. * @param replace The text pattern to replace matches with. */ public REFilterReader(Reader stream, RE expr, String replace) { super(stream); this.stream = new CharIndexedReader(stream,0); this.expr = expr; this.replace = replace; } /** * Reads the next character from the stream per the general contract of * Reader.read(). Returns -1 on error or end of stream. */ public int read() { // If we have buffered replace data, use it. if ((buffer != null) && (bufpos < buffer.length())) { return (int) buffer.charAt(bufpos++); } // check if input is at a valid position if (!stream.isValid()) return -1; REMatch mymatch = new REMatch(expr.getNumSubs(),offset,0); if (expr.match(stream,mymatch)) { mymatch.end[0] = mymatch.index; mymatch.finish(stream); stream.move(mymatch.toString().length()); offset += mymatch.toString().length(); buffer = mymatch.substituteInto(replace); bufpos = 1; if (buffer.length() > 0) { return buffer.charAt(0); } } char ch = stream.charAt(0); if (ch == CharIndexed.OUT_OF_BOUNDS) return -1; stream.move(1); offset++; return ch; } /** * Returns false. REFilterReader does not support mark() and * reset() methods. */ public boolean markSupported() { return false; } /** Reads from the stream into the provided array. */ public int read(char[] b, int off, int len) { int i; int ok = 0; while (len-- > 0) { i = read(); if (i == -1) return (ok == 0) ? -1 : ok; b[off++] = (char) i; ok++; } return ok; } /** Reads from the stream into the provided array. */ public int read(char[] b) { return read(b,0,b.length); } } /* * gnu/regexp/REMatch.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * An instance of this class represents a match * completed by a gnu.regexp matching function. It can be used * to obtain relevant information about the location of a match * or submatch. * * @author Wes Biggs */ public static final class REMatch implements Serializable, Cloneable { private String matchedText; // These variables are package scope for fast access within the engine int eflags; // execution flags this match was made using // Offset in source text where match was tried. This is zero-based; // the actual position in the source text is given by (offset + anchor). int offset; // Anchor position refers to the index into the source input // at which the matching operation began. // This is also useful for the ANCHORINDEX option. int anchor; // Package scope; used by RE. int index; // used while matching to mark current match position in input int[] start; // start positions (relative to offset) for each (sub)exp. int[] end; // end positions for the same REMatch next; // other possibility (to avoid having to use arrays) public Object clone() { try { REMatch copy = (REMatch) super.clone(); copy.next = null; copy.start = (int[]) start.clone(); copy.end = (int[]) end.clone(); return copy; } catch (CloneNotSupportedException e) { throw new Error(); // doesn't happen } } void assignFrom(REMatch other) { start = other.start; end = other.end; index = other.index; // need to deep clone? next = other.next; } REMatch(int subs, int anchor, int eflags) { start = new int[subs+1]; end = new int[subs+1]; this.anchor = anchor; this.eflags = eflags; clear(anchor); } void finish(CharIndexed text) { start[0] = 0; StringBuffer sb = new StringBuffer(); int i; for (i = 0; i < end[0]; i++) sb.append(text.charAt(i)); matchedText = sb.toString(); for (i = 0; i < start.length; i++) { // If any subexpressions didn't terminate, they don't count // TODO check if this code ever gets hit if ((start[i] == -1) ^ (end[i] == -1)) { start[i] = -1; end[i] = -1; } } next = null; // cut off alternates } /** Clears the current match and moves the offset to the new index. */ void clear(int index) { offset = index; this.index = 0; for (int i = 0; i < start.length; i++) { start[i] = end[i] = -1; } next = null; // cut off alternates } /** * Returns the string matching the pattern. This makes it convenient * to write code like the following: *

    * * REMatch myMatch = myExpression.getMatch(myString);
    * if (myMatch != null) System.out.println("Regexp found: "+myMatch); *
    */ public String toString() { return matchedText; } /** * Returns the index within the input text where the match in its entirety * began. */ public int getStartIndex() { return offset + start[0]; } /** * Returns the index within the input string where the match in * its entirety ends. The return value is the next position after * the end of the string; therefore, a match created by the * following call: * *

    * REMatch myMatch = myExpression.getMatch(myString); *

    * can be viewed (given that myMatch is not null) by creating *

    * String theMatch = myString.substring(myMatch.getStartIndex(), * myMatch.getEndIndex()); *

    * But you can save yourself that work, since the toString() * method (above) does exactly that for you. */ public int getEndIndex() { return offset + end[0]; } /** * Returns the string matching the given subexpression. The subexpressions * are indexed starting with one, not zero. That is, the subexpression * identified by the first set of parentheses in a regular expression * could be retrieved from an REMatch by calling match.toString(1). * * @param sub Index of the subexpression. */ public String toString(int sub) { if ((sub >= start.length) || (start[sub] == -1)) return ""; return (matchedText.substring(start[sub],end[sub])); } /** * Returns the index within the input string used to generate this match * where subexpression number sub begins, or -1 if * the subexpression does not exist. The initial position is zero. * * @param sub Subexpression index * @deprecated Use getStartIndex(int) instead. */ public int getSubStartIndex(int sub) { if (sub >= start.length) return -1; int x = start[sub]; return (x == -1) ? x : offset + x; } /** * Returns the index within the input string used to generate this match * where subexpression number sub begins, or -1 if * the subexpression does not exist. The initial position is zero. * * @param sub Subexpression index * @since gnu.regexp 1.1.0 */ public int getStartIndex(int sub) { if (sub >= start.length) return -1; int x = start[sub]; return (x == -1) ? x : offset + x; } /** * Returns the index within the input string used to generate this match * where subexpression number sub ends, or -1 if * the subexpression does not exist. The initial position is zero. * * @param sub Subexpression index * @deprecated Use getEndIndex(int) instead */ public int getSubEndIndex(int sub) { if (sub >= start.length) return -1; int x = end[sub]; return (x == -1) ? x : offset + x; } /** * Returns the index within the input string used to generate this match * where subexpression number sub ends, or -1 if * the subexpression does not exist. The initial position is zero. * * @param sub Subexpression index */ public int getEndIndex(int sub) { if (sub >= start.length) return -1; int x = end[sub]; return (x == -1) ? x : offset + x; } /** * Substitute the results of this match to create a new string. * This is patterned after PERL, so the tokens to watch out for are * $0 through $9. $0 matches * the full substring matched; $n matches * subexpression number n. * * @param input A string consisting of literals and $n tokens. */ public String substituteInto(String input) { // a la Perl, $0 is whole thing, $1 - $9 are subexpressions StringBuffer output = new StringBuffer(); int pos; for (pos = 0; pos < input.length()-1; pos++) { if ((input.charAt(pos) == '$') && (Character.isDigit(input.charAt(pos+1)))) { int val = Character.digit(input.charAt(++pos),10); if (val < start.length) { output.append(toString(val)); } } else output.append(input.charAt(pos)); } if (pos < input.length()) output.append(input.charAt(pos)); return output.toString(); } } /* * gnu/regexp/REMatchEnumeration.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * An REMatchEnumeration enumerates regular expression matches over a * given input text. You obtain a reference to an enumeration using * the getMatchEnumeration() methods on an instance of * RE. * *

    * * REMatchEnumeration does lazy computation; that is, it will not * search for a match until it needs to. If you'd rather just get all * the matches at once in a big array, use the * getAllMatches() methods on RE. However, using an * enumeration can help speed performance when the entire text does * not need to be searched immediately. * *

    * * The enumerated type is especially useful when searching on a Reader * or InputStream, because the InputStream read position cannot be * guaranteed after calling getMatch() (see the * description of that method for an explanation of why). Enumeration * also saves a lot of overhead required when calling * getMatch() multiple times. * * @author Wes Biggs */ public static class REMatchEnumeration implements Enumeration, Serializable { private static final int YES = 1; private static final int MAYBE = 0; private static final int NO = -1; private int more; private REMatch match; private RE expr; private CharIndexed input; private int eflags; private int index; // Package scope constructor is used by RE.getMatchEnumeration() REMatchEnumeration(RE expr, CharIndexed input, int index, int eflags) { more = MAYBE; this.expr = expr; this.input = input; this.index = index; this.eflags = eflags; } /** Returns true if there are more matches in the input text. */ public boolean hasMoreElements() { return hasMoreMatches(null); } /** Returns true if there are more matches in the input text. */ public boolean hasMoreMatches() { return hasMoreMatches(null); } /** Returns true if there are more matches in the input text. * Saves the text leading up to the match (or to the end of the input) * in the specified buffer. */ public boolean hasMoreMatches(StringBuffer buffer) { if (more == MAYBE) { match = expr.getMatchImpl(input,index,eflags,buffer); if (match != null) { input.move((match.end[0] > 0) ? match.end[0] : 1); index = (match.end[0] > 0) ? match.end[0] + match.offset : index + 1; more = YES; } else more = NO; } return (more == YES); } /** Returns the next match in the input text. */ public Object nextElement() throws NoSuchElementException { return nextMatch(); } /** * Returns the next match in the input text. This method is provided * for convenience to avoid having to explicitly cast the return value * to class REMatch. */ public REMatch nextMatch() throws NoSuchElementException { if (hasMoreElements()) { more = (input.isValid()) ? MAYBE : NO; return match; } throw new NoSuchElementException(); } } /* * gnu/regexp/RESyntax.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * An RESyntax specifies the way a regular expression will be compiled. * This class provides a number of predefined useful constants for * emulating popular regular expression syntaxes. Additionally the * user may construct his or her own syntax, using any combination of the * syntax bit constants. The syntax is an optional argument to any of the * matching methods on class RE. * * @author Wes Biggs */ public static final class RESyntax implements Serializable { static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator"); private static final String SYNTAX_IS_FINAL = RE.getLocalizedMessage("syntax.final"); private BitSet bits; // true for the constant defined syntaxes private boolean isFinal = false; private String lineSeparator = DEFAULT_LINE_SEPARATOR; // Values for constants are bit indexes /** * Syntax bit. Backslash is an escape character in lists. */ public static final int RE_BACKSLASH_ESCAPE_IN_LISTS = 0; /** * Syntax bit. Use \? instead of ? and \+ instead of +. */ public static final int RE_BK_PLUS_QM = 1; /** * Syntax bit. POSIX character classes ([:...:]) in lists are allowed. */ public static final int RE_CHAR_CLASSES = 2; /** * Syntax bit. ^ and $ are special everywhere. * Not implemented. */ public static final int RE_CONTEXT_INDEP_ANCHORS = 3; /** * Syntax bit. Repetition operators are only special in valid positions. * Not implemented. */ public static final int RE_CONTEXT_INDEP_OPS = 4; /** * Syntax bit. Repetition and alternation operators are invalid * at start and end of pattern and other places. * Not implemented. */ public static final int RE_CONTEXT_INVALID_OPS = 5; /** * Syntax bit. Match-any-character operator (.) matches a newline. */ public static final int RE_DOT_NEWLINE = 6; /** * Syntax bit. Match-any-character operator (.) does not match a null. */ public static final int RE_DOT_NOT_NULL = 7; /** * Syntax bit. Intervals ({x}, {x,}, {x,y}) are allowed. */ public static final int RE_INTERVALS = 8; /** * Syntax bit. No alternation (|), match one-or-more (+), or * match zero-or-one (?) operators. */ public static final int RE_LIMITED_OPS = 9; /** * Syntax bit. Newline is an alternation operator. */ public static final int RE_NEWLINE_ALT = 10; // impl. /** * Syntax bit. Intervals use { } instead of \{ \} */ public static final int RE_NO_BK_BRACES = 11; /** * Syntax bit. Grouping uses ( ) instead of \( \). */ public static final int RE_NO_BK_PARENS = 12; /** * Syntax bit. Backreferences not allowed. */ public static final int RE_NO_BK_REFS = 13; /** * Syntax bit. Alternation uses | instead of \| */ public static final int RE_NO_BK_VBAR = 14; /** * Syntax bit. Not implemented. */ public static final int RE_NO_EMPTY_RANGES = 15; /** * Syntax bit. An unmatched right parenthesis (')' or '\)', depending * on RE_NO_BK_PARENS) will throw an exception when compiling. */ public static final int RE_UNMATCHED_RIGHT_PAREN_ORD = 16; /** * Syntax bit. Not implemented. */ public static final int RE_HAT_LISTS_NOT_NEWLINE = 17; /** * Syntax bit. Stingy matching is allowed (+?, *?, ??, {x,y}?). */ public static final int RE_STINGY_OPS = 18; /** * Syntax bit. Allow character class escapes (\d, \D, \s, \S, \w, \W). */ public static final int RE_CHAR_CLASS_ESCAPES = 19; /** * Syntax bit. Allow use of (?:xxx) grouping (subexpression is not saved). */ public static final int RE_PURE_GROUPING = 20; /** * Syntax bit. Allow use of (?=xxx) and (?!xxx) apply the subexpression * to the text following the current position without consuming that text. */ public static final int RE_LOOKAHEAD = 21; /** * Syntax bit. Allow beginning- and end-of-string anchors (\A, \Z). */ public static final int RE_STRING_ANCHORS = 22; /** * Syntax bit. Allow embedded comments, (?#comment), as in Perl5. */ public static final int RE_COMMENTS = 23; /** * Syntax bit. Allow character class escapes within lists, as in Perl5. */ public static final int RE_CHAR_CLASS_ESC_IN_LISTS = 24; private static final int BIT_TOTAL = 25; /** * Predefined syntax. * Emulates regular expression support in the awk utility. */ public static final RESyntax RE_SYNTAX_AWK; /** * Predefined syntax. * Emulates regular expression support in the ed utility. */ public static final RESyntax RE_SYNTAX_ED; /** * Predefined syntax. * Emulates regular expression support in the egrep utility. */ public static final RESyntax RE_SYNTAX_EGREP; /** * Predefined syntax. * Emulates regular expression support in the GNU Emacs editor. */ public static final RESyntax RE_SYNTAX_EMACS; /** * Predefined syntax. * Emulates regular expression support in the grep utility. */ public static final RESyntax RE_SYNTAX_GREP; /** * Predefined syntax. * Emulates regular expression support in the POSIX awk specification. */ public static final RESyntax RE_SYNTAX_POSIX_AWK; /** * Predefined syntax. * Emulates POSIX basic regular expression support. */ public static final RESyntax RE_SYNTAX_POSIX_BASIC; /** * Predefined syntax. * Emulates regular expression support in the POSIX egrep specification. */ public static final RESyntax RE_SYNTAX_POSIX_EGREP; /** * Predefined syntax. * Emulates POSIX extended regular expression support. */ public static final RESyntax RE_SYNTAX_POSIX_EXTENDED; /** * Predefined syntax. * Emulates POSIX basic minimal regular expressions. */ public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_BASIC; /** * Predefined syntax. * Emulates POSIX extended minimal regular expressions. */ public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_EXTENDED; /** * Predefined syntax. * Emulates regular expression support in the sed utility. */ public static final RESyntax RE_SYNTAX_SED; /** * Predefined syntax. * Emulates regular expression support in Larry Wall's perl, version 4, */ public static final RESyntax RE_SYNTAX_PERL4; /** * Predefined syntax. * Emulates regular expression support in Larry Wall's perl, version 4, * using single line mode (/s modifier). */ public static final RESyntax RE_SYNTAX_PERL4_S; // single line mode (/s) /** * Predefined syntax. * Emulates regular expression support in Larry Wall's perl, version 5. */ public static final RESyntax RE_SYNTAX_PERL5; /** * Predefined syntax. * Emulates regular expression support in Larry Wall's perl, version 5, * using single line mode (/s modifier). */ public static final RESyntax RE_SYNTAX_PERL5_S; static { // Define syntaxes RE_SYNTAX_EMACS = new RESyntax().makeFinal(); RESyntax RE_SYNTAX_POSIX_COMMON = new RESyntax() .set(RE_CHAR_CLASSES) .set(RE_DOT_NEWLINE) .set(RE_DOT_NOT_NULL) .set(RE_INTERVALS) .set(RE_NO_EMPTY_RANGES) .makeFinal(); RE_SYNTAX_POSIX_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON) .set(RE_BK_PLUS_QM) .makeFinal(); RE_SYNTAX_POSIX_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON) .set(RE_CONTEXT_INDEP_ANCHORS) .set(RE_CONTEXT_INDEP_OPS) .set(RE_NO_BK_BRACES) .set(RE_NO_BK_PARENS) .set(RE_NO_BK_VBAR) .set(RE_UNMATCHED_RIGHT_PAREN_ORD) .makeFinal(); RE_SYNTAX_AWK = new RESyntax() .set(RE_BACKSLASH_ESCAPE_IN_LISTS) .set(RE_DOT_NOT_NULL) .set(RE_NO_BK_PARENS) .set(RE_NO_BK_REFS) .set(RE_NO_BK_VBAR) .set(RE_NO_EMPTY_RANGES) .set(RE_UNMATCHED_RIGHT_PAREN_ORD) .makeFinal(); RE_SYNTAX_POSIX_AWK = new RESyntax(RE_SYNTAX_POSIX_EXTENDED) .set(RE_BACKSLASH_ESCAPE_IN_LISTS) .makeFinal(); RE_SYNTAX_GREP = new RESyntax() .set(RE_BK_PLUS_QM) .set(RE_CHAR_CLASSES) .set(RE_HAT_LISTS_NOT_NEWLINE) .set(RE_INTERVALS) .set(RE_NEWLINE_ALT) .makeFinal(); RE_SYNTAX_EGREP = new RESyntax() .set(RE_CHAR_CLASSES) .set(RE_CONTEXT_INDEP_ANCHORS) .set(RE_CONTEXT_INDEP_OPS) .set(RE_HAT_LISTS_NOT_NEWLINE) .set(RE_NEWLINE_ALT) .set(RE_NO_BK_PARENS) .set(RE_NO_BK_VBAR) .makeFinal(); RE_SYNTAX_POSIX_EGREP = new RESyntax(RE_SYNTAX_EGREP) .set(RE_INTERVALS) .set(RE_NO_BK_BRACES) .makeFinal(); /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ RE_SYNTAX_ED = new RESyntax(RE_SYNTAX_POSIX_BASIC) .makeFinal(); RE_SYNTAX_SED = new RESyntax(RE_SYNTAX_POSIX_BASIC) .makeFinal(); RE_SYNTAX_POSIX_MINIMAL_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON) .set(RE_LIMITED_OPS) .makeFinal(); /* Differs from RE_SYNTAX_POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ RE_SYNTAX_POSIX_MINIMAL_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON) .set(RE_CONTEXT_INDEP_ANCHORS) .set(RE_CONTEXT_INVALID_OPS) .set(RE_NO_BK_BRACES) .set(RE_NO_BK_PARENS) .set(RE_NO_BK_REFS) .set(RE_NO_BK_VBAR) .set(RE_UNMATCHED_RIGHT_PAREN_ORD) .makeFinal(); /* There is no official Perl spec, but here's a "best guess" */ RE_SYNTAX_PERL4 = new RESyntax() .set(RE_BACKSLASH_ESCAPE_IN_LISTS) .set(RE_CONTEXT_INDEP_ANCHORS) .set(RE_CONTEXT_INDEP_OPS) // except for '{', apparently .set(RE_INTERVALS) .set(RE_NO_BK_BRACES) .set(RE_NO_BK_PARENS) .set(RE_NO_BK_VBAR) .set(RE_NO_EMPTY_RANGES) .set(RE_CHAR_CLASS_ESCAPES) // \d,\D,\w,\W,\s,\S .makeFinal(); RE_SYNTAX_PERL4_S = new RESyntax(RE_SYNTAX_PERL4) .set(RE_DOT_NEWLINE) .makeFinal(); RE_SYNTAX_PERL5 = new RESyntax(RE_SYNTAX_PERL4) .set(RE_PURE_GROUPING) // (?:) .set(RE_STINGY_OPS) // *?,??,+?,{}? .set(RE_LOOKAHEAD) // (?=)(?!) .set(RE_STRING_ANCHORS) // \A,\Z .set(RE_CHAR_CLASS_ESC_IN_LISTS)// \d,\D,\w,\W,\s,\S within [] .set(RE_COMMENTS) // (?#) .makeFinal(); RE_SYNTAX_PERL5_S = new RESyntax(RE_SYNTAX_PERL5) .set(RE_DOT_NEWLINE) .makeFinal(); } /** * Construct a new syntax object with all bits turned off. * This is equivalent to RE_SYNTAX_EMACS. */ public RESyntax() { bits = new BitSet(BIT_TOTAL); } /** * Called internally when constructing predefined syntaxes * so their interpretation cannot vary. Conceivably useful * for your syntaxes as well. Causes IllegalAccessError to * be thrown if any attempt to modify the syntax is made. * * @return this object for convenient chaining */ public RESyntax makeFinal() { isFinal = true; return this; } /** * Construct a new syntax object with all bits set the same * as the other syntax. */ public RESyntax(RESyntax other) { bits = (BitSet) other.bits.clone(); } /** * Check if a given bit is set in this syntax. */ public boolean get(int index) { return bits.get(index); } /** * Set a given bit in this syntax. * * @param index the constant (RESyntax.RE_xxx) bit to set. * @return a reference to this object for easy chaining. */ public RESyntax set(int index) { if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); bits.set(index); return this; } /** * Clear a given bit in this syntax. * * @param index the constant (RESyntax.RE_xxx) bit to clear. * @return a reference to this object for easy chaining. */ public RESyntax clear(int index) { if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); bits.clear(index); return this; } /** * Changes the line separator string for regular expressions * created using this RESyntax. The default separator is the * value returned by the system property "line.separator", which * should be correct when reading platform-specific files from a * filesystem. However, many programs may collect input from * sources where the line separator is differently specified (for * example, in the applet environment, the text box widget * interprets line breaks as single-character newlines, * regardless of the host platform. * * Note that setting the line separator to a character or * characters that have specific meaning within the current syntax * can cause unexpected chronosynclastic infundibula. * * @return this object for convenient chaining */ public RESyntax setLineSeparator(String aSeparator) { if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); lineSeparator = aSeparator; return this; } /** * Returns the currently active line separator string. The default * is the platform-dependent system property "line.separator". */ public String getLineSeparator() { return lineSeparator; } } /* * gnu/regexp/REToken.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ abstract static class REToken implements Serializable { protected REToken next = null; protected REToken uncle = null; protected int subIndex; protected REToken(int subIndex) { this.subIndex = subIndex; } int getMinimumLength() { return 0; } void setUncle(REToken anUncle) { uncle = anUncle; } /** Returns true if the match succeeded, false if it failed. */ abstract boolean match(CharIndexed input, REMatch mymatch); /** Returns true if the rest of the tokens match, false if they fail. */ protected boolean next(CharIndexed input, REMatch mymatch) { if (next == null) { if (uncle == null) { return true; } else { return uncle.match(input, mymatch); } } else { return next.match(input, mymatch); } } boolean chain(REToken token) { next = token; return true; // Token was accepted } abstract void dump(StringBuffer os); void dumpAll(StringBuffer os) { dump(os); if (next != null) next.dumpAll(os); } } /* * gnu/regexp/RETokenAny.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenAny extends REToken { /** True if '.' can match a newline (RE_DOT_NEWLINE) */ private boolean newline; /** True if '.' can't match a null (RE_DOT_NOT_NULL) */ private boolean matchNull; RETokenAny(int subIndex, boolean newline, boolean matchNull) { super(subIndex); this.newline = newline; this.matchNull = matchNull; } int getMinimumLength() { return 1; } boolean match(CharIndexed input, REMatch mymatch) { char ch = input.charAt(mymatch.index); if ((ch == CharIndexed.OUT_OF_BOUNDS) || (!newline && (ch == '\n')) || (matchNull && (ch == 0))) { return false; } ++mymatch.index; return next(input, mymatch); } void dump(StringBuffer os) { os.append('.'); } } /* * gnu/regexp/RETokenBackRef.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenBackRef extends REToken { private int num; private boolean insens; RETokenBackRef(int subIndex, int num, boolean insens) { super(subIndex); this.num = num; this.insens = insens; } // should implement getMinimumLength() -- any ideas? boolean match(CharIndexed input, REMatch mymatch) { int b,e; b = mymatch.start[num]; e = mymatch.end[num]; if ((b==-1)||(e==-1)) return false; // this shouldn't happen, but... for (int i=b; i0) ? false : next(input, mymatch); if (newline != null) { char z; int i = 0; // position in newline do { z = newline.charAt(i); if (ch != z) return false; ++i; ch = input.charAt(mymatch.index + i); } while (i < newline.length()); return next(input, mymatch); } return false; } void dump(StringBuffer os) { os.append('$'); } } /* * gnu/regexp/RETokenEndSub.java * Copyright (C) 2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenEndSub extends REToken { RETokenEndSub(int subIndex) { super(subIndex); } boolean match(CharIndexed input, REMatch mymatch) { mymatch.end[subIndex] = mymatch.index; return next(input, mymatch); } void dump(StringBuffer os) { // handled by RE } } /* * gnu/regexp/RETokenOneOf.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * @since gnu.regexp 1.1.3 * @author Shashank Bapat */ final static class RETokenLookAhead extends REToken { REToken re; boolean negative; RETokenLookAhead(REToken re, boolean negative) throws REException { super(0); this.re = re; this.negative = negative; } boolean match(CharIndexed input, REMatch mymatch) { REMatch trymatch = (REMatch)mymatch.clone(); REMatch trymatch1 = (REMatch)mymatch.clone(); REMatch newMatch = null; if (re.match(input, trymatch)) { if (negative) return false; if (next(input, trymatch1)) newMatch = trymatch1; } if (newMatch != null) { if (negative) return false; //else mymatch.assignFrom(newMatch); return true; } else { // no match if (negative) return next(input, mymatch); //else return false; } } void dump(StringBuffer os) { os.append("(?"); os.append(negative ? '!' : '='); re.dumpAll(os); os.append(')'); } } /* * gnu/regexp/RETokenOneOf.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenOneOf extends REToken { private Vector options; private boolean negative; // This constructor is used for convenience when we know the set beforehand, // e.g. \d --> new RETokenOneOf("0123456789",false, ..) // \D --> new RETokenOneOf("0123456789",true, ..) RETokenOneOf(int subIndex, String optionsStr, boolean negative, boolean insens) { super(subIndex); options = new Vector(); this.negative = negative; for (int i = 0; i < optionsStr.length(); i++) options.addElement(new RETokenChar(subIndex,optionsStr.charAt(i),insens)); } RETokenOneOf(int subIndex, Vector options, boolean negative) { super(subIndex); this.options = options; this.negative = negative; } int getMinimumLength() { int min = Integer.MAX_VALUE; int x; for (int i=0; i < options.size(); i++) { if ((x = ((REToken) options.elementAt(i)).getMinimumLength()) < min) min = x; } return min; } boolean match(CharIndexed input, REMatch mymatch) { if (negative && (input.charAt(mymatch.index) == CharIndexed.OUT_OF_BOUNDS)) return false; REMatch newMatch = null; REMatch last = null; REToken tk; boolean isMatch; for (int i=0; i < options.size(); i++) { tk = (REToken) options.elementAt(i); REMatch tryMatch = (REMatch) mymatch.clone(); if (tk.match(input, tryMatch)) { // match was successful if (negative) return false; if (next(input, tryMatch)) { // Add tryMatch to list of possibilities. if (last == null) { newMatch = tryMatch; last = tryMatch; } else { last.next = tryMatch; last = tryMatch; } } // next succeeds } // is a match } // try next option if (newMatch != null) { if (negative) { return false; } else { // set contents of mymatch equal to newMatch // try each one that matched mymatch.assignFrom(newMatch); return true; } } else { if (negative) { ++mymatch.index; return next(input, mymatch); } else { return false; } } // index+1 works for [^abc] lists, not for generic lookahead (--> index) } void dump(StringBuffer os) { os.append(negative ? "[^" : "(?:"); for (int i = 0; i < options.size(); i++) { if (!negative && (i > 0)) os.append('|'); ((REToken) options.elementAt(i)).dumpAll(os); } os.append(negative ? ']' : ')'); } } /* * gnu/regexp/RETokenPOSIX.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenPOSIX extends REToken { int type; boolean insens; boolean negated; static final int ALNUM = 0; static final int ALPHA = 1; static final int BLANK = 2; static final int CNTRL = 3; static final int DIGIT = 4; static final int GRAPH = 5; static final int LOWER = 6; static final int PRINT = 7; static final int PUNCT = 8; static final int SPACE = 9; static final int UPPER = 10; static final int XDIGIT = 11; // Array indices correspond to constants defined above. static final String[] s_nameTable = { "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper", "xdigit" }; // The RE constructor uses this to look up the constant for a string static int intValue(String key) { for (int i = 0; i < s_nameTable.length; i++) { if (s_nameTable[i].equals(key)) return i; } return -1; } RETokenPOSIX(int subIndex, int type, boolean insens, boolean negated) { super(subIndex); this.type = type; this.insens = insens; this.negated = negated; } int getMinimumLength() { return 1; } boolean match(CharIndexed input, REMatch mymatch) { char ch = input.charAt(mymatch.index); if (ch == CharIndexed.OUT_OF_BOUNDS) return false; boolean retval = false; switch (type) { case ALNUM: // Note that there is some debate over whether '_' should be included retval = Character.isLetterOrDigit(ch) || (ch == '_'); break; case ALPHA: retval = Character.isLetter(ch); break; case BLANK: retval = ((ch == ' ') || (ch == '\t')); break; case CNTRL: retval = Character.isISOControl(ch); break; case DIGIT: retval = Character.isDigit(ch); break; case GRAPH: retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch))); break; case LOWER: retval = ((insens && Character.isLetter(ch)) || Character.isLowerCase(ch)); break; case PRINT: retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch))) || (ch == ' '); break; case PUNCT: // This feels sloppy, especially for non-U.S. locales. retval = ("`~!@#$%^&*()-_=+[]{}\\|;:'\"/?,.<>".indexOf(ch)!=-1); break; case SPACE: retval = Character.isWhitespace(ch); break; case UPPER: retval = ((insens && Character.isLetter(ch)) || Character.isUpperCase(ch)); break; case XDIGIT: retval = (Character.isDigit(ch) || ("abcdefABCDEF".indexOf(ch)!=-1)); break; } if (negated) retval = !retval; if (retval) { ++mymatch.index; return next(input, mymatch); } else return false; } void dump(StringBuffer os) { if (negated) os.append('^'); os.append("[:" + s_nameTable[type] + ":]"); } } /* * gnu/regexp/RETokenRange.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenRange extends REToken { private char lo, hi; private boolean insens; RETokenRange(int subIndex, char lo, char hi, boolean ins) { super(subIndex); this.lo = (insens = ins) ? Character.toLowerCase(lo) : lo; this.hi = ins ? Character.toLowerCase(hi) : hi; } int getMinimumLength() { return 1; } boolean match(CharIndexed input, REMatch mymatch) { char c = input.charAt(mymatch.index); if (c == CharIndexed.OUT_OF_BOUNDS) return false; if (insens) c = Character.toLowerCase(c); if ((c >= lo) && (c <= hi)) { ++mymatch.index; return next(input, mymatch); } return false; } void dump(StringBuffer os) { os.append(lo).append('-').append(hi); } } /* * gnu/regexp/RETokenRepeated.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ final static class RETokenRepeated extends REToken { private REToken token; private int min,max; private boolean stingy; RETokenRepeated(int subIndex, REToken token, int min, int max) { super(subIndex); this.token = token; this.min = min; this.max = max; } /** Sets the minimal matching mode to true. */ void makeStingy() { stingy = true; } /** Queries if this token has minimal matching enabled. */ boolean isStingy() { return stingy; } /** * The minimum length of a repeated token is the minimum length * of the token multiplied by the minimum number of times it must * match. */ int getMinimumLength() { return (min * token.getMinimumLength()); } // We do need to save every possible point, but the number of clone() // invocations here is really a killer for performance on non-stingy // repeat operators. I'm open to suggestions... // Hypothetical question: can you have a RE that matches 1 times, // 3 times, 5 times, but not 2 times or 4 times? Does having // the subexpression back-reference operator allow that? boolean match(CharIndexed input, REMatch mymatch) { // number of times we've matched so far int numRepeats = 0; // Possible positions for the next repeat to match at REMatch newMatch = mymatch; REMatch last = null; REMatch current; // Add the '0-repeats' index // positions.elementAt(z) == position [] in input after <> matches Vector positions = new Vector(); positions.addElement(newMatch); // Declare variables used in loop REMatch doables; REMatch doablesLast; REMatch recurrent; do { // Check for stingy match for each possibility. if (stingy && (numRepeats >= min)) { REMatch result = matchRest(input, newMatch); if (result != null) { mymatch.assignFrom(result); return true; } } doables = null; doablesLast = null; // try next repeat at all possible positions for (current = newMatch; current != null; current = current.next) { recurrent = (REMatch) current.clone(); if (token.match(input, recurrent)) { // add all items in current to doables array if (doables == null) { doables = recurrent; doablesLast = recurrent; } else { // Order these from longest to shortest // Start by assuming longest (more repeats) doablesLast.next = recurrent; } // Find new doablesLast while (doablesLast.next != null) { doablesLast = doablesLast.next; } } } // if none of the possibilities worked out, break out of do/while if (doables == null) break; // reassign where the next repeat can match newMatch = doables; // increment how many repeats we've successfully found ++numRepeats; positions.addElement(newMatch); } while (numRepeats < max); // If there aren't enough repeats, then fail if (numRepeats < min) return false; // We're greedy, but ease off until a true match is found int posIndex = positions.size(); // At this point we've either got too many or just the right amount. // See if this numRepeats works with the rest of the regexp. REMatch allResults = null; REMatch allResultsLast = null; REMatch results = null; while (--posIndex >= min) { newMatch = (REMatch) positions.elementAt(posIndex); results = matchRest(input, newMatch); if (results != null) { if (allResults == null) { allResults = results; allResultsLast = results; } else { // Order these from longest to shortest // Start by assuming longest (more repeats) allResultsLast.next = results; } // Find new doablesLast while (allResultsLast.next != null) { allResultsLast = allResultsLast.next; } } // else did not match rest of the tokens, try again on smaller sample } if (allResults != null) { mymatch.assignFrom(allResults); // does this get all? return true; } // If we fall out, no matches. return false; } private REMatch matchRest(CharIndexed input, final REMatch newMatch) { REMatch current, single; REMatch doneIndex = null; REMatch doneIndexLast = null; // Test all possible matches for this number of repeats for (current = newMatch; current != null; current = current.next) { // clone() separates a single match from the chain single = (REMatch) current.clone(); if (next(input, single)) { // chain results to doneIndex if (doneIndex == null) { doneIndex = single; doneIndexLast = single; } else { doneIndexLast.next = single; } // Find new doneIndexLast while (doneIndexLast.next != null) { doneIndexLast = doneIndexLast.next; } } } return doneIndex; } void dump(StringBuffer os) { os.append("(?:"); token.dumpAll(os); os.append(')'); if ((max == Integer.MAX_VALUE) && (min <= 1)) os.append( (min == 0) ? '*' : '+' ); else if ((min == 0) && (max == 1)) os.append('?'); else { os.append('{').append(min); if (max > min) { os.append(','); if (max != Integer.MAX_VALUE) os.append(max); } os.append('}'); } if (stingy) os.append('?'); } } /* * gnu/regexp/RETokenStart.java * Copyright (C) 1998-2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ static class RETokenStart extends REToken { private String newline; // matches after a newline RETokenStart(int subIndex, String newline) { super(subIndex); this.newline = newline; } boolean match(CharIndexed input, REMatch mymatch) { // charAt(index-n) may be unknown on a Reader/InputStream. // Match after a newline if in multiline mode if (newline != null) { int len = newline.length(); if (mymatch.offset >= len) { boolean found = true; char z; int i = 0; // position in REToken.newline char ch = input.charAt(mymatch.index - len); do { z = newline.charAt(i); if (ch != z) { found = false; break; } ++i; ch = input.charAt(mymatch.index - len + i); } while (i < len); if (found) return next(input, mymatch); } } // Don't match at all if REG_NOTBOL is set. if ((mymatch.eflags & RE.REG_NOTBOL) > 0) return false; if ((mymatch.eflags & RE.REG_ANCHORINDEX) > 0) return (mymatch.anchor == mymatch.offset) ? next(input, mymatch) : false; else return ((mymatch.index == 0) && (mymatch.offset == 0)) ? next(input, mymatch) : false; } void dump(StringBuffer os) { os.append('^'); } } /* * gnu/regexp/RETokenWordBoundary.java * Copyright (C) 2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * Represents a combination lookahead/lookbehind for POSIX [:alnum:]. */ final static class RETokenWordBoundary extends REToken { private boolean negated; private int where; static final int BEGIN = 1; static final int END = 2; RETokenWordBoundary(int subIndex, int where, boolean negated) { super(subIndex); this.where = where; this.negated = negated; } boolean match(CharIndexed input, REMatch mymatch) { // Word boundary means input[index-1] was a word character // and input[index] is not, or input[index] is a word character // and input[index-1] was not // In the string "one two three", these positions match: // |o|n|e| |t|w|o| |t|h|r|e|e| // ^ ^ ^ ^ ^ ^ boolean after = false; // is current character a letter or digit? boolean before = false; // is previous character a letter or digit? char ch; // TODO: Also check REG_ANCHORINDEX vs. anchor if (((mymatch.eflags & RE.REG_ANCHORINDEX) != RE.REG_ANCHORINDEX) || (mymatch.offset + mymatch.index > mymatch.anchor)) { if ((ch = input.charAt(mymatch.index - 1)) != CharIndexed.OUT_OF_BOUNDS) { before = Character.isLetterOrDigit(ch) || (ch == '_'); } } if ((ch = input.charAt(mymatch.index)) != CharIndexed.OUT_OF_BOUNDS) { after = Character.isLetterOrDigit(ch) || (ch == '_'); } // if (before) and (!after), we're at end (\>) // if (after) and (!before), we're at beginning (\<) boolean doNext = false; if ((where & BEGIN) == BEGIN) { doNext = after && !before; } if ((where & END) == END) { doNext ^= before && !after; } if (negated) doNext = !doNext; return (doNext ? next(input, mymatch) : false); } void dump(StringBuffer os) { if (where == (BEGIN | END)) { os.append( negated ? "\\B" : "\\b" ); } else if (where == BEGIN) { os.append("\\<"); } else { os.append("\\>"); } } } /* * gnu/regexp/UncheckedRE.java * Copyright (C) 2001 Wes Biggs * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * UncheckedRE is a subclass of RE that allows programmers an easier means * of programmatically precompiling regular expressions. It is constructed * and used in exactly the same manner as an instance of the RE class; the * only difference is that its constructors do not throw REException. * Instead, if a syntax error is encountered during construction, a * RuntimeException will be thrown. *

    * Note that this makes UncheckedRE dangerous if constructed with * dynamic data. Do not use UncheckedRE unless you are completely sure * that all input being passed to it contains valid, well-formed * regular expressions for the syntax specified. * * @author Wes Biggs * @see gnu.regexp.RE * @since gnu.regexp 1.1.4 */ public final static class UncheckedRE extends RE { /** * Constructs a regular expression pattern buffer without any compilation * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). * * @param pattern A regular expression pattern, in the form of a String, * StringBuffer or char[]. Other input types will be converted to * strings using the toString() method. * @exception RuntimeException The input pattern could not be parsed. * @exception NullPointerException The pattern was null. */ public UncheckedRE(Object pattern) { this(pattern,0,RESyntax.RE_SYNTAX_PERL5); } /** * Constructs a regular expression pattern buffer using the specified * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). * * @param pattern A regular expression pattern, in the form of a String, * StringBuffer, or char[]. Other input types will be converted to * strings using the toString() method. * @param cflags The logical OR of any combination of the compilation flags in the RE class. * @exception RuntimeException The input pattern could not be parsed. * @exception NullPointerException The pattern was null. */ public UncheckedRE(Object pattern, int cflags) { this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5); } /** * Constructs a regular expression pattern buffer using the specified * compilation flags and regular expression syntax. * * @param pattern A regular expression pattern, in the form of a String, * StringBuffer, or char[]. Other input types will be converted to * strings using the toString() method. * @param cflags The logical OR of any combination of the compilation flags in the RE class. * @param syntax The type of regular expression syntax to use. * @exception RuntimeException The input pattern could not be parsed. * @exception NullPointerException The pattern was null. */ public UncheckedRE(Object pattern, int cflags, RESyntax syntax) { try { initialize(pattern,cflags,syntax,0,0); } catch (REException e) { throw new RuntimeException(e.getMessage()); } } } }