Предыстория.
Где-то на отрезке времени длиной в пол года я экспериментировал с использованием регулярных выражений в Haskell. Потому что хотел использовать для написание небольших утилит, где как раз требовалось применение регулярных выражений. В конечном итоге сделал все на Ruby.
Вступление.
И как оказалось в Haskell нет единой библиотеки для поддержки регулярных выражений. Так обилие выбора создала проблему с выбором. Да и документации по каждой из библиотеке оказалось не так и много. Что и подтолкнула меня к написанию этого поста.
Примечания:
1) Я не буду приводить примеры типа "Hello world": поиск количества совпадение, захват фрагмента. В место этого приведу готовый листинг кода с основыными замечанными особенностями.
2) Под скальпель попали:
- regex-pcre (0.94.2)
- pcre-light (0.4)
- regex-posix (0.95.1)
3) На производительность библиотек я не смотрел. Так как это было для меня вторичным.
То на что я обращал внимание:
- корректная обработка unicode и кириллицы (как оказалось это актуально!)
- возможность обработки строк (замена, разбитие на фрагменты)
4) Мои выводы могут быть неправильными, так как у меня не было подробной документации/примеров кода по каждой библиотеке.
Акт 1. На сцене regex-pcre
-- Пример на тестирование Regex.PCRE
import qualified Text.Regex.PCRE as PCRE
{-
regex-pcre v 0.94.2
"+"
1) вроде как бы поддерживает PCRE нотацию
"-"
1) С некоторыми русскими буквам не работает ('Ф', 'Ъ' и др.)
2) Возможности для обработки строк (замена, разбитие) не нашел
-}
main = do
putStrLn "START:"
-- работает
putStrLn "#0:"
print $ testPCRE "Name: ([А-Яа-я\\s]+) Ser" "Name: ЧьетоИмя Ser"
-- работает
putStrLn "#1:"
print $ testPCRE "имя:\r\n([А-Яа-я\\s]+)\r\nфамилия" "имя:\r\nФЫВв \r\nфамилия"
-- работает
putStrLn "#2:"
print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nфамилия" "Имя:\r\nФЫВв \r\nфамилия"
-- работает
putStrLn "#3:"
print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nфАмилия" "Имя:\r\nФЫВв \r\nфАмилия"
-- не работает (Не любит Regex.Posix букву "Ф")
putStrLn "#4:"
print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nФамилия" "Имя:\r\nФЫВв \r\nФамилия"
-- Заменяем "Ф" на "[Ф]" и работает:
putStrLn "#5:"
print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\n[Ф]амилия" "Имя:\r\nФЫВв \r\nФамилия"
print $ let regex = "имя:(.*?)фамилия" ; match = "имя:\r\nПетр \r\nфамилия" PCRE.=~ regex :: [[String]] in match
print $ let m= "xyz abc more text" PCRE.=~ "(\\w+) \\w+" ::[[String]] in m
putStrLn "#6 Поиск email: "
-- Тут все нормально
print $ testPCRE "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "aaaa \r\nlesprom.spb@bk.ru\r\n bbbb"
-- А вот здесь текст захватвается паттерном!
print $ testPCRE "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "регистрация"
testPCRE regex text = let match = text PCRE.=~ regex :: [[String]] in match
|
Оранжевым подсвечены ошибки. |
Как оказалось самая худшая из всех тестируемых:
- В документации были намеки на то что имеется возможность "настройки" поиска (нечувствительность регистра, точка - включает себя символ конца строки и т. п.) - но как это сделать не понятно.
- Явная проблема с кириллицей. Были случаи ложного захвата в тексте на русском.
Акт 2. На сцене pcre-light
-- Пример на тестирование Regex.PCRE.Light
import Text.Regex.PCRE.Light
import qualified Data.ByteString.UTF8 as B
{-
pcre-light v 0.4
"+":
1)шаблон регулярных выражений c PCRE нотацией
2)unicode и как следствие адекватная работа с кириллицей
"-"
1) Нет возможности работы с строками (замена, разбитие)
-}
main = do
putStrLn $ B.toString $ B.fromString "START:"
print $ let r = compile (B.fromString "(\\s[фыва]+\\s)") [] in (match r (B.fromString "aaddd фыва dd") [])
print $ testPCRELight "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "aaaa \r\nlesprom.spb@bk.ru\r\n bbbb"
print $ testPCRELight "\\b([\\w.\\-\\d]+@[\\w.\\-\\d]+)\\b" "регистрация"
testPCRELight :: String -> String -> Maybe [B.ByteString]
testPCRELight pattern str = let r = compile (B.fromString pattern) [] in (match r (B.fromString str) [])
-- Могут пригодится:
func1 :: Maybe [String] -> String
func1 (Just x) = last x
func1 Nothing = " - "
func2 :: Maybe [B.ByteString] -> B.ByteString
func2 (Just x) = last x
func2 Nothing = B.fromString " - "
Хорошая PCRE библиотека:
- PCRE и кириллица в норме.
- Нет возможности работы с строками (замена, разбитие).
Акт 3. На сцене Нет возможности работы с строками (замена, разбитие)
-- Пример на тестирование Regex.posix
import Text.Regex.Posix
import Text.Regex --Regular expression matching. Uses the POSIX regular expression interface in Text.Regex.Posix.
-- <) http://hackage.haskell.org/packages/archive/regex-compat/0.95.1/doc/html/Text-Regex.html
{-
regex-posix v. 0.95.1
Особенности:
1) Это Posix а не Perl нотация регулярных выражений, по этому:
- нет никаких \w, \d, \s символьных классов
- нет ленивых квантификаторов (.*?), только жадные
2) Есть возможность замены в строковых переменных по posix регулярным выражениям
-}
main = do
putStrLn $ "START:"
putStrLn $ "exp #0:"
print $ testCountMatches "12, 34, 78"
print $ testReplace "12 moon"
putStrLn $ "exp #1:"
print $ removeByPattern " /*Hel\r\nl122o*/ 13 friday " "(/\\*.*\\*/)"
putStrLn $ "exp #2:" -- Posix видимо использует только жадные квантификаторы
print $ removeByPattern " /*Hel\r\nl122o*/ 13 friday /* ddd*/ dddd" "/\\*.*?\\*/"
testCountMatches :: String -> Int
testCountMatches inPut = inPut =~ "([0-9]+)" :: Int
testReplace :: String -> String
testReplace input = subRegex (mkRegex "([0-9]+)") input "White"
-- mkRegexWithOpts() parameters:
-- False -> meen that '.' include also "\n" character!
-- True -> meen that Case sensitive!
removeByPattern :: String -> String -> String
removeByPattern input pattern = subRegex (mkRegexWithOpts pattern False True) input ""
Все хорошо только PCRE все же не хватает.
Имеется возможность работы замены в строке, разбитие строк.
Заключение.
Все течет. Все меняется. Надеюсь, с библиотеками так же.