Для меня это неправильный способ определить диапазон буквенно-цифровых символов ASCII, и я думаю, что он должен быть заменен на:
Является ли первый по-прежнему идиоматическим способом? Или принято решение? Или ошибка?
Ответ 3
Поскольку этот вопрос выходит за рамки Perl, мне было интересно узнать, как это происходит в целом. Тестирование этого на популярных языках программирования с поддержкой собственных регулярных выражений, Perl, PHP, Python, Ruby, Java и Javascript, заключаются в следующем:
-
[a-z]
будет соответствовать ASCII-7 диапазон az в каждом из этих языков, всегда и языковые настройки не влияют это никак. Символы типа ž
и š
никогда не совпадают.
-
\w
может соответствовать или не соответствовать символам ž
и š
, в зависимости от языка программирования и параметров, заданных при создании регулярного выражения. Для этого выражения разнообразие является наибольшим, так как на некоторых языках они никогда не сопоставляются, не имеют отношения к опциям, в других они всегда соответствуют друг другу, а в некоторых из них зависят.
- POSIX
[[:alpha:]]
и Unicode \p{Alpha}
и \p{L}
, если они поддерживаются системой регулярных выражений соответствующего языка программирования, и соответствующая конфигурация будет соответствовать символам типа ž
и š
.
Обратите внимание, что "Соответствующая конфигурация" не требовала изменения локали:
изменение локали не оказало влияния на результаты ни в одном из тестируемых
системы.
Чтобы быть в безопасности, я также тестировал командную строку Perl, grep и awk. Оттуда командная строка Perl ведет себя одинаково с приведенным выше. Тем не менее, grep и awk, похоже, отличаются от других в отношении того, что для них, locale имеет значение и для [a-z]
. Поведение также зависит от версии и реализации.
В этом контексте - grep, awk или подобные инструменты командной строки - я бы согласился с тем, что использование диапазона a-z
без определения языкового стандарта можно считать ошибкой, поскольку вы не можете действительно знать, что у вас получилось.
Если мы перейдем к более подробным сведениям на один язык, статус будет выглядеть следующим образом:
Java
В java \p{Alpha}
работает как [a-z]
, если класс unicode не указан,
и алфавитный символ unicode, если это так, сопоставление ž
. \w
будет соответствовать символам типа ž
, если присутствует флаг unicode, а не если нет, а \p{L}
будет соответствовать независимо от флага unicode. Для [[alpha]]
нет регулярных выражений, поддерживающих язык, или поддержки.
PHP
В PHP \w
, [[:alpha:]]
и \p{L}
будут совпадать с символами типа ž
, если присутствует unicode-переключатель, а не если это не так. \p{Alpha}
не поддерживается. Язык не влияет на регулярные выражения.
Python
\w
будет соответствовать указанным символам, если флаг unicode присутствует, а флаг локали отсутствует. Для строк unicode флаг unicode принимается по умолчанию, если используется Python 3, но не с Python 2. Unicode \p{Alpha}
, \p{L}
или POSIX [[:alpha:]]
не поддерживаются в Python.
Модификатор для использования регулярных выражений, специфичных для локали, видимо работает только для наборов символов с 1 байтом на символ, что делает его непригодным для использования в Юникоде.
Perl
\w
соответствует ранее указанным символам в дополнение к соответствию [a-z]
. Unicode \p{Letter}
, \p{Alpha}
и POSIX [[:alpha:]]
поддерживаются и работают должным образом. Флаги Unicode и locale для регулярного выражения не изменяли результаты, а также не изменяли языковой стандарт или use locale;
/no locale;
.
Behavour не изменяется, если мы запускаем тесты с использованием командной строки Perl.
рубин
[a-z]
и \w
обнаруживают только символы [a-z]
, не относящиеся к параметрам. Unicode \p{Letter}
, \p{Alpha}
и POSIX [[:alpha:]]
поддерживаются и работают как ожидалось. Язык не влияет.
Javascript
[a-z]
и \w
всегда обнаруживают только символы [a-z]
. В ECMA2015 поддерживается поддержка /u
unicode-переключателя, который в основном поддерживается основными браузерами, но не поддерживает [[:alpha:]]
, \p{Alpha}
или \p{L}
или изменяет поведение \w
. Коммутатор unicode добавляет обращение к символам unicode как к одному символу, что было проблемой раньше.
Ситуация такая же для javascript на стороне клиента, а также Node.js.
AWK
Для AWK существует более подробное описание статуса, опубликованного в статье A.8 Диапазоны регулярных выражений и локали: длинная печальная история, В нем подробно сказано, что в старом мире инструментов unix [a-z]
был правильным способом обнаружения строчных букв, и именно так работали инструменты времени. Однако 1992 POSIX представил локали и изменил интерпретацию классов символов, чтобы порядок символов был определен для каждого порядка сортировки, привязывая его к языку. Это также было принято AWK того времени (серия 3.x), что привело к нескольким вопросам. Когда была разработана серия 4.x, POSIX 2008 определил порядок undefined, а сопровождающий вернулся к исходному поведению.
В настоящее время используется в основном версия 4.x AWK. Когда это используется, [a-z]
соответствует a-z, игнорируя любые изменения локали, а \w
и [[:alpha:]]
будут соответствовать языковым символам. Unicode\p {Alpha} и \p {L} не поддерживаются.
Grep
Grep (а также sed, ed) используют GNU Basic Regular Expressions, который является старым ароматом. Он не поддерживает классы символов Unicode.
По крайней мере, gnu grep 2.16 и 2.25, по-видимому, следует за 1992 годом в том, что языковой вопрос имеет значение также для [a-z]
, а также для \w
и [[:alpha:]]
. Это означает, например, что [a-z] соответствует только z в наборе xuzvöä, если используется эстонский язык.
Тестовый код, используемый ниже для каждого языка.
Java (1.8.0_131)
import java.util.regex.*;
import java.util.Locale;
public class RegExpTest {
public static void main(String args[]) {
verify("v", 118);
verify("š", 353);
verify("ž", 382);
tryWith("v");
tryWith("š");
tryWith("ž");
}
static void tryWith(String input) {
matchWith("[a-z]", input);
matchWith("\\w", input);
matchWith("\\p{Alpha}", input);
matchWith("\\p{L}", input);
matchWith("[[:alpha:]]", input);
}
static void matchWith(String pattern, String input) {
printResult(Pattern.compile(pattern), input);
printResult(Pattern.compile(pattern, Pattern.UNICODE_CHARACTER_CLASS), input);
}
static void printResult(Pattern pattern, String input) {
System.out.printf("%s\t%03d\t%5s\t%-10s\t%-10s\t%-5s%n",
input, input.codePointAt(0), Locale.getDefault(),
specialFlag(pattern.flags()),
pattern, pattern.matcher(input).matches());
}
static String specialFlag(int flags) {
if ((flags & Pattern.UNICODE_CHARACTER_CLASS) == Pattern.UNICODE_CHARACTER_CLASS) {
return "UNICODE_FLAG";
}
return "";
}
static void verify(String str, int code) {
if (str.codePointAt(0) != code) {
throw new RuntimeException("your editor is not properly configured for this character: " + str);
}
}
}
PHP (7.1.5)
<?php
/*
PHP, even with 7, only has binary strings that can be operated with unicode-aware
functions, if needed. So functions operating them need to be told which charset to use.
When there is encoding assumed and not specified, PHP defaults to ISO-8859-1.
*/
// PHP7 and extension=php_intl.dll enabled in PHP.ini is needed for IntlChar class
function codepoint($char) {
return IntlChar::ord($char);
}
function verify($inputp, $code) {
if (codepoint($inputp) != $code) {
throw new Exception(sprintf('Your editor is not configured correctly for %s (result %s, should be %s)',
$inputp, codepoint($inputp), $code));
}
}
$rowindex = 0;
$origlocale = getlocale();
verify('v', 118);
verify('š', 353); // https://en.wikipedia.org/wiki/%C5%A0#Computing_code
verify('ž', 382); // https://en.wikipedia.org/wiki/%C5%BD#Computing_code
function tryWith($input) {
matchWith('[a-z]', $input);
matchWith('\\w', $input);
matchWith('[[:alpha:]]', $input); // POSIX, http://www.regular-expressions.info/posixbrackets.html
matchWith('\p{L}', $input);
}
function matchWith($pattern, $input) {
global $origlocale;
selectLocale($origlocale);
printResult("/^$pattern\$/", $input);
printResult("/^$pattern\$/u", $input);
selectLocale('C'); # default (root) locale
printResult("/^$pattern\$/", $input);
printResult("/^$pattern\$/u", $input);
selectLocale(['et_EE', 'et_EE.UTF-8', 'Estonian_Estonia.1257']);
printResult("/^$pattern\$/", $input);
printResult("/^$pattern\$/u", $input);
selectLocale($origlocale);
}
function selectLocale($locale) {
if (!is_array($locale)) {
$locale = [$locale];
}
// On Windows, no UTF-8 locale can be set
// https://stackoverflow.com/a/16120506/365237
// https://msdn.microsoft.com/en-us/library/x99tb11d.aspx
// Available Windows locales
// https://docs.moodle.org/dev/Table_of_locales
$retval = setlocale(LC_ALL, $locale);
//printf("setting locale %s, retval was %s\n", join(',', $locale), $retval);
if ($retval === false || $retval === null) {
throw new Exception(sprintf('Setting locale %s failed', join(',', $locale)));
}
}
function getlocale() {
return setlocale(LC_ALL, 0);
}
function printResult($pattern, $input) {
global $rowindex;
printf("%2d: %s\t%03d\t%-20s\t%-25s\t%-10s\t%-5s\n",
$rowindex, $input, codepoint($input), getlocale(),
specialFlag($pattern),
$pattern, (preg_match($pattern, $input) === 1)?'true':'false');
$rowindex = $rowindex + 1;
}
function specialFlag($pattern) {
$arr = explode('/',$pattern);
$lastelem = array_pop($arr);
if (strpos($lastelem, 'u') !== false) {
return 'UNICODE';
}
return '';
}
tryWith('v');
tryWith('š');
tryWith('ž');
Python (3.5.3)
# -*- coding: utf-8 -*-
# with python, there are two strings: unicode strings and regular ones.
# when you use unicode strings, regular expressions also take advantage of it,
# so no need to tell that separately. However, if you want to be using specific
# locale, that you need to tell.
# Note that python3 regexps defaults to unicode mode if unicode regexp string is used,
# python2 does not. Also strings are unicode strings in python3 by default.
# summary: [a-z] is always [a-z], \w will match if unicode flag is present and
# locale flag is not present, no unicode \p{Letter} or POSIX :alpha: exists.
# Letters outside ascii-7 never match \w if locale-specific
# regexp is used, as it only supports charsets with one byte per character
# (https://lists.gt.net/python/python/850772).
# Note that in addition to standard https://docs.python.org/3/library/re.html, more
# complete https://pypi.python.org/pypi/regex/ third-party regexp library exists.
import re, locale
def verify(inputp, code):
if (ord(inputp[0]) != code):
raise Exception('Your editor is not configured correctly for %s (result %s)' % (inputp, ord(inputp[0])))
return
rowindex = 0
origlocale = locale.getlocale(locale.LC_ALL)
verify(u'v', 118)
verify(u'š', 353)
verify(u'ž', 382)
def tryWith(input):
matchWith(u'[a-z]', input)
matchWith(u'\\w', input)
def matchWith(pattern, input):
global origlocale
locale.setlocale(locale.LC_ALL, origlocale)
printResult(re.compile(pattern), input)
printResult(re.compile(pattern, re.UNICODE), input)
printResult(re.compile(pattern, re.UNICODE | re.LOCALE), input)
matchWith2(pattern, input, 'C') # default (root) locale
matchWith2(pattern, input, 'et_EE')
matchWith2(pattern, input, 'et_EE.UTF-8')
matchWith2(pattern, input, 'Estonian_Estonia.1257') # Windows locale
locale.setlocale(locale.LC_ALL, origlocale)
def matchWith2(pattern, input, localeParam):
try:
locale.setlocale(locale.LC_ALL, localeParam) # default (root) locale
printResult(re.compile(pattern), input)
printResult(re.compile(pattern, re.UNICODE), input)
printResult(re.compile(pattern, re.UNICODE | re.LOCALE), input)
except locale.Error:
print("Locale %s not supported on this platform" % localeParam)
def printResult(pattern, input):
global rowindex
try:
print("%2d: %s\t%03d\t%-20s\t%-25s\t%-10s\t%-5s" % \
(rowindex, input, ord(input[0]), locale.getlocale(), \
specialFlag(pattern.flags), \
pattern.pattern, pattern.match(input) != None))
except UnicodeEncodeError:
print("%2d: %s\t%03d\t%-20s\t%-25s\t%-10s\t%-5s" % \
(rowindex, '?', ord(input[0]), locale.getlocale(), \
specialFlag(pattern.flags), \
pattern.pattern, pattern.match(input) != None))
rowindex = rowindex + 1
def specialFlag(flags):
ret = []
if ((flags & re.UNICODE) == re.UNICODE):
ret.append("UNICODE_FLAG")
if ((flags & re.LOCALE) == re.LOCALE):
ret.append("LOCALE_FLAG")
return ','.join(ret)
tryWith(u'v')
tryWith(u'š')
tryWith(u'ž')
Perl (v5.22.3)
# Summary: [a-z] is always [a-z], \w always seems to recognize given test chars and
# unicode \p{Letter}, \p{Alpha} and POSIX :alpha: are supported.
# Unicode and locale flags for regular expression didn't matter in this use case.
use warnings;
use strict;
use utf8;
use v5.14;
use POSIX qw(locale_h);
use Encode;
binmode STDOUT, "utf8";
sub codepoint {
my $inputp = $_[0];
return unpack('U*', $inputp);
}
sub verify {
my($inputp, $code) = @_;
if (codepoint($inputp) != $code) {
die sprintf('Your editor is not configured correctly for %s (result %s)', $inputp, codepoint($inputp))
}
}
sub getlocale {
return setlocale(LC_ALL);
}
my $rowindex = 0;
my $origlocale = getlocale();
verify('v', 118);
verify('š', 353);
verify('ž', 382);
# printf('orig locale is %s', $origlocale);
sub tryWith {
my ($input) = @_;
matchWith('[a-z]', $input);
matchWith('\w', $input);
matchWith('[[:alpha:]]', $input);
matchWith('\p{Alpha}', $input);
matchWith('\p{L}', $input);
}
sub matchWith {
my ($pattern, $input) = @_;
my @locales_to_test = ($origlocale, 'C','C.UTF-8', 'et_EE.UTF-8', 'Estonian_Estonia.UTF-8');
for my $testlocale (@locales_to_test) {
use locale;
# printf("Testlocale %s\n", $testlocale);
setlocale(LC_ALL, $testlocale);
printResult($pattern, $input, '');
printResult($pattern, $input, 'u');
printResult($pattern, $input, 'l');
printResult($pattern, $input, 'a');
};
no locale;
setlocale(LC_ALL, $origlocale);
printResult($pattern, $input, '');
printResult($pattern, $input, 'u');
printResult($pattern, $input, 'l');
printResult($pattern, $input, 'a');
}
sub printResult{
no warnings 'locale';
# for this test, as we want to be able to test non-unicode-compliant locales as well
# remove this for real usage
my ($pattern, $input, $flags) = @_;
my $regexp = qr/$pattern/;
$regexp = qr/$pattern/u if ($flags eq 'u');
$regexp = qr/$pattern/l if ($flags eq 'l');
printf("%2d: %s\t%03d\t%-20s\t%-25s\t%-10s\t%-5s\n",
$rowindex, $input, codepoint($input), getlocale(),
$flags, $pattern, (($input =~ $regexp) ? 'true':'false'));
$rowindex = $rowindex + 1;
}
tryWith('v');
tryWith('š');
tryWith('ž');
Ruby (ruby 2.2.6p396 (2016-11-15 версия 56800) [x64-mingw32])
# -*- coding: utf-8 -*-
# Summary: [a-z] and \w are always [a-z], unicode \p{Letter}, \p{Alpha} and POSIX
# :alpha: are supported. Locale does not have impact.
# Ruby doesn't seem to be able to interact very well with locale without 'locale'
# rubygem (https://github.com/mutoh/locale), so that is used.
require 'rubygems'
require 'locale'
def verify(inputp, code)
if (inputp.unpack('U*')[0] != code)
raise Exception, sprintf('Your editor is not configured correctly for %s (result %s)', inputp, inputp.unpack('U*')[0])
end
end
$rowindex = 0
$origlocale = Locale.current
$origcharmap = Encoding.locale_charmap
verify('v', 118)
verify('š', 353)
verify('ž', 382)
# printf('orig locale is %s.%s', $origlocale, $origcharmap)
def tryWith(input)
matchWith('[a-z]', input)
matchWith('\w', input)
matchWith('[[:alpha:]]', input)
matchWith('\p{Alpha}', input)
matchWith('\p{L}', input)
end
def matchWith(pattern, input)
locales_to_test = [$origlocale, 'C', 'et_EE', 'Estonian_Estonia']
for testlocale in locales_to_test
Locale.current = testlocale
printResult(Regexp.new(pattern), input)
printResult(Regexp.new(pattern.force_encoding('utf-8'),Regexp::FIXEDENCODING), input)
end
Locale.current = $origlocale
end
def printResult(pattern, input)
printf("%2d: %s\t%03d\t%-20s\t%-25s\t%-10s\t%-5s\n",
$rowindex, input, input.unpack('U*')[0], Locale.current,
specialFlag(pattern),
pattern, !pattern.match(input).nil?)
$rowindex = $rowindex + 1
end
def specialFlag(pattern)
return pattern.encoding
end
tryWith('v')
tryWith('š')
tryWith('ž')
Javascript (node.js) (v6.10.3)
function match(pattern, input) {
try {
var re = new RegExp(pattern, "u");
return input.match(re) !== null;
} catch(e) {
return 'unsupported';
}
}
function regexptest() {
var chars = [
String.fromCodePoint(118),
String.fromCodePoint(353),
String.fromCodePoint(382)
];
for (var i = 0; i < chars.length; i++) {
var char = chars[i];
console.log(
char
+'\t'
+ char.codePointAt(0)
+'\t'
+(match("[a-z]", char))
+'\t'
+(match("\\w", char))
+'\t'
+(match("[[:alpha:]]", char))
+'\t'
+(match("\\p{Alpha}", char))
+'\t'
+(match("\\p{L}", char))
);
}
}
regexptest();
Javascript (веб-браузеры)
function match(pattern, input) {
try {
var re = new RegExp(pattern, "u");
return input.match(re) !== null;
} catch(e) {
return 'unsupported';
}
}
window.onload = function() {
var chars = [
String.fromCodePoint(118),
String.fromCodePoint(353),
String.fromCodePoint(382)
];
for (var i = 0; i < chars.length; i++) {
var char = chars[i];
var table = document.getElementById('results');
table.innerHTML +=
'<tr><td>' + char
+'</td><td>'
+ char.codePointAt(0)
+'</td><td>'
+(match("[a-z]", char))
+'</td><td>'
+(match("\\w", char))
+'</td><td>'
+(match("[[:alpha:]]", char))
+'</td><td>'
+(match("\\p{Alpha}", char))
+'</td><td>'
+(match("\\p{L}", char))
+'</td></tr>';
}
}
table {
border-collapse: collapse;
}
table td, table th {
border: 1px solid black;
}
table tr:first-child th {
border-top: 0;
}
table tr:last-child td {
border-bottom: 0;
}
table tr td:first-child,
table tr th:first-child {
border-left: 0;
}
table tr td:last-child,
table tr th:last-child {
border-right: 0;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<table id="results">
<tr>
<td>char</td>
<td>codepoint</td>
<td>[a-z]</td>
<td>\w</td>
<td>[[:alpha:]]</td>
<td>\p{Alpha}</td>
<td>\p{L}</td>
</tr>
</table>
</body>
</html>