| /* 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 gnu.java.lang.CPStringBuilder; | |
| import java.io.InputStream; | |
| import java.io.Serializable; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Locale; | |
| import java.util.PropertyResourceBundle; | |
| import java.util.ResourceBundle; | |
| /** | |
| * 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 | |
| // Used by getLocalizedMessage(). | |
| private static ResourceBundle messages; | |
| // Name of the bundle that contains the localized messages. | |
| private static final String bundle = "gnu/java/util/regex/MessagesBundle"; | |
| // 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; | |
| /** | |
| * Execution flag. | |
| * Do not move the position at which the search begins. If not set, | |
| * the starting position will be moved until a match is found. | |
| */ | |
| public static final int REG_FIX_STARTING_POSITION = 0x1000; | |
| /** 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) | |
| { | |
| if (messages == null) | |
| messages = | |
| PropertyResourceBundle.getBundle (bundle, Locale.getDefault ()); | |
| 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 if (patternObj instanceof StringBuilder) | |
| { | |
| pattern = new char[((StringBuilder) patternObj).length ()]; | |
| ((StringBuilder) patternObj).getChars (0, pattern.length, pattern, 0); | |
| } | |
| else if (patternObj instanceof CPStringBuilder) | |
| { | |
| pattern = new char[((CPStringBuilder) patternObj).length ()]; | |
| ((CPStringBuilder) patternObj).getChars (0, pattern.length, pattern, | |
| 0); | |
| } | |
| else | |
| { | |
| pattern = patternObj.toString ().toCharArray (); | |
| } | |
| int pLength = pattern.length; | |
| numSubs = 0; // Number of subexpressions in this token. | |
| ArrayList < REToken > 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; | |
| 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 ArrayList < REToken > (); | |
| } | |
| branches.add (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. | |
| add (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); | |
| final ArrayList < REToken > options = new ArrayList < REToken > (); | |
| ArrayList < Object > addition = new ArrayList < Object > (); | |
| 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.add (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.add (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.add (t); | |
| } | |
| if (posixID != -1) | |
| { | |
| RETokenPOSIX t = | |
| new RETokenPOSIX (subIndex, posixID, insens, negate); | |
| if (insensUSASCII) | |
| t.unicodeAware = false; | |
| options.add (t); | |
| } | |
| else if (np != null) | |
| { | |
| RETokenNamedProperty t = | |
| getRETokenNamedProperty (subIndex, np, insens, index); | |
| if (insensUSASCII) | |
| t.unicodeAware = false; | |
| options.add (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] == ':')) | |
| { | |
| CPStringBuilder posixSet = new CPStringBuilder (); | |
| 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.add (t); | |
| } | |
| } | |
| else if ((ch == '[') && (syntax.get (RESyntax.RE_NESTED_CHARCLASS))) | |
| { | |
| ParseCharClassResult result = | |
| parseCharClass (subIndex, pattern, index, pLength, cflags, | |
| syntax, 0); | |
| addition.add (result.token); | |
| addition.add ("|"); | |
| 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.add ("&"); | |
| 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.add ("&"); | |
| addition.add (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.add (result.token); | |
| addition.add ("|"); | |
| // 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.add (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.add (t); | |
| } | |
| ParseCharClassResult result = new ParseCharClassResult (); | |
| // Create a new RETokenOneOf | |
| options.trimToSize (); | |
| if (additionAndAppeared) | |
| addition.add ("&"); | |
| 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) | |
| { | |
| List < REMatch > all = new ArrayList < REMatch > (); | |
| REMatch m = null; | |
| while ((m = getMatchImpl (input, index, eflags, null)) != null) | |
| { | |
| all.add (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; | |
| } | |
| return all.toArray (new REMatch[all.size ()]); | |
| } | |
| /* Implements abstract method REToken.match() */ | |
| boolean match (CharIndexed input, REMatch mymatch) | |
| { | |
| input.setHitEnd (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, | |
| CPStringBuilder buffer) | |
| { | |
| return getMatchImpl (makeCharIndexed (input, index), index, eflags, | |
| buffer); | |
| } | |
| REMatch getMatchImpl (CharIndexed input, int anchor, int eflags, | |
| CPStringBuilder buffer) | |
| { | |
| boolean tryEntireMatch = ((eflags & REG_TRY_ENTIRE_MATCH) != 0); | |
| boolean doMove = ((eflags & REG_FIX_STARTING_POSITION) == 0); | |
| RE re = (tryEntireMatch ? (RE) this.clone () : this); | |
| if (tryEntireMatch) | |
| { | |
| RETokenEnd reEnd = new RETokenEnd (0, null); | |
| reEnd.setFake (true); | |
| re.chain (reEnd); | |
| } | |
| // Create a new REMatch to hold results | |
| REMatch mymatch = new REMatch (numSubs, anchor, eflags); | |
| do | |
| { | |
| /* The following potimization is commented out because | |
| the matching should be tried even if the length of | |
| input is obviously too short in order that | |
| java.util.regex.Matcher#hitEnd() may work correctly. | |
| // 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; | |
| } | |
| /* End of the optimization commented out | |
| } | |
| */ | |
| mymatch.clear (++anchor); | |
| // Append character to buffer if needed | |
| if (buffer != null && input.charAt (0) != CharIndexed.OUT_OF_BOUNDS) | |
| { | |
| buffer.append (input.charAt (0)); | |
| } | |
| // java.util.regex.Matcher#hitEnd() requires that the search should | |
| // be tried at the end of input, so we use move1(1) instead of move(1) | |
| } | |
| while (doMove && input.move1 (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) | |
| { | |
| CPStringBuilder buffer = new CPStringBuilder (); | |
| 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) | |
| { | |
| CPStringBuilder buffer = new CPStringBuilder (); | |
| 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) | |
| { | |
| CPStringBuilder sb = new CPStringBuilder (); | |
| 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, | |
| CPStringBuilder 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 (); | |
| CPStringBuilder buf = new CPStringBuilder (); | |
| // 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 CPStringBuilder (); | |
| // 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 () | |
| { | |
| CPStringBuilder sb = new CPStringBuilder (); | |
| dump (sb); | |
| return sb.toString (); | |
| } | |
| void dump (CPStringBuilder 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); | |
| } | |
| } |