#c_sharp #регулярные_выражения
Согласно документации С#: Регулярные выражения в C# поддерживают работу с общими категориями Юникода, среди которых есть: Nd - Число: десятичная цифра Nl - Число: буква No - Число: другое N - Все числа.All numbers. Включает категории Nd, Nl и No. Для того, чтобы проверить утверждение о том, что регулярное выражение \p{N} может содержать в себе категории Nd, Nl и No, я написал код при помощи которого можно получить полный набор символов категории N: // Get all Unicode groups: Dictionary> charInfo = ( Enumerable.Range(0, 0x110000) .Where (x => (x < 0x00D800 || x > 0x00DFFF)) .Select (Char.ConvertFromUtf32) .GroupBy(s => Char.GetUnicodeCategory(s, 0)) .ToDictionary(g => g.Key) ); // Get all number unicode groups: string[] Nd = charInfo[UnicodeCategory.DecimalDigitNumber].ToArray(); string[] Nl = charInfo[UnicodeCategory.LetterNumber ].ToArray(); string[] No = charInfo[UnicodeCategory.OtherNumber ].ToArray(); string[] N = Nd.Union(Nl).Union(No).ToArray(); Прогнав в NUnit тестах полученный набор символов группы N через регулярное выражение: Regex.IsMatch(symbol, @"\p{N}+", RegexOptions.Multiline); я получил результат в котором половина символов массива N не прошла проверку метода Regex.IsMatch(). Пример символов которые не прошли проверку: 𐒠, 𐒡. Большая часть из них выглядит как: 𖭖 Скажите пожалуйста, почему символы относящиеся к категории "числовые" не распознаются регулярным выражением \p{N}+?
Ответы
Ответ 1
Если коротко... Значение объекта Char представляет собой 16-разрядное числовое (порядковое) значение. Строки, кодируемые в UTF-16, позволяют использовать суррогатные пары, состоящие из старшего и младшего знака-заменителя, каждый из которых представляет собой переменную типа char. Если подсчитать длину такого символа, str.Length вернёт 2. По-английски, такой 𝟬 знак определён как "code point", он состоит из двух "code unit", \ud835 и \udfec. Регулярные выражения в .NET не поддерживают суррогатные пары, то есть работает на уровне "code unit". Каждая из таких пар — это обычная последовательность 16-битных байтов для движка регулярных выражений. Для того, чтобы найти их, нужно задать эти последовательности в виде диапазонов в символьных классах. Демо такого регулярного выражения, находит все числовые символы: \p{N}|\ud801[\udca0-\udca9]|\ud804[\udc66-\udc6f\udcf0-\udcf9\udd36-\udd3f\uddd0-\uddd9\udef0-\udef9\udc52-\udc65\udde1-\uddf4]|\ud805[\udcd0-\udcd9\ude50-\ude59\udec0-\udec9\udf30-\udf3b]|\ud806[\udce0-\udcf2]|\ud81a[\ude60-\ude69\udf50-\udf59\udf5b-\udf61]|\ud835[\udfce-\udfff]|\ud800[\udd40-\udd74\udf41\udf4a\udfd1-\udfd5\udd07-\udd33\udd75-\udd78\udd8a\udd8b\udee1-\udefb\udf20-\udf23]|\ud809[\udc00-\udc6e]|\ud802[\udc58-\udc5f\udc79-\udc7f\udca7-\udcaf\udcfb-\udcff\udd16-\udd1b\uddbc\uddbd\uddc0-\uddcf\uddd2-\uddff\ude40-\ude47\ude7d\ude7e\ude9d-\ude9f\udeeb-\udeef\udf58-\udf5f\udf78-\udf7f\udfa9-\udfaf]|\ud803[\udcfa-\udcff\ude60-\ude7e]|\ud834[\udf60-\udf71]|\ud83a[\udcc7-\udccf]|\ud83c[\udd00-\udd0c] Построение и тестирование шаблона Я объявил следующие переменные: var chrLst = new List>(); // список суррогатных пар со старшим и младшим знаком var testList = new List (); // список символов для тестирования var N_pattern_groups = new List (); // список для создания шаблона регулярного выражения Затем прогнал все знаки из N через следующий код: foreach (var symbol in N) { if (!Regex.IsMatch(symbol, @"\p{N}")) { testList.Add(symbol); // заполняем список тестируемых символов var chrs = symbol.ToCharArray(); chrLst.Add(new KeyValuePair (chrs[0], chrs[1])); // Заполняем список суррогатных пар var hex = string.Concat(chrs.Select(x => string.Format(@"\u{0:x4}", (int)x))); // Получаем строку шестнадцатеричных значение обоих знаков file.WriteLine($"{symbol}\t{hex}\t{char.IsSurrogatePair(symbol, 0)}"); // Сохранил информацию о символе в файл } } Файл для отладки содержит 708 символов при запуске в MS Visual Studio 2017, но всего 443 в C# Mono на ideone.com. Далее, нужно построить шаблон. Для этого группируем список с ключами и значениями по ключу, строим диапазон из значений и соединяем всё в одно регулярное выражение: var groups = chrLst.GroupBy(x => x.Key); foreach (var grp in groups) { N_pattern_groups.Add(string.Format(@"\u{0:x4}", (int)grp.Key) + CreateRangesFromList(grp.Select(x => x.Value).ToList())); } var N_pattern = string.Join("|", N_pattern_groups); Console.WriteLine($"Pattern: {N_pattern}"); // Этот шаблон без \p{N}| приведён выше Метод CreateRangesFromList выглядит не самым лучшим образом, но выполняет свою работу: private string CreateRangesFromList(List chrs) { var tmp = ""; var rngnum = 0; for (var i = 0; i < chrs.Count; i++) { if ((i < chrs.Count - 1) && (int)chrs[i] == ((int)chrs[i + 1]) - 1) { if (rngnum == 0) { tmp += string.Format(@"\u{0:x4}", (int)chrs[i]); } rngnum++; } else { tmp += (rngnum > 1 ? "-" : "") + string.Format(@"\u{0:x4}", (int)chrs[i]); rngnum = 0; } } return $"[{tmp}]"; } Проверка с помощью bool check = testList.All(symbol => Regex.IsMatch(symbol, N_pattern)); показала, что всё работает:
Комментариев нет:
Отправить комментарий