Регулярное выражение для разделения на пробелы, кроме как в кавычках
Я хотел бы использовать метод.Net Regex.Split, чтобы разбить эту входную строку на массив. Он должен разбиваться на пробелы, если он не заключен в кавычки.
Входные данные: вот "моя строка", она имеет "шесть совпадений"
Ожидаемый результат:
- Вот
- является
- моя строка
- Это
- имеет
- шесть матчей
Какой шаблон мне нужен? Также мне нужно указать какие-либо RegexOptions?
12 ответов
Варианты не требуются
Regex:
\w+|"[\w\s]*"
C#:
Regex regex = new Regex(@"\w+|""[\w\s]*""");
Или, если вам нужно исключить "символы:
Regex
.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""")
.Cast<Match>()
.Select(m => m.Groups["match"].Value)
.ToList()
.ForEach(s => Console.WriteLine(s));
Решение Ливена проходит большую часть пути, и, как он заявляет в своих комментариях, это всего лишь вопрос изменения концовки на решение Бартека. Конечный результат - следующий рабочий регекс:
(?<=")\w[\w\s]*(?=")|\w+|"[\w\s]*"
Входные данные: вот "моя строка", она имеет "шесть совпадений"
Выход:
- Вот
- является
- "моя строка"
- Это
- имеет
- "шесть матчей"
К сожалению, это в том числе цитаты. Если вы вместо этого используете следующее:
(("((?<token>.*?)(?<!\\)")|(?<token>[\w]+))(\s)*)
И явно захватить совпадения "токен" следующим образом:
RegexOptions options = RegexOptions.None;
Regex regex = new Regex( @"((""((?<token>.*?)(?<!\\)"")|(?<token>[\w]+))(\s)*)", options );
string input = @" Here is ""my string"" it has "" six matches"" ";
var result = (from Match m in regex.Matches( input )
where m.Groups[ "token" ].Success
select m.Groups[ "token" ].Value).ToList();
for ( int i = 0; i < result.Count(); i++ )
{
Debug.WriteLine( string.Format( "Token[{0}]: '{1}'", i, result[ i ] ) );
}
Отладочный вывод:
Token[0]: 'Here'
Token[1]: 'is'
Token[2]: 'my string'
Token[3]: 'it'
Token[4]: 'has'
Token[5]: ' six matches'
Главный ответ не совсем подходит для меня. Я пытался разделить строку такого типа по пробелам, но похоже, что она разделяется и на точки ('.').
"the lib.lib" "another lib".lib
Я знаю, что вопрос задает регулярные выражения, но в итоге я написал функцию без регулярных выражений, чтобы сделать это:
/// <summary>
/// Splits the string passed in by the delimiters passed in.
/// Quoted sections are not split, and all tokens have whitespace
/// trimmed from the start and end.
public static List<string> split(string stringToSplit, params char[] delimiters)
{
List<string> results = new List<string>();
bool inQuote = false;
StringBuilder currentToken = new StringBuilder();
for (int index = 0; index < stringToSplit.Length; ++index)
{
char currentCharacter = stringToSplit[index];
if (currentCharacter == '"')
{
// When we see a ", we need to decide whether we are
// at the start or send of a quoted section...
inQuote = !inQuote;
}
else if (delimiters.Contains(currentCharacter) && inQuote == false)
{
// We've come to the end of a token, so we find the token,
// trim it and add it to the collection of results...
string result = currentToken.ToString().Trim();
if (result != "") results.Add(result);
// We start a new token...
currentToken = new StringBuilder();
}
else
{
// We've got a 'normal' character, so we add it to
// the curent token...
currentToken.Append(currentCharacter);
}
}
// We've come to the end of the string, so we add the last token...
string lastResult = currentToken.ToString().Trim();
if (lastResult != "") results.Add(lastResult);
return results;
}
Я использовал ответ Бартека Сабата, но мне нужно было захватить больше, чем просто символы \ w в моих токенах. Чтобы решить эту проблему, я слегка изменил его регулярное выражение, аналогично ответу Грзенио:
Regular Expression: (?<match>[^\s"]+)|(?<match>"[^"]*")
C# String: (?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")
Код Бартека (который возвращает токены без заключенных в кавычки) становится:
Regex
.Matches(input, "(?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")")
.Cast<Match>()
.Select(m => m.Groups["match"].Value)
.ToList()
.ForEach(s => Console.WriteLine(s));
Я нашел регулярное выражение в этом ответе весьма полезным. Чтобы он работал в C#, вам нужно использовать класс MatchCollection.
//need to escape \s
string pattern = "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'";
MatchCollection parsedStrings = Regex.Matches(line, pattern);
for (int i = 0; i < parsedStrings.Count; i++)
{
//print parsed strings
Console.Write(parsedStrings[i].Value + " ");
}
Console.WriteLine();
Это регулярное выражение будет разделено в зависимости от приведенного выше случая, хотя оно не удаляет кавычки или лишние пробелы, поэтому вы можете захотеть выполнить некоторую постобработку ваших строк. Это должно правильно держать строки в кавычках вместе, хотя.
"[^"]+"|\s?\w+?\s
С небольшим количеством беспорядка, обычные языки могут отслеживать четное / нечетное подсчет кавычек, но если ваши данные могут включать в себя экранированные кавычки (\"), то у вас есть реальная проблема с производством или пониманием регулярного выражения, которое будет обрабатывать это правильно,
РЕДАКТИРОВАТЬ: Извините за мой предыдущий пост, это, очевидно, возможно.
Для обработки всех не алфавитно-цифровых символов вам нужно что-то вроде этого:
MatchCollection matchCollection = Regex.Matches(input, @"(?<match>[^""\s]+)|\""(?<match>[^""]*)""");
foreach (Match match in matchCollection)
{
yield return match.Groups["match"].Value;
}
вы можете сделать foreach умнее, если используете.Net >2.0
Shaun,
Я считаю, что следующее регулярное выражение должно сделать это
(?<=")\w[\w\s]*(?=")|\w+
С Уважением,
Ливен
Посмотрите на разделенную функцию LSteinle, которая поддерживает классификаторы текста в проекте Code
Вот фрагмент из его проекта, который вас интересует.
using System.Text.RegularExpressions;
public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)
{
string _Statement = String.Format("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))",
Regex.Escape(delimiter), Regex.Escape(qualifier));
RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;
if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;
Regex _Expression = New Regex(_Statement, _Options);
return _Expression.Split(expression);
}
Просто следите за вызовом этого в цикле, поскольку он создает и компилирует оператор Regex каждый раз, когда вы вызываете его. Поэтому, если вам нужно вызывать его несколько раз, я бы посмотрел на создание кеша Regex.
Если вы хотите взглянуть на общее решение этой проблемы в форме бесплатного объекта javascript с открытым исходным кодом, вы можете посетить http://splitterjsobj.sourceforge.net/ для демонстрации (и загрузки), Объект имеет следующие особенности:
- Пары определяемых пользователем символов кавычек могут использоваться для экранирования (не допускайте разделения внутри кавычек). Кавычки могут быть экранированы с помощью пользовательского escape-символа и / или "двойным кавычкой". Экранирующая буква может быть экранирована (сама с собой). В одном из 5 выходных массивов (свойств объекта) выход не экранирован. (Например, если escape-символ = /, "a///"b"не экранируется как a/"b)
- Разбить на массив разделителей; разбирать файл за один звонок. (Выходные массивы будут вложенными.)
- Все escape-последовательности, распознаваемые JavaScript, могут быть оценены во время процесса разделения и / или в предварительном процессе.
- Функциональность обратного вызова
- Кросс-браузерная согласованность
Объект также доступен в виде плагина jQuery, но как новый пользователь на этом сайте я могу включить только одну ссылку в это сообщение.
Мне нужно поддерживать вложение, чтобы ничего из этого у меня не сработало. Я отказался от попыток сделать это через Regex и просто закодировал:
public static Argument[] ParseCmdLine(string args) {
List<string> ls = new List<string>();
StringBuilder sb = new StringBuilder(128);
// support quoted text nesting up to 8 levels deep
Span<char> quoteChar = stackalloc char[8];
int quoteLevel = 0;
for (int i = 0; i < args.Length; ++i) {
char ch = args[i];
switch (ch) {
case ' ':
if (quoteLevel == 0) {
ls.Add(sb.ToString());
sb.Clear();
break;
}
goto default;
case '"':
case '\'':
if (quoteChar[quoteLevel] == ch) {
--quoteLevel;
} else {
quoteChar[++quoteLevel] = ch;
}
goto default;
default:
sb.Append(ch);
break;
}
}
if (sb.Length > 0) { ls.Add(sb.ToString()); sb.Clear(); }
return Arguments.ParseCmdLine(ls.ToArray());
}
И вот дополнительный код для синтаксического анализа аргументов командной строки для объектов:
public struct Argument {
public string Prefix;
public string Name;
public string Eq;
public string QuoteType;
public string Value;
public string[] ToArray() => this.Eq == " " ? new string[] { $"{Prefix}{Name}", $"{QuoteType}{Value}{QuoteType}" } : new string[] { this.ToString() };
public override string ToString() => $"{Prefix}{Name}{Eq}{QuoteType}{Value}{QuoteType}";
}
private static readonly Regex RGX_MatchArg = new Regex(@"^(?<prefix>-{1,2}|\/)(?<name>[a-zA-Z][a-zA-Z_-]*)(?<assignment>(?<eq>[:= ]|$)(?<quote>[""'])?(?<value>.+?)(?:\k<quote>|\s*$))?");
private static readonly Regex RGX_MatchQuoted = new Regex(@"(?<quote>[""'])?(?<value>.+?)(?:\k<quote>|\s*$)");
public static Argument[] ParseCmdLine(string[] rawArgs) {
int count = 0;
Argument[] pairs = new Argument[rawArgs.Length];
int i = 0;
while(i < rawArgs.Length) {
string current = rawArgs[i];
i+=1;
Match matches = RGX_MatchArg.Match(current);
Argument arg = new Argument();
arg.Prefix = matches.Groups["prefix"].Value;
arg.Name = matches.Groups["name"].Value;
arg.Value = matches.Groups["value"].Value;
if(!string.IsNullOrEmpty(arg.Value)) {
arg.Eq = matches.Groups["eq"].Value;
arg.QuoteType = matches.Groups["quote"].Value;
} else if ((i < rawArgs.Length) && !rawArgs[i].StartsWith('-') && !rawArgs[i].StartsWith('/')) {
arg.Eq = " ";
Match quoted = RGX_MatchQuoted.Match(rawArgs[i]);
arg.QuoteType = quoted.Groups["quote"].Value;
arg.Value = quoted.Groups["value"].Value;
i+=1;
}
if(string.IsNullOrEmpty(arg.QuoteType) && arg.Value.IndexOfAny(new char[] { ' ', '/', '\\', '-', '=', ':' }) >= 0) {
arg.QuoteType = "\"";
}
pairs[count++] = arg;
}
return pairs.Slice(0..count);
}
public static ILookup<string, Argument> ToLookup(this Argument[] args) => args.ToLookup((arg) => arg.Name, StringComparer.OrdinalIgnoreCase);
}
Он может анализировать все виды вариантов аргументов:
-test -environment staging /DEqTest=avalue /Dcolontest:anothervalue /DwithSpaces="heys: guys" /slashargflag -action="Do: 'The Thing'" -action2 "do: 'Do: \"The Thing\"'" -init
Просто нужно чередовать вложенные кавычки между разными типами котировок.