Regexp-ы для java точь в точь как для php
Материал из Dom.
Сегодня я столкнулся с необходимость написать несколько регулярных выражений для java. Надо сказать, что последнее время я часто переключаюсь между java и php, так что держать в голове два стиля использования regexp-ов становится все труднее: допускаю мелкие ошибки и опечатки. Отличия в regexp-ах не в самом синтаксисе (он обычный, те же самый \w, \d, классы символов и их модификаторы). Отличия в мелочах и эти мелочи мне не нравятся:
[править] Сравнение java-подхода к regexp-ам и подхода php
Например, для того чтобы в java проверить строку на наличие определенного шаблона я должен сделать так:
System.out.println ( "boy goes to school".matches("boy") );
И ... вы думаете, что вызов такого метода вернет true. Как бы не так, считается что строка regexp-а должна полностью совпасть со всей строкой.
System.out.println ( "boy goes to school".matches(".*boy.*") );
Для php и некоторых других продуктов, поведение противоположное - по-умолчанию считается что ассоциация regexp-а ищется везде, в любом месте строки (здесь и далее в примерах php, большое спасибо официальному php-доку).
// 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."; }
Как видите, отличия еще и в том, как задаются модификаторы поиска (например, i - говорит, что искать нужно без учета регистра). В java синтаксис указания модификаторов другой:
"boy goes to school".matches("(?i).*BOY.*")
Теперь как сделать так чтобы найти все совпадения? В php это делается так:
// 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"; }
Результат будет таким:
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 все гораздо сложнее (ну то есть, все гораздо гибче и сделать можно более хитрый поиск и прочая и прочая ... но почти всегда такой код излишен). Например следующий код я взял со страницы Proj csscompactor, он выполняет поиск в строке комментариев css и их последующее выбрасывание если какое-то условие выполнилось:
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; } }
Собственно, написанный мною код - не панацея т.к. есть мелкие отличия которые все-равно приходится помнить, но их уже гораздо меньше и встречаются они гораздо реже.
|
|
Subscribe Now! |
|
