Some people, when confronted with a problem, think "I know, I’ll use regular expressions." Now they have two problems.
Jamie Zawinski in comp.lang.emacs.
Jamie Zawinski in comp.lang.emacs.
Давно это было, честно сказать и забыл уже - но считаю необходимым поделиться с общественностью :
Введение в регулярные выражения на Java.
/*
* Created on 12.02.2005 0:10:43
*/
package test.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import junit.framework.TestCase;
/**
* Regular Expression Introduction
* @author Vladimir ``BoB'' Dolzhenko
* @see "Mastering Regular Expressions" by Jeffrey E.F. Friedl
* @see {@link java.util.regex.Pattern}
* @see {@link java.util.regex.Matcher}
*/
public class RegExpIntro extends TestCase {
public static void main(String[] args) {
junit.textui.TestRunner.run(RegExpIntro.class);
}
public void setUp(){
assertTrue("Regular Expression Introduction started", true);
}
public void tearDown(){
assertTrue("Regular Expression Introduction finished.", true);
}
public void testSymbolClass(){
// символьный класс
// Представляет собой набор допустимых символов для символа в тексте
// задаётся при помощи конструкции [...]
String regexp = "[ea]";
// The matches method attempts to match the entire input sequence against the pattern.
assertTrue("e".matches(regexp)); // e is correspoding to regexp
assertTrue("a".matches(regexp)); // a is corresponding to regexp too !
assertFalse("u".matches(regexp)); // u (and other) is NOT corresponding to regexp
}
public void testInvertSymbolClass(){
// инвертированный символьный класс
// класс совпадает с любыми символами, не входящими в список.
// конструкция [^...],
String regexp = "[^ea]";
assertFalse("e".matches(regexp)); // e is not correspoding to regexp
assertFalse("a".matches(regexp)); // a is not corresponding to regexp too !
assertTrue("u".matches(regexp)); // u (and other) IS corresponding to regexp
}
public void testSymbolClassIntervals(){
// интервалы в символьном классе:
// требуется найти цифры, очевидно, что символьный класс
// [0123456789], но такая запись слишком громоздка и неудобна.Для таких целей сущетсвуют
// интервалы: [0123456789] == [0-9]
String regexp = "[0-9]";
assertTrue("0".matches(regexp)); // 0 is corresponding to regexp
assertTrue("2".matches(regexp)); // 2 is corresponding to regexp too
assertFalse("q".matches(regexp));
}
public void testSymbolClassMetaSymbols(){
// метасимволы, определяющие символьные классы:
// . (точка) - определяет любой символ
// \s == [ \t\n\r]
// "пропуской" символ - пробел, табуляция, новая строка (space)
// \S == [^ \t\n\r]
// всё, что не относится к \s
// \w == [a-zA-Z0-9_]
// буквы, цифры, знак подчёркивания (word)
// \W == [^a-zA-Z0-9_]
// всё, что не относится к \w
// \d == [0-9]
// цифры (digits)
// \D == [^0-9]
// всё, что не относится к \d
String regexp = ".";
assertTrue("c".matches(regexp));
assertTrue("$".matches(regexp));
regexp = "\\w"; // due to java style '\' have to be escaped by itself
assertTrue("c".matches(regexp)); // is corresponding to regexp
assertFalse("$".matches(regexp)); // is NOT corresponding to regexp
regexp = "\\W";
// vice-versa
assertFalse("c".matches(regexp));
assertTrue("$".matches(regexp));
}
public void testSymbolClassHiddenDangers(){
// подводный камень #1
// символ - только внутри определения символьного класса
// интерпретируется как метасимвол, во всех других случаях это обычный символ
String regexp = "-";
assertTrue("-".matches(regexp));
// если только он стоит между символьными литералами определяющими начало и конец символьного интервала
regexp = "[-A-Z]"; // символьный класс будет состоять из A-Z и символа -
assertTrue("-".matches(regexp));
assertTrue("C".matches(regexp));
regexp = "[A-Z-]"; // символьный класс будет состоять из A-Z и символа -
assertTrue("-".matches(regexp));
assertTrue("C".matches(regexp));
// Избыточно (для параноиков): в любом случае, для пущей безопасности, можно писать "\-" вместо "-"
// и "[\-A-Z]" или "[A-Z\-]" вместо "[-A-Z]".
regexp = "[A-Z\\-]"; // символьный класс будет состоять из A-Z и символа -
assertFalse("\\".matches(regexp));
assertTrue("-".matches(regexp));
assertTrue("C".matches(regexp));
// точка только вне определения символьного класса определяет любой символ
// т.е внутри символьного класса точка действует не как метасимвол, а как обычный символ точки
regexp = "[.]";
assertTrue(".".matches(regexp));
assertFalse("a".matches(regexp));
// но метасимволы определяющие символьные классы
// действуют как внутри определения символьного класса, так и вне его
regexp = "[\\w]"; // т.е это будет == [a-zA-Z0-9_], а не (\|w)
assertFalse("\\".matches(regexp));
assertTrue("w".matches(regexp));
assertTrue("a".matches(regexp));
}
public void testSelect(){
// выбор
// позволяет объединить несколько рег.выражений в одно, совпадающее с любым из выражений-компонентов.
String regexp = "this|super";
assertTrue("this".matches(regexp)); // is corresponding to regexp
assertTrue("super".matches(regexp)); // is corresponding to regexp
assertFalse("thisuper".matches(regexp));
assertFalse("thissuper".matches(regexp));
assertFalse("something".matches(regexp)); // is NOT corresponding to regexp
}
public void testQuantificator(){
// квантификаторы
// требуется найти color или colour, при этом u может "выпадать".
// Для этого существует квантификатор необязательного символа ? и регулярное выражение выглядит
// как colou?r
// т.е кол-во минимальных воспадаений 0, максимальных 1
// существуют другие метасимволы определяющие кол-во совпадений
// * = min 0, max = infinity
// + = min 1, max = infinity
//
// так же существуют интервалы:
// {min,max} соот-тует минимальному количеству совпадению min, максимальному max
// {eq} соот-тует точному совпадению eq
String regexp = "colou?r";
assertTrue("color".matches(regexp)); // is corresponding to regexp
assertTrue("colour".matches(regexp)); // is corresponding to regexp
assertFalse("coloir".matches(regexp)); // is NOT corresponding to regexp
regexp = "\\w+"; // ONE or more word-symbols occurence
assertTrue("regular_expression".matches(regexp));
assertTrue("125".matches(regexp));
assertFalse("".matches(regexp)); // there is no even one symbol
assertFalse("!@#$%".matches(regexp)); // there are not word-symbols
assertFalse("Hello!".matches(regexp)); // not of them are word-symbols
regexp = "\\w*"; // ZERO or more word-symbols occurence
assertTrue("regular_expression".matches(regexp));
assertTrue("125".matches(regexp));
assertTrue("".matches(regexp)); // empty string matches!
assertFalse("!@#$%".matches(regexp)); // there are not word-symbols
assertFalse("Hello!".matches(regexp)); // not of them are word-symbols
}
public void testQuantificatorHiddenDangers(){
// подводный камень #2
// максимальное совпадение (жадность regexp'а)
// Квантификаторы действуют максимально, по принципу "кто раньше встал, того и тапки"
String regexp = "(.*)(.*)";
Pattern pattern = Pattern.compile(regexp);
String str = "this.changed = true";
Matcher matcher = pattern.matcher(str);
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 2);
String firstGroup = str.substring(matcher.start(1), matcher.end(1));
// первый квантификатор отработал первым и забрал всю строку
assertEquals(firstGroup, str);
String secondGroup = str.substring(matcher.start(2), matcher.end(2));
// второму ничего не осталось
assertEquals(secondGroup, "");
// Сравните:
regexp = "(.*)(.+)";
pattern = Pattern.compile(regexp);
matcher = pattern.matcher(str);
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 2);
firstGroup = str.substring(matcher.start(1), matcher.end(1));
// первый квантификатор отработал первым и забрал столько,
// сколько позволил весь regexp
// т.е забрал максимальное количество символов при котором ещё возможно
// совпадение regexp'а
assertEquals(firstGroup, "this.changed = tru");
secondGroup = str.substring(matcher.start(2), matcher.end(2));
assertEquals(secondGroup, "e");
// other example here
regexp = "('.*')";
pattern = Pattern.compile(regexp);
str = "'GNU' is 'GNU is Not Unix'";
matcher = pattern.matcher(str);
assertTrue(matcher.matches());
firstGroup = str.substring(matcher.start(1), matcher.end(1));
// ожидаемое значение будет не 'GNU', а вся исходная строка, т.к квантификатор очень жадный
assertEquals(firstGroup, str);
}
public void testEscapeMetasymbols(){
// Многие символы в regexp'ах используются как ключевые управляющие символы
// для того, чтобы задавать совпадение именно по этим символам их необходимо экранировать
String regexp = "\\\\"
+ "\\."
+ "\\+"
+ "\\*"
+ "\\{"
+ "\\}"
+ "\\("
+ "\\)";
// это далеко не полный список метасимволов
// см. и читайте Mastering Regular Expressions за более детальной информацией
assertTrue("\\.+*{}()".matches(regexp));
}
public void testGroups(){
// Группы позволяют запоминать найденные совпадения, к которым можно
// в послетствии обращаться.
// Конструкция: ()
// Группы начинают нумероваться с 1ой
String regexp = "(\\w+)\\.(\\w+)\\s*=\\s*(true|false)";
Pattern pattern = Pattern.compile(regexp);
String str = "this.changed = true";
Matcher matcher = pattern.matcher(str);
// вызов matcher.matches() (либо matcher.lookingAt(), либо matcher.find() ) обязателен,
// т.к. он запускает механизм нахождения соответствия
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 3);
String firstGroup = str.substring(matcher.start(1), matcher.end(1));
assertEquals(firstGroup, "this");
String secondGroup = str.substring(matcher.start(2), matcher.end(2));
assertEquals(secondGroup, "changed");
String thirdGroup = str.substring(matcher.start(3), matcher.end(3));
assertEquals(thirdGroup, "true");
// Группы также позволяют отделить логические части выбора
regexp = "if \\((true|false)\\) \\{";
assertTrue("if (true) {".matches(regexp));
assertTrue("if (false) {".matches(regexp));
// следующий пример относится не столько к regexp'ам, сколько к java api
regexp = "(\\w+)";
pattern = Pattern.compile(regexp);
String string = " This is a table.";
matcher = pattern.matcher(string);
// в данном случае matcher.find() запускает механизм поиска соответсвий
for(int occurence=0;matcher.find();occurence++){
String substring = string.substring(matcher.start(1), matcher.end(1));
switch(occurence){
case 0:
assertEquals("This", substring);
break;
case 1:
assertEquals("is", substring);
break;
case 2:
assertEquals("a", substring);
break;
case 3:
assertEquals("table", substring);
break;
default:
// нет больше строк удовлетворящих regexp'у
fail();
break;
}
}
// меняем исходную строку, но не меняем regexp
string = " This is an apple ";
// нет необходимости вызывать pattern.matcher(string);
// достаточно сбросить matcher
matcher.reset(string);
for(int occurence=0;matcher.find();occurence++){
String substring = string.substring(matcher.start(1), matcher.end(1));
switch(occurence){
case 0:
assertEquals("This", substring);
break;
case 1:
assertEquals("is", substring);
break;
case 2:
assertEquals("an", substring);
break;
case 3:
assertEquals("apple", substring);
break;
default:
fail();
break;
}
}
}
public void testMatcherHiddenDangers(){
// всё тоже самое, только без запуска механизма поиска совпадений
String regexp = "(\\w+)\\.(\\w+)\\s*=\\s*(true|false)";
Pattern pattern = Pattern.compile(regexp);
String str = "this.changed = true";
Matcher matcher = pattern.matcher(str);
// assertTrue(matcher.matches()); // не выполнил matches() - лови exception
try{
String firstGroup = str.substring(matcher.start(1), matcher.end(1)); // вот тут возникнет exception !
assertEquals(firstGroup, "this");
fail(); // до сюда дойти не должен из-за возникшего exception'а
String secondGroup = str.substring(matcher.start(2), matcher.end(2));
assertEquals(secondGroup, "changed");
fail();
String thirdGroup = str.substring(matcher.start(3), matcher.end(3));
assertEquals(thirdGroup, "true");
fail();
} catch(IllegalStateException illegalStateException){
assertTrue(true);
}
}
public void testBeginAndEndLine(){
// Метасимвол ^ означает начало строки
String regexp = "(^\\w+)";
Pattern pattern = Pattern.compile(regexp);
String string = "StartLine ^any words$ here endLine";
Matcher matcher = pattern.matcher(string);
for(int occurence=0;matcher.find();occurence++){
String substring = string.substring(matcher.start(1), matcher.end(1));
switch(occurence){
case 0:
assertEquals("StartLine", substring);
break;
default:
// нет больше строк удовлетворящих regexp'у
fail();
break;
}
}
// Метасимвол $ означает конец строки
regexp = "(\\w+$)";
pattern = Pattern.compile(regexp);
matcher = pattern.matcher(string);
for(int occurence=0;matcher.find();occurence++){
String substring = string.substring(matcher.start(1), matcher.end(1));
switch(occurence){
case 0:
assertEquals("endLine", substring);
break;
default:
// нет больше строк удовлетворящих regexp'у
fail();
break;
}
}
}
public void testBeginAndEndLineHiddenDangers(){
// подводный камень #3
// Символы $ ^ внутри симльвольного класс интерпретируются как обычные символы
// поиск символа $ или ^ можно производить и без введения символьного класса
// эти метасимволы, как и все другие метасимволы достаточно заэкранировать,
// чтобы искать как обычные символы: \$ или \^
String regexp = "([$^]\\w+)";
Pattern pattern = Pattern.compile(regexp);
String string = "StartLine ^any words$ here endLine";
Matcher matcher = pattern.matcher(string);
for(int occurence=0;matcher.find();occurence++){
String substring = string.substring(matcher.start(1), matcher.end(1));
switch(occurence){
case 0:
assertEquals("^any", substring);
break;
default:
// нет больше строк удовлетворящих regexp'у
fail();
break;
}
}
}
public void testGroupsOrder(){
// Группы нумеруются в порядке их открытия в regexp'е
// (т.е. в том же порядке, что и их открывающие скобки):
String str = "ABC";
String regexp = "(((\\w)\\w)\\w)";
Pattern pattern = Pattern.compile(regexp);
Matcher matcher = pattern.matcher(str);
assertTrue(matcher.matches());
assertEquals(matcher.groupCount(), 3);
String firstGroup = str.substring(matcher.start(1), matcher.end(1));
assertEquals(firstGroup, "ABC");
String secondGroup = str.substring(matcher.start(2), matcher.end(2));
assertEquals(secondGroup, "AB");
String thirdGroup = str.substring(matcher.start(3), matcher.end(3));
assertEquals(thirdGroup, "A");
}
public void testBackReferences(){
// Обратные ссылки позволяют требовать совпадение со значением ранее совпавшей группы
// конструкция: \N , где N - номер группы
String regexp = "('|\")\\w+\\1";
assertTrue("'Test'".matches(regexp));
assertTrue("\"Test\"".matches(regexp));
assertFalse("'Test\"".matches(regexp));
assertFalse("\"Test'".matches(regexp));
}
public void testReferencesInReplacement(){
// Ссылки на группы можно использовать в выражении для замены
// конструкция: $N , где N - номер группы
// ВНИМАНИЕ !!! В Java \N используется как обратная ссылка на группу внутри regexp'а,
// а $N как ссылка в строке замены на группу из regexp'а
// удаление повторяющихся слов
String str = "the the table of the theories and the duties duties";
String regexp = "(\\w+)\\s+(\\1(\\s+|$))";
String replacedString = str.replaceAll(regexp, "$2");
assertEquals(replacedString, "the table of the theories and the duties");
}
}
Комментариев нет:
Отправить комментарий