Лучший способ разбора пространственно разделенного текста

У меня есть строка, подобная этой

 /c SomeText\MoreText "Some Text\More Text\Lol" SomeText

Я хочу его tokenize, но я не могу просто разбить на пробелы. Я придумал несколько уродливый парсер, который работает, но мне интересно, есть ли у кого-то более элегантный дизайн.

Это в С# btw.

РЕДАКТИРОВАТЬ: Моя уродливая версия, в то время как уродливая, - O (N) и может быть быстрее, чем использование RegEx.

private string[] tokenize(string input)
{
    string[] tokens = input.Split(' ');
    List<String> output = new List<String>();

    for (int i = 0; i < tokens.Length; i++)
    {
        if (tokens[i].StartsWith("\""))
        {
            string temp = tokens[i];
            int k = 0;
            for (k = i + 1; k < tokens.Length; k++)
            {
                if (tokens[k].EndsWith("\""))
                {
                    temp += " " + tokens[k];
                    break;
                }
                else
                {
                    temp += " " + tokens[k];
                }
            }
            output.Add(temp);
            i = k + 1;
        }
        else
        {
            output.Add(tokens[i]);
        }
    }

    return output.ToArray();            
}

Ответ 1

Компьютерный термин для того, что вы делаете, лексический анализ; прочитайте, что для хорошего резюме этой общей задачи.

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

Самый простой способ сделать это - определить слово как регулярное выражение:

([^"^\s]+)\s*|"([^"]+)"\s*

В этом выражении указано, что "слово" представляет собой либо (1) нецитирующий, небелый текст, окруженный пробелом, либо (2) текст без кавычек, окруженный кавычками (за которым следуют некоторые пробелы). Обратите внимание на использование скобок для выделения желаемого текста.

Вооруженный этим регулярным выражением, ваш алгоритм прост: найдите свой текст для следующего "слова", как определено скобками и верните его. Повторяйте это, пока не закончите "слова".

Здесь самый простой бит рабочего кода, который я мог бы найти в VB.NET. Обратите внимание, что мы должны проверять обе группы для данных, так как есть два набора скобок.

Dim token As String
Dim r As Regex = New Regex("([^""^\s]+)\s*|""([^""]+)""\s*")
Dim m As Match = r.Match("this is a ""test string""")

While m.Success
    token = m.Groups(1).ToString
    If token.length = 0 And m.Groups.Count > 1 Then
        token = m.Groups(2).ToString
    End If
    m = m.NextMatch
End While

Примечание 1: Ответ Уилла, выше, та же идея, что и эта. Надеюсь, этот ответ объяснит детали за сценой немного лучше:)

Ответ 2

В пространстве имен Microsoft.VisualBasic.FileIO(в Microsoft.VisualBasic.dll) есть TextFieldParser, который вы можете использовать для разбиения по разделительному пространству. Он обрабатывает строки в кавычках (т.е. "Это один токен" thisistokentwo).

Обратите внимание, только потому, что DLL говорит, что VisualBasic не означает, что вы можете использовать его только в проекте VB. Его часть всей Структуры.

Ответ 3

Существует подход к государственному компьютеру.

    private enum State
    {
        None = 0,
        InTokin,
        InQuote
    }

    private static IEnumerable<string> Tokinize(string input)
    {
        input += ' '; // ensure we end on whitespace
        State state = State.None;
        State? next = null; // setting the next state implies that we have found a tokin
        StringBuilder sb = new StringBuilder();
        foreach (char c in input)
        {
            switch (state)
            {
                default:
                case State.None:
                    if (char.IsWhiteSpace(c))
                        continue;
                    else if (c == '"')
                    {
                        state = State.InQuote;
                        continue;
                    }
                    else
                        state = State.InTokin;
                    break;
                case State.InTokin:
                    if (char.IsWhiteSpace(c))
                        next = State.None;
                    else if (c == '"')
                        next = State.InQuote;
                    break;
                case State.InQuote:
                    if (c == '"')
                        next = State.None;
                    break;
            }
            if (next.HasValue)
            {
                yield return sb.ToString();
                sb = new StringBuilder();
                state = next.Value;
                next = null;
            }
            else
                sb.Append(c);
        }
    }

Его можно легко расширить для таких вещей, как вложенные кавычки и экранирование. Возврат IEnumerable<string> позволяет вашему коду анализировать только столько, сколько вам нужно. Нет никаких реальных недостатков такого ленивого подхода, поскольку строки неизменяемы, поэтому вы знаете, что input не изменится, прежде чем вы проанализировали все это.

Смотрите: http://en.wikipedia.org/wiki/Automata-Based_Programming

Ответ 4

Вы также можете посмотреть регулярные выражения. Это может помочь вам. Вот пример, сорванный с MSDN...

using System;
using System.Text.RegularExpressions;

public class Test
{

    public static void Main ()
    {

        // Define a regular expression for repeated words.
        Regex rx = new Regex(@"\b(?<word>\w+)\s+(\k<word>)\b",
          RegexOptions.Compiled | RegexOptions.IgnoreCase);

        // Define a test string.        
        string text = "The the quick brown fox  fox jumped over the lazy dog dog.";

        // Find matches.
        MatchCollection matches = rx.Matches(text);

        // Report the number of matches found.
        Console.WriteLine("{0} matches found in:\n   {1}", 
                          matches.Count, 
                          text);

        // Report on each match.
        foreach (Match match in matches)
        {
            GroupCollection groups = match.Groups;
            Console.WriteLine("'{0}' repeated at positions {1} and {2}",  
                              groups["word"].Value, 
                              groups[0].Index, 
                              groups[1].Index);
        }

    }

}
// The example produces the following output to the console:
//       3 matches found in:
//          The the quick brown fox  fox jumped over the lazy dog dog.
//       'The' repeated at positions 0 and 4
//       'fox' repeated at positions 20 and 25
//       'dog' repeated at positions 50 and 54

Ответ 5

Craig - правильные регулярные выражения. Regex.Split может быть более кратким для ваших нужд.

Ответ 6

[^\т] +\т | "[^" ] + "\ т

использование Regex определенно выглядит как лучшая ставка, однако эта только возвращает всю строку. Я пытаюсь настроить его, но пока не так много удачи.

string[] tokens = System.Text.RegularExpressions.Regex.Split(this.BuildArgs, @"[^\t]+\t|""[^""]+""\t");