matched: <b>bold text</b> part 1: <b> part 2: bold text part 3: </b> matched: <a href=howdy.html>click me</a> part 1: <a href=howdy.html> part 2: click me part 3: </a>
« Java аннотации. Пример 1 | Системы управления версиями для программистов и не только. Часть 2 » |
Regexp-ы для java точь в точь как для php
Сегодня я столкнулся с необходимость написать несколько регулярных выражений для java. Надо сказать, что последнее время я часто переключаюсь между java и php, так что держать в голове два стиля использования regexp-ов становится все труднее: допускаю мелкие ошибки и опечатки. Отличия в regexp-ах не в самом синтаксисе (он обычный, те же самый \w, \d, классы символов и их модификаторы). Отличия в мелочах и эти мелочи мне не нравятся:Сравнение java-подхода к regexp-ам и подхода php
Например, для того чтобы в java проверить строку на наличие определенного шаблона я должен сделать так:
System.out.println ( "boy goes to school".matches("boy") );
System.out.println ( "boy goes to school".matches(".*boy.*") );
// The "i" after the pattern delimiter indicates a case-insensitive search
if (preg_match("/php/i", "PHP is the web scripting language of choice.")) {
echo "A match was found.";
}
else {
echo "A match was not found.";
}
В java синтаксис указания модификаторов другой:
"boy goes to school".matches("(?i).*BOY.*")
// The \\2 is an example of backreferencing. This tells pcre that
// it must match the second set of parentheses in the regular expression
// itself, which would be the ([\w]+) in this case. The extra backslash is
// required because the string is in double quotes.
$html = "<b>bold text</b><a href=howdy.html>click me</a>";
preg_match_all("/(<([\w]+)[^>]*>)(.*)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER);
foreach ($matches as $val) {
echo "matched: " . $val[0] . "\n";
echo "part 1: " . $val[1] . "\n";
echo "part 2: " . $val[3] . "\n";
echo "part 3: " . $val[4] . "\n\n";
}
Pattern p = null;
Matcher m = null;
try {
// компилируем regexp, который ищет все комментарии без учета их длины
p = Pattern.compile("(?is)/\\*.*?\\*/");
m = p.matcher(sin);
// создаем объект Matcher, с помощью которого затем будет организован
// цикл перебора всех найденных (подошедших под шаблон) строк-коментариев.
sb = new StringBuffer();
while (m.find()) {
// функция find ищет в исходной строке очередной подошедший для regexp-а фрагмент,
// но как только таких совпадений больше нет, то цикл будет прекращен
try {
String gr_0 = m.group(0);
// все группы (части regexp-а заключенные в круглые скобки) могут быть доступны
// с помощью функции group(номер_группы).
// Есть особый номер группы – 0 – эта группа захватывает абсолютно весь текст строки,
// который проассоциировался с регулярным выражением
if (gr_0.length() - 4 <= pi)
// проверяем, что если длина этого комментария за вычетом четырех символов
//(два знака “*” и два знака “/”) все же не смогла превзойти предельную то в буфер sb помещается комментарий
m.appendReplacement(sb, gr_0);
//else
// иначе комментарий удалется
// m.appendReplacement(sb, "");
} catch (Exception e) {
e.printStackTrace();
}
}// end of -- while --
// завершаем обработку хвоста исходной строки – после последнего совпадения с regexp
m.appendTail(sb);
} catch (Exception e) {
e.printStackTrace();
}
sin = sb.toString();
Ближе к делу
Одним словом, в java сделать можно больше чем в php но всегда большим количеством кода. Так что я решил написать небольшой класс-утилитку которая бы позволяла вызывать regexp-ы сходным образом, как для php:
Вот пример использования:
/*
Для записи регулярного выражения используется Perl-like синтаксис, когда строка состоит из двух секций
РАЗДЕЛИТЕЛЬ_1 СЕКЦИЯ_ЧТО_ИСКАТЬ РАЗДЕЛИТЕЛЬ_2 СЕКЦИЯ_МОДИФИКАТОРЫ
разделители строятся по стандартным правилам и могут быть любыми одинаковыми символами, или парными
[] и {} и () и <>
секция модификаторов может состоять из следующих символов:
i - поиск/замена будет регистронечувствительной CASE_INSENSITIVE
d - модификатор UNIX_LINES
x - режим когда игнорируются пробелы и можно комментировать выражение COMMENTS
m - многострочный режим, в котором символы ^ и $ ассоциируются с началом и концом каждой из строк MULTILINE
s - режим "точка - это все", в котором символ новой строки ассоциирутся с символом "." DOTALL
u - включена поддержка Unicode case-folding-га UNICODE_CASE
*/
List<String> rez = new ArrayList<String>();
List<List<String>> rez2 = new ArrayList<List<String>>();
String pattern = "/(\\w+)-(\\w+)/idxmsu";
String inputString = "hello-petyano petka-lenka";
// пример с поиском внутри строки всех совпадений
if (RegexpUtils.preg_match_all(pattern, inputString, rez2)) {
System.out.println("rez = " + rez);
}
// ищем одно, только первое, совпадение
if (RegexpUtils.preg_match("|(\\w+)-(\\w+)|", inputString, rez)) {
System.out.println("rez = " + rez);
}
System.out.println("=============================== ");
// теперь пример простой замены
System.out.println("rez = " + RegexpUtils.preg_replace("/-(\\w+)/i", inputString, "X:$1"));
// и пример сложной замены
System.out.println("rez = " + RegexpUtils.preg_replace_callback("/-(\\w+)/i", inputString,
new RegexpUtils.Replacer() {
public String onMatch(List<String> matches) {
return "z:" + matches.get(1);
}
}
));
rez = [[hello-petyano, hello, petyano], [ petka-lenka, petka, lenka]] rez = [hello-petyano petka-lenka, hello, petyano] =============================== rez = helloX:petyano petkaX:lenka rez = helloz:petyano petkaz:lenka
А вот пример исходных кодов моей библиотечки
package testi.catsandusers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Класс-обертка над стандартными для java средствами работы с регулярными выражениями
* Вместо классов Pattern, Matcher и циклов используются функции и подход их использования аналогичный php
*/
public class RegexpUtils {
/**
* Интерфейс который должен реализовать тот кто хочет выполнить обработку, замену каждого вхождения программно
*/
public static interface Replacer {
/**
* Метод должен вернуть строку на которую будет выполнена замена найденного regexp-ом фрагмента
*
* @param matches список с информацией об найденном фрагменте, нулевой элемент списка
* содержит весь текст "совпадения"
* остальные же элементы 1,2, ... содержат значения для групп внутри регулярного выражения
* @return
*/
public String onMatch(List<String> matches);
}
/**
* Кэш, в котором хранятся скомпилированные regexp-выражения
*/
private static HashMap<String, Pattern> cache = new HashMap<String, Pattern>();
/**
* Очиска кэша скомпилированных regexp-выражений
*/
public void clearCache() {
cache.clear();
}
/**
* Выполнить поиск в строке шаблона и заменить его на новую величину вычисляемую динамически, пользователем
*
* @param pattern шаблон (regexp)
* @param input строка, где выполнить поиск
* @param by объект Replacer - задает значение на что выполнить замену
* @return строка после замены
*/
public static String preg_replace_callback(String pattern, String input, Replacer by) {
Pattern p = compile(pattern, false);
Matcher m = p.matcher(input);
final int gcount = m.groupCount();
StringBuffer sb = new StringBuffer();
ArrayList<String> row = new ArrayList<String>();
while (m.find()) {
try {
row.clear();
for (int i = 0; i <= gcount; i++)
row.add(m.group(i));
m.appendReplacement(sb, by.onMatch(row));
} catch (Exception e) {
e.printStackTrace();
}
}//end -- while --
m.appendTail(sb);
return sb.toString();
}
/**
* Выполнить поиск в строке шаблона и заменить его на новую величину вычисляемую средствами Regexp-выражения
* @param pattern шаблон (regexp)
* @param input строка, где выполнить поиск
* @param by строка, на которую нужно заменить найденное значение
* @return строка после замены
*/
public static String preg_replace(String pattern, String input, String by) {
Pattern p = compile(pattern, false);
Matcher m = p.matcher(input);
StringBuffer sb = new StringBuffer();
while (m.find()) {
try {
m.appendReplacement(sb, by);
} catch (Exception e) {
e.printStackTrace();
}
}//end -- while --
m.appendTail(sb);
return sb.toString();
}
/**
* Проверка того ассоциирутся ли строка с шаблоном
* @param pattern шаблон (regexp)
* @param input строка, где выполнить поиск
* @param rez Список куда будет помещена информация об совпадении:
* нулевой элемент списка содержит весь текст совпадения
* 1, 2, ... содержат значения групп
* @return булево выражение - признак того что ассоциация произошла
*/
public static boolean preg_match(String pattern, String input, List <String> rez) {
Pattern p = compile(pattern, true);
Matcher m = p.matcher(input);
final int gcount = m.groupCount();
if (rez != null)
rez.clear();
if (m.matches())
for (int i = 0; i <= gcount; i++) {
if (rez != null)
rez.add(m.group(i));
}
return rez.size() > 0;
}
/**
* Проверка того что в строке содержится некоторый шаблон и возвращается список со всеми найденными группами совпадений
* @param pattern шаблон (regexp)
* @param input строка, где выполнить поиск
* @param rez список, куда будут помещены все найденные соответвия, список двухуровневый: первый уровень
* содержит перечисление объектов-списков, каждый из которых содержит информацию об
* очередном совпадении в таком же формате как и метод preg_match
* @return
*/
public static boolean preg_match_all(String pattern, String input, List <List <String>> rez) {
Pattern p = compile(pattern, true);
Matcher m = p.matcher(input);
final int gcount = m.groupCount();
if (rez != null)
rez.clear();
while (m.find()) {
ArrayList row = new ArrayList();
for (int i = 0; i <= gcount; i++) {
if (rez != null)
row.add(m.group(i));
}
if (rez != null)
rez.add(row);
}
return rez.size() > 0;
}
/**
* Слежебный метод выполняющий компиляцию regexp-а и сохранение его в кэш
* @param pattern текст регулярного выражения
* @param surroundBy признак того нужно ли выражение окружить .*?
* @return скомпилированный Pattern
*/
private static Pattern compile(String pattern, boolean surroundBy) {
if (cache.containsKey(pattern)) return cache.get(pattern);
final String pattern_orig = pattern;
final char firstChar = pattern.charAt(0);
char endChar = firstChar;
if (firstChar == '(') endChar = '}';
if (firstChar == '[') endChar = ']';
if (firstChar == '{') endChar = '}';
if (firstChar == '<') endChar = '>';
int lastPos = pattern.lastIndexOf(endChar);
if (lastPos == -1)
throw new RuntimeException("Invalid pattern: " + pattern);
char[] modifiers = pattern.substring(lastPos + 1).toCharArray();
int mod = 0;
for (int i = 0; i < modifiers.length; i++) {
char modifier = modifiers[i];
switch (modifier) {
case 'i':
mod |= Pattern.CASE_INSENSITIVE;
break;
case 'd':
mod |= Pattern.UNIX_LINES;
break;
case 'x':
mod |= Pattern.COMMENTS;
break;
case 'm':
mod |= Pattern.MULTILINE;
break;
case 's':
mod |= Pattern.DOTALL;
break;
case 'u':
mod |= Pattern.UNICODE_CASE;
break;
}
}
pattern = pattern.substring(1, lastPos);
if (surroundBy) {
if (pattern.charAt(0) != '^')
pattern = ".*?" + pattern;
if (pattern.charAt(pattern.length() - 1) != '$')
pattern = pattern + ".*?";
}
final Pattern rezPattern = Pattern.compile(pattern, mod);
cache.put(pattern_orig, rezPattern);
return rezPattern;
}
}
« Java аннотации. Пример 1 | Системы управления версиями для программистов и не только. Часть 2 » |