Регулярное выражение - escape escape-символы
Моя проблема довольно сложная, но ее можно свести к простому примеру.
Я пишу пользовательский язык запросов, где пользователи могут вводить строки, которые я разбираю с выражениями LinQ.
То, что я хотел бы сделать, это разделить строки символом *
, если только он не экранирован правильно.
Input Output Query Description
"*\\*" --> { "*", "\\", "*" } -- contains a '\'
"*\\\**" --> { "*", "\\\*", "*" } -- contains '\*'
"*\**" --> { "*", "\*", "*" } -- contains '*' (works now)
Я не возражаю Regex.Split
возвращать пустые строки, но в итоге получаю следующее:
Regex.Split(@"*\\*", @"(?<!\\)(\*)") --> {"", "*", "\\*"}
Как вы можете видеть, я попытался с отрицательным lookbehind, который работает для всех моих случаев, кроме этого. Я также пробовал Regex.Escape
, но не повезло.
Очевидно, моя проблема в том, что я ищу \*
, который соответствует \\*
. Но в этом случае,
\\
- другая escape-последовательность.
В любом решении нет необходимости включать регулярное выражение.
Ответы
Ответ 1
Я думаю, что это намного легче сопоставить, чем разбивать, тем более, что вы ничего не удаляете из исходной строки. Так что же с ним поиграть? Все, кроме неизолированного *
.
Как это сделать? С помощью следующего регулярного выражения:
@"(?:[^*\\]+|\\.)+|\*"
(?:[^*\\]+|\\.)+
соответствует всем, что не является *
, или любому экранированному символу. Нет необходимости в поиске.
\*
будет соответствовать разделителю.
В коде:
using System;
using System.Text.RegularExpressions;
using System.Linq;
public class Test
{
public static void Main()
{
string[] tests = new string[]{
@"*\\*",
@"*\\\**",
@"*\**",
};
Regex re = new Regex(@"(?:[^*\\]+|\\.)+|\*");
foreach (string s in tests) {
var parts = re.Matches(s)
.OfType<Match>()
.Select(m => m.Value)
.ToList();
Console.WriteLine(string.Join(", ", parts.ToArray()));
}
}
}
Вывод:
*, \\, *
*, \\\*, *
*, \*, *
демонстрация ideone
Ответ 2
Я придумал это регулярное выражение (?<=(?:^|[^\\])(?:\\\\)*)(\*)
.
Объяснение:
Вы просто делаете "белые списки", которые могут произойти до *
, и это:
- начало строки
^
- not
\
- [^\\]
- (не
\
или начало строки), а затем четное число \
- (^|[^\\])(\\\\)*
Тестовый код и примеры:
string[] tests = new string[]{
@"*\\*",
@"*\\\**",
@"*\**",
@"test\**test2",
};
Regex re = new Regex(@"(?<=(?:^|[^\\])(?:\\\\)*)(\*)");
foreach (string s in tests) {
string[] m = re.Split( s );
Console.WriteLine(String.Format("{0,-20} {1}", s, String.Join(", ",
m.Where(x => !String.IsNullOrEmpty(x)))));
}
Результат:
*\\* *, \\, *
*\\\** *, \\\*, *
*\** *, \*, *
test\**test2 test\*, *, test2
Ответ 3
Я понял, что чистое синтаксическое разбор, не-регулярное решение будет хорошим дополнением к этому вопросу.
Я мог бы прочитать это значительно быстрее, чем я мог понять любое из этих регулярных выражений. Это также упрощает фиксацию неожиданных угловых шкафов. Логика прямо выложена.
public static String[] splitOnDelimiterWithEscape(String toSplit, char delimiter, char escape) {
List<String> strings = new ArrayList<>();
char[] chars = toSplit.toCharArray();
String sub = "";
for(int i = 0 ; i < chars.length ; i++) {
if(chars[i] == escape) {
sub += (i+1 < chars.length) ? chars[++i] : ""; //assign whatever char is after the escape to the string. This essentially makes single escape character non-existent. It just forces the next character to be literal. If the escape is at end, then we just ignore it
//this is the simplest implementation of the escape. If escaping certain characters should have
//special behaviour it should be implemented here.
//You could even pass a Map mapping escape characters, to literal characters to make this even
//more general.
} else if(chars[i] == delimiter) {
strings.add(sub); //Found delimiter. So we split.
sub = "";
} else {
sub += chars[i]; //nothing special. Just append to current string.
}
}
strings.add(sub); //end of string is a boundary. Must include.
return strings.toArray(new String[strings.size()]);
}
ОБНОВЛЕНИЕ: Я сейчас немного запутался в вопросе. Разделение, как я всегда знал, не включает разграничение (но похоже, что ваши примеры). Если вы хотите, чтобы разделители существовали в массиве, в их собственном слоте, модификация от этого довольно проста. (Я оставлю это как упражнение для читателя в качестве доказательства работоспособности кода)