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!

...

ObMachine projects & articles (java, flash, flex, php, ...)  -- black-zorro.com


Личные инструменты
Навигация