Разделить строку с пробелами, если они не заключены в "кавычки"?

Чтобы сделать вещи простыми:

string streamR = sr.ReadLine();  // sr.Readline results in one "two two"

Я хочу сохранить их как две разные строки, удалить все пробелы EXCEPT для пробелов, найденных между кавычками. Поэтому мне нужно:

string 1 = one
string 2 = two two

До сих пор я обнаружил, что работает следующий код, но он удаляет пробелы внутри кавычек.

//streamR.ReadLine only has two strings
  string[] splitter = streamR.Split(' ');
    str1 = splitter[0];
    // Only set str2 if the length is >1
    str2 = splitter.Length > 1 ? splitter[1] : string.Empty;

Результат этого будет

one
two

Я просмотрел Регулярное выражение для разделения на пробелы, если только в кавычках, однако я не могу заставить регулярное выражение работать/понимать код, особенно, как разделить так что это две разные строки. Все коды там дают компиляционную ошибку (я использую System.Text.RegularExpressions)

Ответ 1

string input = "one \"two two\" three \"four four\" five six";
var parts = Regex.Matches(input, @"[\""].+?[\""]|[^ ]+")
                .Cast<Match>()
                .Select(m => m.Value)
                .ToList();

Ответ 2

Вы можете даже сделать это без Regex: выражение LINQ с String.Split может выполнить задание.

Вы можете разделить строку до ", а затем разделить только элементы с четным индексом в результирующем массиве на .

var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Для строки:

This is a test for "Splitting a string" that has white spaces, unless they are "enclosed within quotes"

Он дает результат:

This
is
a
test
for
Splitting a string
that
has
white
spaces,
unless
they
are
enclosed within quotes

UPDATE

string myString = "WordOne \"Word Two\"";
var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Console.WriteLine(result[0]);
Console.WriteLine(result[1]);
Console.ReadKey();

ОБНОВЛЕНИЕ 2

Как вы определяете цитированную часть строки?

Мы будем предполагать, что строка перед первым " не цитируется.

Затем строка, помещенная между первым " и перед вторым ", цитируется. Строка между вторым " и третьим " не цитируется. Строка между третьим и четвертым цитируется,...

Общее правило: Каждая строка между (2 * n-1) th (нечетное число) " и (2 * n) th (четное число) " цитируется. (1)

Каково отношение с String.Split?

String.Split с по умолчанию StringSplitOption (define as StringSplitOption.None) создает список из 1 строки, а затем добавляет новую строку в список для каждого найденного символа разделения.

Итак, перед первым " строка находится в индексе 0 в разбитом массиве, между первым и вторым ", строка находится в индексе 1 в массиве, между третьим и четвертым, индексом 2,...

Общее правило: строка между nth и (n + 1) th " находится в индексе n в массиве. (2)

Приведенные (1) и (2), мы можем заключить, что: Цитированная часть имеет нечетный индекс в разбитом массиве.

Ответ 3

Вы можете использовать класс TextFieldParser, который является частью пространства имен Microsoft.VisualBasic.FileIO. (Вам нужно добавить ссылку на Microsoft.VisualBasic к вашему проекту.):

string inputString = "This is \"a test\" of the parser.";

using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(inputString)))
{
    using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp = new TextFieldParser(ms))
    {
        tfp.Delimiters = new string[] { " " };
        tfp.HasFieldsEnclosedInQuotes = true;
        string[] output = tfp.ReadFields();

        for (int i = 0; i < output.Length; i++)
        {
            Console.WriteLine("{0}:{1}", i, output[i]);
        }
    }
}

Что генерирует вывод:

0:This
1:is
2:a test
3:of
4:the
5:parser.

Ответ 4

Так как пользовательский парсер может быть более подходящим для этого.

Это то, что я написал однажды, когда у меня было определенное (и очень странное) требование синтаксического анализа, которое включало скобки и пробелы, но оно достаточно общее, чтобы оно работало практически с любым разделителем и разделителем текста.

public static IEnumerable<String> ParseText(String line, Char delimiter, Char textQualifier)
{

    if (line == null)
        yield break;

    else
    {
        Char prevChar = '\0';
        Char nextChar = '\0';
        Char currentChar = '\0';

        Boolean inString = false;

        StringBuilder token = new StringBuilder();

        for (int i = 0; i < line.Length; i++)
        {
            currentChar = line[i];

            if (i > 0)
                prevChar = line[i - 1];
            else
                prevChar = '\0';

            if (i + 1 < line.Length)
                nextChar = line[i + 1];
            else
                nextChar = '\0';

            if (currentChar == textQualifier && (prevChar == '\0' || prevChar == delimiter) && !inString)
            {
                inString = true;
                continue;
            }

            if (currentChar == textQualifier && (nextChar == '\0' || nextChar == delimiter) && inString)
            {
                inString = false;
                continue;
            }

            if (currentChar == delimiter && !inString)
            {
                yield return token.ToString();
                token = token.Remove(0, token.Length);
                continue;
            }

            token = token.Append(currentChar);

        }

        yield return token.ToString();

    } 
}

Использование будет:

var parsedText = ParseText(streamR, ' ', '"');

Ответ 5

Есть только крошечная проблема с ответом Squazz. он работает для его строки, но не если вы добавите больше предметов. Например.

string myString = "WordOne \"Word Two\" Three"

В этом случае удаление последней кавычки даст нам 4 результата, а не три.

Это легко фиксируется, хотя.. просто посчитайте количество escape-символов, и если это неравномерно, разделите последнее (адаптируйте в соответствии с вашими требованиями..)

    public static List<String> Split(this string myString, char separator, char escapeCharacter)
    {
        int nbEscapeCharactoers = myString.Count(c => c == escapeCharacter);
        if (nbEscapeCharactoers % 2 != 0) // uneven number of escape characters
        {
            int lastIndex = myString.LastIndexOf("" + escapeCharacter, StringComparison.Ordinal);
            myString = myString.Remove(lastIndex, 1); // remove the last escape character
        }
        var result = myString.Split(escapeCharacter)
                             .Select((element, index) => index % 2 == 0  // If even index
                                                   ? element.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                                   : new string[] { element })  // Keep the entire item
                             .SelectMany(element => element).ToList();
        return result;
    }

Я также превратил его в метод расширения и сделал конфигурацию разделителя и escape-символа.

Ответ 6

OP хотел

... удалить все пробелы EXCEPT для найденных пробелов между кавычками

Решение от Cédric Bignon почти это сделало, но не принимало во внимание, что может быть нечетное количество кавычек. Начиная с проверки этого, а затем удаляя лишние, мы гарантируем, что мы прекратим расщепление, если элемент действительно заключен в кавычки.

string myString = "WordOne \"Word Two";
int placement = myString.LastIndexOf("\"", StringComparison.Ordinal);
if (placement >= 0)
myString = myString.Remove(placement, 1);

var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Console.WriteLine(result[0]);
Console.WriteLine(result[1]);
Console.ReadKey();

Кредит за логику идет к Седрику Биньону, я только добавил гарантию.