it-swarm.com.ru

C #, регулярные выражения: как анализировать значения, разделенные запятыми, где некоторые значения могут быть заключены в кавычки; сами строки содержат запятые

В C #, используя класс Regex, как анализировать значения, разделенные запятыми, где некоторые значения могут быть в кавычках самих строк, содержащих запятые?

using System ;
using System.Text.RegularExpressions ;

class  Example
    {
    public static void Main ( )
        {
        string  myString  =  "cat,dog,\"0 = OFF, 1 = ON\",lion,tiger,'R = red, G = green, B = blue',bear" ;
        Console.WriteLine ( "\nmyString is ...\n\t" + myString + "\n" ) ;
        Regex   regex  =  new Regex  (  "(?<=,(\"|\')).*?(?=(\"|\'),)|(^.*?(?=,))|((?<=,).*?(?=,))|((?<=,).*?$)"  )  ;
        Match   match  =  regex.Match ( myString ) ;
        int j = 0 ;
        while ( match.Success )
            {
            Console.WriteLine ( j++ + " \t" + match ) ;
            match  =  match.NextMatch() ;
            }
        }
    }

Вывод (частично) выглядит следующим образом:

0       cat
1       dog
2       "0 = OFF
3        1 = ON"
4       lion
5       tiger
6       'R = red
7        G = green
8        B = blue'
9       bear

Тем не менее, желательно вывод:

0       cat
1       dog
2       0 = OFF, 1 = ON
3       lion
4       tiger
5       R = red, G = green, B = blue
6       bear
9
JaysonFix

Попробуйте с этим регулярным выражением:

"[^"\r\n]*"|'[^'\r\n]*'|[^,\r\n]*

    Regex regexObj = new Regex(@"""[^""\r\n]*""|'[^'\r\n]*'|[^,\r\n]*");
    Match matchResults = regexObj.Match(input);
    while (matchResults.Success) 
    {
        Console.WriteLine(matchResults.Value);
        matchResults = matchResults.NextMatch();
    }

Результаты:

  • кошка
  • собака
  • "0 = ВЫКЛ, 1 = ВКЛ"
  • лев
  • тигр
  • "R = красный, G = зеленый, B = синий"
  • нести

Примечание: Это решение для регулярных выражений будет работать для вашего случая, однако я рекомендую вам использовать специализированную библиотеку, например FileHelpers .

23
CMS

Почему бы не прислушаться к советам экспертов и не катите свой собственный анализатор CSV .

Ваша первая мысль: "Мне нужно обрабатывать запятые внутри кавычек".

Ваша следующая мысль будет: "О, , мне нужно обрабатывать кавычки внутри кавычек. Кавычки, к которым вы обращаетесь. Двойные кавычки. Одинарные кавычки ..."

Это дорога к безумию. Не пиши свое. Найдите библиотеку с обширным охватом модульных тестов, которая поразит все сложные части и прошла через ад. Для .NET используйте бесплатный и открытый исходный код библиотека FileHelpers .

21
Judah Gabriel Himango

Просто добавляю решение, над которым я работал этим утром.

var regex = new Regex("(?<=^|,)(\"(?:[^\"]|\"\")*\"|[^,]*)");

foreach (Match m in regex.Matches("<-- input line -->"))
{
    var s = m.Value; 
}

Как видите, вам нужно вызвать regex.Matches () per line. Затем он вернет MatchCollection с тем же количеством элементов, что и у вас, как в столбцах. Свойство Value каждого совпадения, очевидно, является проанализированным значением.

Это все еще в стадии разработки, но, к счастью, он анализирует строки CSV, такие как:

2,3.03,"Hello, my name is ""Joshua""",A,B,C,,,D
7
Joshua

это не регулярное выражение, но я использовал Microsoft.VisualBasic.FileIO.TextFieldParser для выполнения этого для CSV-файлов. да, может показаться немного странным добавление ссылки на Microsoft.VisualBasic в приложение на C #, возможно, даже немного грязное, но эй, это работает.

7
kenwarner

Ах, RegEx. Теперь у вас есть две проблемы. ;)

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

Это работает, например:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        string myString = "cat,dog,\"0 = OFF, 1 = ON\",lion,tiger,'R = red, G = green,     B = blue',bear"; 
        Console.WriteLine("\nmyString is ...\n\t" + myString + "\n");
        CsvParser parser = new CsvParser(myString);

        Int32 lineNumber = 0;
        foreach (string s in parser)
        {
            Console.WriteLine(lineNumber + ": " + s);
        }

        Console.ReadKey();
    }
}

internal enum TokenType
{
    Comma,
    Quote,
    Value
}

internal class Token
{
    public Token(TokenType type, string value)
    {
        Value = value;
        Type = type;
    }

    public String Value { get; private set; }
    public TokenType Type { get; private set; }
}

internal class StreamTokenizer : IEnumerable<Token>
{
    private TextReader _reader;

    public StreamTokenizer(TextReader reader)
    {
        _reader = reader;    
    }

    public IEnumerator<Token> GetEnumerator()
    {
        String line;
        StringBuilder value = new StringBuilder();

        while ((line = _reader.ReadLine()) != null)
        {
            foreach (Char c in line)
            {
                switch (c)
                {
                    case '\'':
                    case '"':
                        if (value.Length > 0)
                        {
                            yield return new Token(TokenType.Value, value.ToString());
                            value.Length = 0;
                        }
                        yield return new Token(TokenType.Quote, c.ToString());
                        break;
                    case ',':
                       if (value.Length > 0)
                        {
                            yield return new Token(TokenType.Value, value.ToString());
                            value.Length = 0;
                        }
                        yield return new Token(TokenType.Comma, c.ToString());
                        break;
                    default:
                        value.Append(c);
                        break;
                }
            }

            // Thanks, dpan
            if (value.Length > 0) 
            {
                yield return new Token(TokenType.Value, value.ToString()); 
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

internal class CsvParser : IEnumerable<String>
{
    private StreamTokenizer _tokenizer;

    public CsvParser(Stream data)
    {
        _tokenizer = new StreamTokenizer(new StreamReader(data));
    }

    public CsvParser(String data)
    {
        _tokenizer = new StreamTokenizer(new StringReader(data));
    }

    public IEnumerator<string> GetEnumerator()
    {
        Boolean inQuote = false;
        StringBuilder result = new StringBuilder();

        foreach (Token token in _tokenizer)
        {
            switch (token.Type)
            {
                case TokenType.Comma:
                    if (inQuote)
                    {
                        result.Append(token.Value);
                    }
                    else
                    {
                        yield return result.ToString();
                        result.Length = 0;
                    }
                    break;
                case TokenType.Quote:
                    // Toggle quote state
                    inQuote = !inQuote;
                    break;
                case TokenType.Value:
                    result.Append(token.Value);
                    break;
                default:
                    throw new InvalidOperationException("Unknown token type: " +    token.Type);
            }
        }

        if (result.Length > 0)
        {
            yield return result.ToString();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
7
codekaizen

CSV не регулярно . Если ваш язык регулярных выражений не обладает достаточной мощностью для обработки состояния синтаксического анализа csv (маловероятно, что у MS нет), то любое чистое решение регулярных выражений представляет собой список ошибок, ожидающих появления при попадании на новый входной источник, которого нет вполне обрабатывается последним регулярным выражением.

Чтение CSV не так сложно записать как конечный автомат, поскольку грамматика проста, но даже в этом случае вы должны учитывать: заключенные в кавычки кавычки, запятые в кавычках, новые строки в кавычках, пустые поля.

Таким образом, вы, вероятно, должны просто использовать чей-то парсер CSV. Я рекомендую CSVReader для .Net

3
ShuggyCoUk

Функция:

    private List<string> ParseDelimitedString (string arguments, char delim = ',')
    {
        bool inQuotes = false;
        bool inNonQuotes = false; //used to trim leading WhiteSpace

        List<string> strings = new List<string>();

        StringBuilder sb = new StringBuilder();
        foreach (char c in arguments)
        {
            if (c == '\'' || c == '"')
            {
                if (!inQuotes)
                    inQuotes = true;
                else
                    inQuotes = false;
            }else if (c == delim)
            {
                if (!inQuotes)
                {
                    strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString());
                    sb.Remove(0, sb.Length);
                    inNonQuotes = false;
                }
                else
                {
                    sb.Append(c);
                }
            }
            else if ( !char.IsWhiteSpace(c) && !inQuotes && !inNonQuotes)  
            {
                if (!inNonQuotes) inNonQuotes = true;
                sb.Append(c);
            }
        }
        strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString());


        return strings;
    }

Использование

    string myString = "cat,dog,\"0 = OFF, 1 = ON\",lion,tiger,'R = red, G = green, B = blue',bear,         text";
    List<string> strings = ParseDelimitedString(myString);

    foreach( string s in strings )
            Console.WriteLine( s );

Результат:

cat
dog
0 = OFF, 1 = ON
lion
tiger
R = red, G = green, B = blue
bear
text
2
Partha Choudhury

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

И я согласен использовать библиотеку FileHelper, когда вы можете, однако эта библиотека требует, чтобы вы знали, как будут выглядеть ваши данные ... Мне нужен универсальный синтаксический анализатор.

Поэтому я обновил код до следующего и думал, что поделюсь ...

    static public List<string> ParseDelimitedString(string value, char delimiter)
    {
        bool inQuotes = false;
        bool inNonQuotes = false;
        bool secondQuote = false;
        char curQuote = '\0';

        List<string> results = new List<string>();

        StringBuilder sb = new StringBuilder();
        foreach (char c in value)
        {
            if (inNonQuotes)
            {
                // then quotes are just characters
                if (c == delimiter)
                {
                    results.Add(sb.ToString());
                    sb.Remove(0, sb.Length);
                    inNonQuotes = false;
                }
                else
                {
                    sb.Append(c);
                }
            }
            else if (inQuotes)
            {
                // then quotes need to be double escaped
                if ((c == '\'' && c == curQuote) || (c == '"' && c == curQuote))
                {
                    if (secondQuote)
                    {
                        secondQuote = false;
                        sb.Append(c);
                    }
                    else
                        secondQuote = true;
                }
                else if (secondQuote && c == delimiter)
                {
                    results.Add(sb.ToString());
                    sb.Remove(0, sb.Length);
                    inQuotes = false;
                }
                else if (!secondQuote)
                {
                    sb.Append(c);
                }
                else
                {
                    // bad,as,"user entered something like"this,poorly escaped,value
                    // just ignore until second delimiter found
                }
            }
            else
            {
                // not yet parsing a field
                if (c == '\'' || c == '"')
                {
                    curQuote = c;
                    inQuotes = true;
                    inNonQuotes = false;
                    secondQuote = false;
                }
                else if (c == delimiter)
                {
                    // blank field
                    inQuotes = false;
                    inNonQuotes = false;
                    results.Add(string.Empty);
                }
                else
                {
                    inQuotes = false;
                    inNonQuotes = true;
                    sb.Append(c);
                }
            }
        }

        if (inQuotes || inNonQuotes)
            results.Add(sb.ToString());

        return results;
    }
1
David Wayne Rasmussen

так как этот вопрос: регулярное выражение для анализа CSV с вложенными кавычками

отчеты здесь и являются гораздо более общими, и поскольку RegEx на самом деле не является правильным способом решения этой проблемы (то есть у меня было много проблем с катастрофическим возвратом ( http://www.regular-expressions.info/catastrophic .html )

вот простая реализация синтаксического анализатора в Python, а также

def csv_to_array(string):
    stack = []
    match = []
    matches = []

    for c in string:
        # do we have a quote or double quote?
        if c == "\"":
            # is it a closing match?
            if len(stack) > 0 and stack[-1] == c:
                stack.pop()
            else:
                stack.append(c)
        Elif (c == "," and len(stack) == 0) or (c == "\n"):
            matches.append("".join(match))
            match = []
        else:
            match.append(c)

    return matches
0
MrE