Подтвердить что ты не робот

Регулярное выражение - escape escape-символы

Моя проблема довольно сложная, но ее можно свести к простому примеру.

Я пишу пользовательский язык запросов, где пользователи могут вводить строки, которые я разбираю с выражениями LinQ.

То, что я хотел бы сделать, это разделить строки символом *, если только он не экранирован правильно.

Input         Output                          Query Description
"*\\*"    --> { "*", "\\", "*" }       -- contains a '\'
"*\\\**"  --> { "*", "\\\*", "*" }     -- contains '\*'
"*\**"    --> { "*", "\*", "*" }       -- contains '*' (works now)

Я не возражаю Regex.Split возвращать пустые строки, но в итоге получаю следующее:

Regex.Split(@"*\\*", @"(?<!\\)(\*)")  --> {"", "*", "\\*"}

Как вы можете видеть, я попытался с отрицательным lookbehind, который работает для всех моих случаев, кроме этого. Я также пробовал Regex.Escape, но не повезло.

Очевидно, моя проблема в том, что я ищу \*, который соответствует \\*. Но в этом случае, \\ - другая escape-последовательность.

В любом решении нет необходимости включать регулярное выражение.

4b9b3361

Ответ 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()]);
}

ОБНОВЛЕНИЕ: Я сейчас немного запутался в вопросе. Разделение, как я всегда знал, не включает разграничение (но похоже, что ваши примеры). Если вы хотите, чтобы разделители существовали в массиве, в их собственном слоте, модификация от этого довольно проста. (Я оставлю это как упражнение для читателя в качестве доказательства работоспособности кода)