| /* gnu/regexp/RE.java |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package gnu.java.util.regex; |
| import java.io.InputStream; |
| import java.io.Serializable; |
| import java.util.Locale; |
| import java.util.PropertyResourceBundle; |
| import java.util.ResourceBundle; |
| import java.util.Stack; |
| import java.util.Vector; |
| |
| /** |
| * RE provides the user interface for compiling and matching regular |
| * expressions. |
| * <P> |
| * 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, |
| * <code>RESyntax.RE_SYNTAX_PERL5</code> is used). |
| * <P> |
| * Once compiled, a regular expression object is reusable as well as |
| * threadsafe: multiple threads can use the RE instance simultaneously |
| * to match against different input text. |
| * <P> |
| * Various methods attempt to match input text against a compiled |
| * regular expression. These methods are: |
| * <LI><code>isMatch</code>: returns true if the input text in its |
| * entirety matches the regular expression pattern. |
| * <LI><code>getMatch</code>: returns the first match found in the |
| * input text, or null if no match is found. |
| * <LI><code>getAllMatches</code>: returns an array of all |
| * non-overlapping matches found in the input text. If no matches are |
| * found, the array is zero-length. |
| * <LI><code>substitute</code>: substitute the first occurence of the |
| * pattern in the input text with a replacement string (which may |
| * include metacharacters $0-$9, see REMatch.substituteInto). |
| * <LI><code>substituteAll</code>: same as above, but repeat for each |
| * match before returning. |
| * <LI><code>getMatchEnumeration</code>: 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 <code>getAllMatches</code>. |
| * <P> |
| * |
| * These methods all have similar argument lists. The input can be a |
| * CharIndexed, String, a character array, a StringBuffer, or an |
| * InputStream of some sort. Note that when using an |
| * 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. |
| * |
| * Although the input object can be of various types, it is recommended |
| * that it should be a CharIndexed because {@link CharIndexed#getLastMatch()} |
| * can show the last match found on this input, which helps the expression |
| * \G work as the end of the previous match. |
| * |
| * <P> |
| * |
| * 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 an InputStream, this means an |
| * offset from the current read position, so subsequent calls with the |
| * same index argument on 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. |
| * |
| * <P> |
| * You can optionally affect the execution environment by using a |
| * combination of execution flags (constants listed below). |
| * |
| * <P> |
| * All operations on a regular expression are performed in a |
| * thread-safe manner. |
| * |
| * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> |
| * @version 1.1.5-dev, to be released |
| */ |
| |
| public class RE extends REToken { |
| |
| private static final class IntPair implements Serializable { |
| public int first, second; |
| } |
| |
| private static final class CharUnit implements Serializable { |
| public char ch; |
| public boolean bk; |
| } |
| |
| // This String will be returned by getVersion() |
| private static final String VERSION = "1.1.5-dev"; |
| |
| // The localized strings are kept in a separate file |
| private static ResourceBundle messages = PropertyResourceBundle.getBundle("gnu/java/util/regex/MessagesBundle", Locale.getDefault()); |
| |
| // 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; |
| private int maximumLength; |
| |
| /** |
| * Compilation flag. Do not differentiate case. Subsequent |
| * searches using this RE will be case insensitive. |
| */ |
| public static final int REG_ICASE = 0x02; |
| |
| /** |
| * 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 = 0x04; |
| |
| /** |
| * 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 = 0x08; |
| |
| /** |
| * 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. |
| * <P> |
| * This example demonstrates the results of various ways of matching on |
| * a substring. |
| * <P> |
| * <CODE> |
| * String s = "food bar fool";<BR> |
| * RE exp = new RE("^foo.");<BR> |
| * REMatch m0 = exp.getMatch(s);<BR> |
| * REMatch m1 = exp.getMatch(s.substring(8));<BR> |
| * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR> |
| * REMatch m3 = exp.getMatch(s,8); <BR> |
| * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX); <BR> |
| * <P> |
| * // Results:<BR> |
| * // m0.toString(): "food"<BR> |
| * // m1.toString(): "fool"<BR> |
| * // m2.toString(): null<BR> |
| * // m3.toString(): null<BR> |
| * // m4.toString(): "fool"<BR> |
| * </CODE> |
| */ |
| public static final int REG_NOTBOL = 0x10; |
| |
| /** |
| * 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 = 0x20; |
| |
| /** |
| * 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 = 0x40; |
| |
| /** |
| * 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 = 0x80; |
| |
| /** |
| * Execution flag. |
| * Try to match the whole input string. An implicit match-end operator |
| * is added to this regexp. |
| */ |
| public static final int REG_TRY_ENTIRE_MATCH = 0x0100; |
| |
| /** |
| * Execution flag. |
| * The substitute and substituteAll methods will treat the |
| * character '\' in the replacement as an escape to a literal |
| * character. In this case "\n", "\$", "\\", "\x40" and "\012" |
| * will become "n", "$", "\", "x40" and "012" respectively. |
| * This flag has no effect if REG_NO_INTERPOLATE is set on. |
| */ |
| public static final int REG_REPLACE_USE_BACKSLASHESCAPE = 0x0200; |
| |
| /** |
| * Compilation flag. Allow whitespace and comments in pattern. |
| * This is equivalent to the "/x" operator in Perl. |
| */ |
| public static final int REG_X_COMMENTS = 0x0400; |
| |
| /** |
| * Compilation flag. If set, REG_ICASE is effective only for US-ASCII. |
| */ |
| public static final int REG_ICASE_USASCII = 0x0800; |
| |
| /** 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, int maxLength) { |
| super(subIndex); |
| firstToken = first; |
| lastToken = last; |
| numSubs = subs; |
| minimumLength = minLength; |
| maximumLength = maxLength; |
| 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); |
| boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 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; |
| boolean quot = false; |
| |
| // Saved syntax and flags. |
| RESyntax savedSyntax = null; |
| int savedCflags = 0; |
| boolean flagsSaved = false; |
| |
| while (index < pLength) { |
| // read the next character unit (including backslash escapes) |
| index = getCharUnit(pattern,index,unit,quot); |
| |
| if (unit.bk) |
| if (unit.ch == 'Q') { |
| quot = true; |
| continue; |
| } else if (unit.ch == 'E') { |
| quot = false; |
| continue; |
| } |
| if (quot) |
| unit.bk = false; |
| |
| if (((cflags & REG_X_COMMENTS) > 0) && (!unit.bk) && (!quot)) { |
| if (Character.isWhitespace(unit.ch)) { |
| continue; |
| } |
| if (unit.ch == '#') { |
| for (int i = index; i < pLength; i++) { |
| if (pattern[i] == '\n') { |
| index = i + 1; |
| continue; |
| } |
| else if (pattern[i] == '\r') { |
| if (i + 1 < pLength && pattern[i + 1] == '\n') { |
| index = i + 2; |
| } |
| else { |
| index = i + 1; |
| } |
| continue; |
| } |
| } |
| index = pLength; |
| continue; |
| } |
| } |
| |
| // 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 || quot))) |
| || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !(unit.bk || quot)) ) |
| && !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, maximumLength); |
| minimumLength = 0; |
| maximumLength = 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? |
| // |
| // This method used to check "repeat.empty.token" to avoid such regexp |
| // as "(a*){2,}", but now "repeat.empty.token" is allowed. |
| |
| else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ (unit.bk || quot))) { |
| 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); |
| index = newIndex; |
| currentToken = setRepeated(currentToken,minMax.first,minMax.second,index); |
| } |
| else { |
| addToken(currentToken); |
| currentToken = new RETokenChar(subIndex,unit.ch,insens); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| } |
| |
| // LIST OPERATOR: |
| // [...] | [^...] |
| |
| else if ((unit.ch == '[') && !(unit.bk || quot)) { |
| // Create a new RETokenOneOf |
| ParseCharClassResult result = parseCharClass( |
| subIndex, pattern, index, pLength, cflags, syntax, 0); |
| addToken(currentToken); |
| currentToken = result.token; |
| index = result.index; |
| } |
| |
| // SUBEXPRESSIONS |
| // (...) | \(...\) depending on RE_NO_BK_PARENS |
| |
| else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) { |
| boolean pure = false; |
| boolean comment = false; |
| boolean lookAhead = false; |
| boolean lookBehind = false; |
| boolean independent = false; |
| boolean negativelh = false; |
| boolean negativelb = 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 '<': |
| // We assume that if the syntax supports look-ahead, |
| // it also supports look-behind. |
| if (syntax.get(RESyntax.RE_LOOKAHEAD)) { |
| index++; |
| switch (pattern[index +1]) { |
| case '!': |
| pure = true; |
| negativelb = true; |
| lookBehind = true; |
| index += 2; |
| break; |
| case '=': |
| pure = true; |
| lookBehind = true; |
| index += 2; |
| } |
| } |
| break; |
| case '>': |
| // We assume that if the syntax supports look-ahead, |
| // it also supports independent group. |
| if (syntax.get(RESyntax.RE_LOOKAHEAD)) { |
| pure = true; |
| independent = true; |
| index += 2; |
| } |
| break; |
| case 'i': |
| case 'd': |
| case 'm': |
| case 's': |
| case 'u': |
| case 'x': |
| case '-': |
| if (!syntax.get(RESyntax.RE_EMBEDDED_FLAGS)) break; |
| // Set or reset syntax flags. |
| int flagIndex = index + 1; |
| int endFlag = -1; |
| RESyntax newSyntax = new RESyntax(syntax); |
| int newCflags = cflags; |
| boolean negate = false; |
| while (flagIndex < pLength && endFlag < 0) { |
| switch(pattern[flagIndex]) { |
| case 'i': |
| if (negate) |
| newCflags &= ~REG_ICASE; |
| else |
| newCflags |= REG_ICASE; |
| flagIndex++; |
| break; |
| case 'd': |
| if (negate) |
| newSyntax.setLineSeparator(RESyntax.DEFAULT_LINE_SEPARATOR); |
| else |
| newSyntax.setLineSeparator("\n"); |
| flagIndex++; |
| break; |
| case 'm': |
| if (negate) |
| newCflags &= ~REG_MULTILINE; |
| else |
| newCflags |= REG_MULTILINE; |
| flagIndex++; |
| break; |
| case 's': |
| if (negate) |
| newCflags &= ~REG_DOT_NEWLINE; |
| else |
| newCflags |= REG_DOT_NEWLINE; |
| flagIndex++; |
| break; |
| case 'u': |
| if (negate) |
| newCflags |= REG_ICASE_USASCII; |
| else |
| newCflags &= ~REG_ICASE_USASCII; |
| flagIndex++; |
| break; |
| case 'x': |
| if (negate) |
| newCflags &= ~REG_X_COMMENTS; |
| else |
| newCflags |= REG_X_COMMENTS; |
| flagIndex++; |
| break; |
| case '-': |
| negate = true; |
| flagIndex++; |
| break; |
| case ':': |
| case ')': |
| endFlag = pattern[flagIndex]; |
| break; |
| default: |
| throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index); |
| } |
| } |
| if (endFlag == ')') { |
| syntax = newSyntax; |
| cflags = newCflags; |
| insens = ((cflags & REG_ICASE) > 0); |
| insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); |
| // This can be treated as though it were a comment. |
| comment = true; |
| index = flagIndex - 1; |
| break; |
| } |
| if (endFlag == ':') { |
| savedSyntax = syntax; |
| savedCflags = cflags; |
| flagsSaved = true; |
| syntax = newSyntax; |
| cflags = newCflags; |
| insens = ((cflags & REG_ICASE) > 0); |
| insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); |
| index = flagIndex -1; |
| // Fall through to the next case. |
| } |
| else { |
| throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index); |
| } |
| 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,false)) > 0) |
| && !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) ) { |
| if ((endIndex = nextIndex) >= pLength) |
| throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); |
| else if ((unit.ch == '[') && !(unit.bk || quot)) { |
| // I hate to do something similar to the LIST OPERATOR matters |
| // above, but ... |
| int listIndex = nextIndex; |
| if (listIndex < pLength && pattern[listIndex] == '^') listIndex++; |
| if (listIndex < pLength && pattern[listIndex] == ']') listIndex++; |
| int listEndIndex = -1; |
| int listNest = 0; |
| while (listIndex < pLength && listEndIndex < 0) { |
| switch(pattern[listIndex++]) { |
| case '\\': |
| listIndex++; |
| break; |
| case '[': |
| // Sun's API document says that regexp like "[a-d[m-p]]" |
| // is legal. Even something like "[[[^]]]]" is accepted. |
| listNest++; |
| if (listIndex < pLength && pattern[listIndex] == '^') listIndex++; |
| if (listIndex < pLength && pattern[listIndex] == ']') listIndex++; |
| break; |
| case ']': |
| if (listNest == 0) |
| listEndIndex = listIndex; |
| listNest--; |
| break; |
| } |
| } |
| if (listEndIndex >= 0) { |
| nextIndex = listEndIndex; |
| if ((endIndex = nextIndex) >= pLength) |
| throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); |
| else |
| continue; |
| } |
| throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); |
| } |
| else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) |
| nested++; |
| else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) |
| 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 || lookBehind || independent) ? |
| 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); |
| } |
| else if (lookBehind) { |
| currentToken = new RETokenLookBehind(currentToken,negativelb); |
| } |
| else if (independent) { |
| currentToken = new RETokenIndependent(currentToken); |
| } |
| |
| index = nextIndex; |
| if (flagsSaved) { |
| syntax = savedSyntax; |
| cflags = savedCflags; |
| insens = ((cflags & REG_ICASE) > 0); |
| insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); |
| flagsSaved = false; |
| } |
| } // 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 || quot)))) { |
| throw new REException(getLocalizedMessage("unmatched.paren"),REException.REG_EPAREN,index); |
| } |
| |
| // START OF LINE OPERATOR |
| // ^ |
| |
| else if ((unit.ch == '^') && !(unit.bk || quot)) { |
| addToken(currentToken); |
| currentToken = null; |
| RETokenStart token = null; |
| if ((cflags & REG_MULTILINE) > 0) { |
| String sep = syntax.getLineSeparator(); |
| if (sep == null) { |
| token = new RETokenStart(subIndex, null, true); |
| } |
| else { |
| token = new RETokenStart(subIndex, sep); |
| } |
| } |
| else { |
| token = new RETokenStart(subIndex, null); |
| } |
| addToken(token); |
| } |
| |
| // END OF LINE OPERATOR |
| // $ |
| |
| else if ((unit.ch == '$') && !(unit.bk || quot)) { |
| addToken(currentToken); |
| currentToken = null; |
| RETokenEnd token = null; |
| if ((cflags & REG_MULTILINE) > 0) { |
| String sep = syntax.getLineSeparator(); |
| if (sep == null) { |
| token = new RETokenEnd(subIndex, null, true); |
| } |
| else { |
| token = new RETokenEnd(subIndex, sep); |
| } |
| } |
| else { |
| token = new RETokenEnd(subIndex, null); |
| } |
| addToken(token); |
| } |
| |
| // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null) |
| // . |
| |
| else if ((unit.ch == '.') && !(unit.bk || quot)) { |
| 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 |
| // * |
| // |
| // This method used to check "repeat.empty.token" to avoid such regexp |
| // as "(a*)*", but now "repeat.empty.token" is allowed. |
| |
| else if ((unit.ch == '*') && !(unit.bk || quot)) { |
| 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); |
| currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index); |
| } |
| |
| // ONE-OR-MORE REPEAT OPERATOR / POSSESSIVE MATCHING OPERATOR |
| // + | \+ depending on RE_BK_PLUS_QM |
| // not available if RE_LIMITED_OPS is set |
| // |
| // This method used to check "repeat.empty.token" to avoid such regexp |
| // as "(a*)+", but now "repeat.empty.token" is allowed. |
| |
| else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) { |
| if (currentToken == null) |
| throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); |
| |
| // Check for possessive matching on RETokenRepeated |
| if (currentToken instanceof RETokenRepeated) { |
| RETokenRepeated tokenRep = (RETokenRepeated)currentToken; |
| if (syntax.get(RESyntax.RE_POSSESSIVE_OPS) && !tokenRep.isPossessive() && !tokenRep.isStingy()) |
| tokenRep.makePossessive(); |
| 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,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 || quot))) { |
| if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); |
| |
| // Check for stingy matching on RETokenRepeated |
| if (currentToken instanceof RETokenRepeated) { |
| RETokenRepeated tokenRep = (RETokenRepeated)currentToken; |
| if (syntax.get(RESyntax.RE_STINGY_OPS) && !tokenRep.isStingy() && !tokenRep.isPossessive()) |
| tokenRep.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); |
| } |
| |
| // OCTAL CHARACTER |
| // \0377 |
| |
| else if (unit.bk && (unit.ch == '0') && syntax.get(RESyntax.RE_OCTAL_CHAR)) { |
| CharExpression ce = getCharExpression(pattern, index - 2, pLength, syntax); |
| if (ce == null) |
| throw new REException("invalid octal character", REException.REG_ESCAPE, index); |
| index = index - 2 + ce.len; |
| addToken(currentToken); |
| currentToken = new RETokenChar(subIndex,ce.ch,insens); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| |
| // BACKREFERENCE OPERATOR |
| // \1 \2 ... \9 and \10 \11 \12 ... |
| // not available if RE_NO_BK_REFS is set |
| // Perl recognizes \10, \11, and so on only if enough number of |
| // parentheses have opened before it, otherwise they are treated |
| // as aliases of \010, \011, ... (octal characters). In case of |
| // Sun's JDK, octal character expression must always begin with \0. |
| // We will do as JDK does. But FIXME, take a look at "(a)(b)\29". |
| // JDK treats \2 as a back reference to the 2nd group because |
| // there are only two groups. But in our poor implementation, |
| // we cannot help but treat \29 as a back reference to the 29th group. |
| |
| else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) { |
| addToken(currentToken); |
| int numBegin = index - 1; |
| int numEnd = pLength; |
| for (int i = index; i < pLength; i++) { |
| if (! Character.isDigit(pattern[i])) { |
| numEnd = i; |
| break; |
| } |
| } |
| int num = parseInt(pattern, numBegin, numEnd-numBegin, 10); |
| |
| currentToken = new RETokenBackRef(subIndex,num,insens); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| index = numEnd; |
| } |
| |
| // 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); |
| if (insensUSASCII) currentToken.unicodeAware = 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); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| |
| // 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); |
| if (insensUSASCII) currentToken.unicodeAware = 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); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| |
| // 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); |
| if (insensUSASCII) currentToken.unicodeAware = 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); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| |
| // END OF STRING OPERATOR |
| // \Z, \z |
| |
| // FIXME: \Z and \z are different in that if the input string |
| // ends with a line terminator, \Z matches the position before |
| // the final terminator. This special behavior of \Z is yet |
| // to be implemented. |
| |
| else if (unit.bk && (unit.ch == 'Z' || unit.ch == 'z') && |
| syntax.get(RESyntax.RE_STRING_ANCHORS)) { |
| addToken(currentToken); |
| currentToken = new RETokenEnd(subIndex,null); |
| } |
| |
| // HEX CHARACTER, UNICODE CHARACTER |
| // \x1B, \u1234 |
| |
| else if ((unit.bk && (unit.ch == 'x') && syntax.get(RESyntax.RE_HEX_CHAR)) || |
| (unit.bk && (unit.ch == 'u') && syntax.get(RESyntax.RE_UNICODE_CHAR))) { |
| CharExpression ce = getCharExpression(pattern, index - 2, pLength, syntax); |
| if (ce == null) |
| throw new REException("invalid hex character", REException.REG_ESCAPE, index); |
| index = index - 2 + ce.len; |
| addToken(currentToken); |
| currentToken = new RETokenChar(subIndex,ce.ch,insens); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| |
| // NAMED PROPERTY |
| // \p{prop}, \P{prop} |
| |
| else if ((unit.bk && (unit.ch == 'p') && syntax.get(RESyntax.RE_NAMED_PROPERTY)) || |
| (unit.bk && (unit.ch == 'P') && syntax.get(RESyntax.RE_NAMED_PROPERTY))) { |
| NamedProperty np = getNamedProperty(pattern, index - 2, pLength); |
| if (np == null) |
| throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); |
| index = index - 2 + np.len; |
| addToken(currentToken); |
| currentToken = getRETokenNamedProperty(subIndex,np,insens,index); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| |
| // END OF PREVIOUS MATCH |
| // \G |
| |
| else if (unit.bk && (unit.ch == 'G') && |
| syntax.get(RESyntax.RE_STRING_ANCHORS)) { |
| addToken(currentToken); |
| currentToken = new RETokenEndOfPreviousMatch(subIndex); |
| } |
| |
| // 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); |
| if (insensUSASCII) currentToken.unicodeAware = false; |
| } |
| } // end while |
| |
| // Add final buffered token and an EndSub marker |
| addToken(currentToken); |
| |
| if (branches != null) { |
| branches.addElement(new RE(firstToken,lastToken,numSubs,subIndex,minimumLength, maximumLength)); |
| branches.trimToSize(); // compact the Vector |
| minimumLength = 0; |
| maximumLength = 0; |
| firstToken = lastToken = null; |
| addToken(new RETokenOneOf(subIndex,branches,false)); |
| } |
| else addToken(new RETokenEndSub(subIndex)); |
| |
| } |
| |
| private static class ParseCharClassResult { |
| RETokenOneOf token; |
| int index; |
| boolean returnAtAndOperator = false; |
| } |
| |
| /** |
| * Parse [...] or [^...] and make an RETokenOneOf instance. |
| * @param subIndex subIndex to be given to the created RETokenOneOf instance. |
| * @param pattern Input array of characters to be parsed. |
| * @param index Index pointing to the character next to the beginning '['. |
| * @param pLength Limit of the input array. |
| * @param cflags Compilation flags used to parse the pattern. |
| * @param pflags Flags that affect the behavior of this method. |
| * @param syntax Syntax used to parse the pattern. |
| */ |
| private static ParseCharClassResult parseCharClass(int subIndex, |
| char[] pattern, int index, |
| int pLength, int cflags, RESyntax syntax, int pflags) |
| throws REException { |
| |
| boolean insens = ((cflags & REG_ICASE) > 0); |
| boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); |
| Vector options = new Vector(); |
| Vector addition = new Vector(); |
| boolean additionAndAppeared = false; |
| final int RETURN_AT_AND = 0x01; |
| boolean returnAtAndOperator = ((pflags & RETURN_AT_AND) != 0); |
| boolean negative = false; |
| char ch; |
| |
| char lastChar = 0; |
| boolean lastCharIsSet = false; |
| 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; lastCharIsSet = true; |
| if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); |
| } |
| |
| while ((ch = pattern[index++]) != ']') { |
| if ((ch == '-') && (lastCharIsSet)) { |
| if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); |
| if ((ch = pattern[index]) == ']') { |
| RETokenChar t = new RETokenChar(subIndex,lastChar,insens); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| lastChar = '-'; |
| } else { |
| if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) { |
| CharExpression ce = getCharExpression(pattern, index, pLength, syntax); |
| if (ce == null) |
| throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); |
| ch = ce.ch; |
| index = index + ce.len - 1; |
| } |
| RETokenRange t = new RETokenRange(subIndex,lastChar,ch,insens); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| lastChar = 0; lastCharIsSet = false; |
| 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; |
| boolean asciiEscIsSet = false; |
| NamedProperty np = null; |
| 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; |
| } |
| } |
| if (("pP".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_NAMED_PROPERTY)) { |
| np = getNamedProperty(pattern, index - 1, pLength); |
| if (np == null) |
| throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); |
| index = index - 1 + np.len - 1; |
| } |
| else { |
| CharExpression ce = getCharExpression(pattern, index - 1, pLength, syntax); |
| if (ce == null) |
| throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); |
| asciiEsc = ce.ch; asciiEscIsSet = true; |
| index = index - 1 + ce.len - 1; |
| } |
| if (lastCharIsSet) { |
| RETokenChar t = new RETokenChar(subIndex,lastChar,insens); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| } |
| |
| if (posixID != -1) { |
| RETokenPOSIX t = new RETokenPOSIX(subIndex,posixID,insens,negate); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| } else if (np != null) { |
| RETokenNamedProperty t = getRETokenNamedProperty(subIndex,np,insens,index); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| } else if (asciiEscIsSet) { |
| lastChar = asciiEsc; lastCharIsSet = true; |
| } else { |
| lastChar = pattern[index]; lastCharIsSet = true; |
| } |
| ++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) { |
| RETokenPOSIX t = new RETokenPOSIX(subIndex,posixId,insens,false); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| } |
| } else if ((ch == '[') && (syntax.get(RESyntax.RE_NESTED_CHARCLASS))) { |
| ParseCharClassResult result = parseCharClass( |
| subIndex, pattern, index, pLength, cflags, syntax, 0); |
| addition.addElement(result.token); |
| addition.addElement("|"); |
| index = result.index; |
| } else if ((ch == '&') && |
| (syntax.get(RESyntax.RE_NESTED_CHARCLASS)) && |
| (index < pLength) && (pattern[index] == '&')) { |
| if (returnAtAndOperator) { |
| ParseCharClassResult result = new ParseCharClassResult(); |
| options.trimToSize(); |
| if (additionAndAppeared) addition.addElement("&"); |
| if (addition.size() == 0) addition = null; |
| result.token = new RETokenOneOf(subIndex, |
| options, addition, negative); |
| result.index = index - 1; |
| result.returnAtAndOperator = true; |
| return result; |
| } |
| // The precedence of the operator "&&" is the lowest. |
| // So we postpone adding "&" until other elements |
| // are added. And we insert Boolean.FALSE at the |
| // beginning of the list of tokens following "&&". |
| // So, "&&[a-b][k-m]" will be stored in the Vecter |
| // addition in this order: |
| // Boolean.FALSE, [a-b], "|", [k-m], "|", "&" |
| if (additionAndAppeared) addition.addElement("&"); |
| addition.addElement(Boolean.FALSE); |
| additionAndAppeared = true; |
| |
| // The part on which "&&" operates may be either |
| // (1) explicitly enclosed by [] |
| // or |
| // (2) not enclosed by [] and terminated by the |
| // next "&&" or the end of the character list. |
| // Let the preceding else if block do the case (1). |
| // We must do something in case of (2). |
| if ((index + 1 < pLength) && (pattern[index + 1] != '[')) { |
| ParseCharClassResult result = parseCharClass( |
| subIndex, pattern, index+1, pLength, cflags, syntax, |
| RETURN_AT_AND); |
| addition.addElement(result.token); |
| addition.addElement("|"); |
| // If the method returned at the next "&&", it is OK. |
| // Otherwise we have eaten the mark of the end of this |
| // character list "]". In this case we must give back |
| // the end mark. |
| index = (result.returnAtAndOperator ? |
| result.index: result.index - 1); |
| } |
| } else { |
| if (lastCharIsSet) { |
| RETokenChar t = new RETokenChar(subIndex,lastChar,insens); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| } |
| lastChar = ch; lastCharIsSet = true; |
| } |
| 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 (lastCharIsSet) { |
| RETokenChar t = new RETokenChar(subIndex,lastChar,insens); |
| if (insensUSASCII) t.unicodeAware = false; |
| options.addElement(t); |
| } |
| |
| ParseCharClassResult result = new ParseCharClassResult(); |
| // Create a new RETokenOneOf |
| options.trimToSize(); |
| if (additionAndAppeared) addition.addElement("&"); |
| if (addition.size() == 0) addition = null; |
| result.token = new RETokenOneOf(subIndex,options, addition, negative); |
| result.index = index; |
| return result; |
| } |
| |
| private static int getCharUnit(char[] input, int index, CharUnit unit, boolean quot) throws REException { |
| unit.ch = input[index++]; |
| unit.bk = (unit.ch == '\\' |
| && (!quot || index >= input.length || input[index] == 'E')); |
| if (unit.bk) |
| if (index < input.length) |
| unit.ch = input[index++]; |
| else throw new REException(getLocalizedMessage("ends.with.backslash"),REException.REG_ESCAPE,index); |
| return index; |
| } |
| |
| private static int parseInt(char[] input, int pos, int len, int radix) { |
| int ret = 0; |
| for (int i = pos; i < pos + len; i++) { |
| ret = ret * radix + Character.digit(input[i], radix); |
| } |
| return ret; |
| } |
| |
| /** |
| * This class represents various expressions for a character. |
| * "a" : 'a' itself. |
| * "\0123" : Octal char 0123 |
| * "\x1b" : Hex char 0x1b |
| * "\u1234" : Unicode char \u1234 |
| */ |
| private static class CharExpression { |
| /** character represented by this expression */ |
| char ch; |
| /** String expression */ |
| String expr; |
| /** length of this expression */ |
| int len; |
| public String toString() { return expr; } |
| } |
| |
| private static CharExpression getCharExpression(char[] input, int pos, int lim, |
| RESyntax syntax) { |
| CharExpression ce = new CharExpression(); |
| char c = input[pos]; |
| if (c == '\\') { |
| if (pos + 1 >= lim) return null; |
| c = input[pos + 1]; |
| switch(c) { |
| case 't': |
| ce.ch = '\t'; |
| ce.len = 2; |
| break; |
| case 'n': |
| ce.ch = '\n'; |
| ce.len = 2; |
| break; |
| case 'r': |
| ce.ch = '\r'; |
| ce.len = 2; |
| break; |
| case 'x': |
| case 'u': |
| if ((c == 'x' && syntax.get(RESyntax.RE_HEX_CHAR)) || |
| (c == 'u' && syntax.get(RESyntax.RE_UNICODE_CHAR))) { |
| int l = 0; |
| int expectedLength = (c == 'x' ? 2 : 4); |
| for (int i = pos + 2; i < pos + 2 + expectedLength; i++) { |
| if (i >= lim) break; |
| if (!((input[i] >= '0' && input[i] <= '9') || |
| (input[i] >= 'A' && input[i] <= 'F') || |
| (input[i] >= 'a' && input[i] <= 'f'))) |
| break; |
| l++; |
| } |
| if (l != expectedLength) return null; |
| ce.ch = (char)(parseInt(input, pos + 2, l, 16)); |
| ce.len = l + 2; |
| } |
| else { |
| ce.ch = c; |
| ce.len = 2; |
| } |
| break; |
| case '0': |
| if (syntax.get(RESyntax.RE_OCTAL_CHAR)) { |
| int l = 0; |
| for (int i = pos + 2; i < pos + 2 + 3; i++) { |
| if (i >= lim) break; |
| if (input[i] < '0' || input[i] > '7') break; |
| l++; |
| } |
| if (l == 3 && input[pos + 2] > '3') l--; |
| if (l <= 0) return null; |
| ce.ch = (char)(parseInt(input, pos + 2, l, 8)); |
| ce.len = l + 2; |
| } |
| else { |
| ce.ch = c; |
| ce.len = 2; |
| } |
| break; |
| default: |
| ce.ch = c; |
| ce.len = 2; |
| break; |
| } |
| } |
| else { |
| ce.ch = input[pos]; |
| ce.len = 1; |
| } |
| ce.expr = new String(input, pos, ce.len); |
| return ce; |
| } |
| |
| /** |
| * This class represents a substring in a pattern string expressing |
| * a named property. |
| * "\pA" : Property named "A" |
| * "\p{prop}" : Property named "prop" |
| * "\PA" : Property named "A" (Negated) |
| * "\P{prop}" : Property named "prop" (Negated) |
| */ |
| private static class NamedProperty { |
| /** Property name */ |
| String name; |
| /** Negated or not */ |
| boolean negate; |
| /** length of this expression */ |
| int len; |
| } |
| |
| private static NamedProperty getNamedProperty(char[] input, int pos, int lim) { |
| NamedProperty np = new NamedProperty(); |
| char c = input[pos]; |
| if (c == '\\') { |
| if (++pos >= lim) return null; |
| c = input[pos++]; |
| switch(c) { |
| case 'p': |
| np.negate = false; |
| break; |
| case 'P': |
| np.negate = true; |
| break; |
| default: |
| return null; |
| } |
| c = input[pos++]; |
| if (c == '{') { |
| int p = -1; |
| for (int i = pos; i < lim; i++) { |
| if (input[i] == '}') { |
| p = i; |
| break; |
| } |
| } |
| if (p < 0) return null; |
| int len = p - pos; |
| np.name = new String(input, pos, len); |
| np.len = len + 4; |
| } |
| else { |
| np.name = new String(input, pos - 1, 1); |
| np.len = 3; |
| } |
| return np; |
| } |
| else return null; |
| } |
| |
| private static RETokenNamedProperty getRETokenNamedProperty( |
| int subIndex, NamedProperty np, boolean insens, int index) |
| throws REException { |
| try { |
| return new RETokenNamedProperty(subIndex, np.name, insens, np.negate); |
| } |
| catch (REException e) { |
| REException ree; |
| ree = new REException(e.getMessage(), REException.REG_ESCAPE, index); |
| ree.initCause(e); |
| throw ree; |
| } |
| } |
| |
| /** |
| * 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)) { |
| if (m != null) { |
| if (input.charAt(m.index) == CharIndexed.OUT_OF_BOUNDS) { |
| return true; |
| } |
| } |
| } |
| 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; |
| } |
| |
| public int getMaximumLength() { |
| return maximumLength; |
| } |
| |
| /** |
| * 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.start1[subIndex] = mymatch.index; |
| |
| return firstToken.match(input, mymatch); |
| } |
| |
| REMatch findMatch(CharIndexed input, REMatch mymatch) { |
| if (mymatch.backtrackStack == null) |
| mymatch.backtrackStack = new BacktrackStack(); |
| boolean b = match(input, mymatch); |
| if (b) { |
| return mymatch; |
| } |
| return null; |
| } |
| |
| /** |
| * 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) { |
| boolean tryEntireMatch = ((eflags & REG_TRY_ENTIRE_MATCH) != 0); |
| RE re = (tryEntireMatch ? (RE) this.clone() : this); |
| if (tryEntireMatch) { |
| re.chain(new RETokenEnd(0, null)); |
| } |
| // 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 (re.match(input, mymatch)) { |
| REMatch best = mymatch; |
| // We assume that the match that coms first is the best. |
| // And the following "The longer, the better" rule has |
| // been commented out. The longest is not neccesarily |
| // the best. For example, "a" out of "aaa" is the best |
| // match for /a+?/. |
| /* |
| // Find best match of them all to observe leftmost longest |
| while ((mymatch = mymatch.next) != null) { |
| if (mymatch.index > best.index) { |
| best = mymatch; |
| } |
| } |
| */ |
| best.end[0] = best.index; |
| best.finish(input); |
| input.setLastMatch(best); |
| return best; |
| } |
| } |
| 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)); |
| |
| // Special handling at end of input for e.g. "$" |
| if (minimumLength == 0) { |
| if (match(input, mymatch)) { |
| mymatch.finish(input); |
| return mymatch; |
| } |
| } |
| |
| 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(getReplacement(replace, m, eflags)); |
| 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(getReplacement(replace, m, eflags)); |
| 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(); |
| } |
| |
| public static String getReplacement(String replace, REMatch m, int eflags) { |
| if ((eflags & REG_NO_INTERPOLATE) > 0) |
| return replace; |
| else { |
| if ((eflags & REG_REPLACE_USE_BACKSLASHESCAPE) > 0) { |
| StringBuffer sb = new StringBuffer(); |
| int l = replace.length(); |
| for (int i = 0; i < l; i++) { |
| char c = replace.charAt(i); |
| switch(c) { |
| case '\\': |
| i++; |
| // Let StringIndexOutOfBoundsException be thrown. |
| sb.append(replace.charAt(i)); |
| break; |
| case '$': |
| int i1 = i + 1; |
| while (i1 < replace.length() && |
| Character.isDigit(replace.charAt(i1))) i1++; |
| sb.append(m.substituteInto(replace.substring(i, i1))); |
| i = i1 - 1; |
| break; |
| default: |
| sb.append(c); |
| } |
| } |
| return sb.toString(); |
| } |
| else |
| return m.substituteInto(replace); |
| } |
| } |
| |
| /* Helper function for constructor */ |
| private void addToken(REToken next) { |
| if (next == null) return; |
| minimumLength += next.getMinimumLength(); |
| int nmax = next.getMaximumLength(); |
| if (nmax < Integer.MAX_VALUE && maximumLength < Integer.MAX_VALUE) |
| maximumLength += nmax; |
| else |
| maximumLength = Integer.MAX_VALUE; |
| |
| 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,false); |
| 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,false)) != 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("(?#startRE subIndex=" + subIndex + ")"); |
| if (subIndex == 0) |
| os.append("?:"); |
| if (firstToken != null) |
| firstToken.dumpAll(os); |
| if (subIndex == 0) |
| os.append(")"); |
| os.append("(?#endRE subIndex=" + subIndex + ")"); |
| } |
| |
| // Cast input appropriately or throw exception |
| // This method was originally a private method, but has been made |
| // public because java.util.regex.Matcher uses this. |
| public static CharIndexed makeCharIndexed(Object input, int index) { |
| // The case where input is already a CharIndexed is supposed |
| // be the most likely because this is the case with |
| // java.util.regex.Matcher. |
| // We could let a String or a CharSequence fall through |
| // to final input, but since it'a very likely input type, |
| // we check it first. |
| if (input instanceof CharIndexed) { |
| CharIndexed ci = (CharIndexed) input; |
| ci.setAnchor(index); |
| return ci; |
| } |
| else if (input instanceof CharSequence) |
| return new CharIndexedCharSequence((CharSequence) input,index); |
| else 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 |
| return new CharIndexedString(input.toString(), index); |
| } |
| } |