Alguien pronto obtendrá una respuesta que hace esto con una sola expresión regular. No soy tan listo, pero solo por el bien del equilibrio, aquí hay una sugerencia que no usa una expresión completa. Basado en el viejo dicho de que cuando intentas resolver un problema con una expresión regular, entonces tienes dos problemas. :)
Personalmente dada mi falta de expresiones regulares-fu, lo haría uno de los siguientes:
- uso de un simple
Replace
escapar ninguna coma dentro cotizaciones con algo más basado en expresiones regulares (es decir, ","
). Luego puede hacer un simple string.Split()
en el resultado y deshacer cada elemento en la matriz resultante antes de usarlo. Esto es asqueroso En parte porque se trata de un doble manejo de todo, y en parte porque también usa expresiones regulares. Boooo!
- Parse it by hand, char by char. Convierta la cadena en una matriz de caracteres, luego repítala a través de ella, teniendo en cuenta si está "entre comillas" o no, y construya la matriz resultante de a a a la vez.
- Igual que la sugerencia anterior, pero usando un csv-analizador de alguien en Internet. El ejemplo que creo a continuación no pasa exactamente todas las pruebas de la especificación csv, por lo que solo es una guía para ilustrar mi punto.
Hay una buena posibilidad de que las opciones no regexes tengan un mejor rendimiento si están bien escritas, porque las expresiones regulares pueden ser un poco caras ya que escanean cadenas internamente buscando patrones.
Realmente, solo quería señalar que no tiene que usar una expresión regular. :)
Aquí hay una implementación bastante ingenua de mi segunda sugerencia. En mi PC, me alegra analizar un millón de cadenas de 15 columnas en poco más de 4,5 segundos.
public class ManualParser : IParser
{
public IEnumerable<string> Parse(string line)
{
if (string.IsNullOrWhiteSpace(line)) return new List<string>();
line = line.Trim();
if (line.Contains(",") == false) return new[] { line.Trim('"') };
if (line.Contains("\"") == false) return line.Split(',').Select(c => c.Trim());
bool withinQuotes = false;
var builder = new List<string>();
var trimChars = new[] { ' ', '"' };
int left = 0;
int right = 0;
for (right = 0; right < line.Length; right++)
{
char c = line[right];
if (c == '"')
{
withinQuotes = !withinQuotes;
continue;
}
if (c == ',' && !withinQuotes)
{
builder.Add(line.Substring(left, right - left).Trim(trimChars));
right++; // Jump the comma
left = right;
}
}
builder.Add(line.Substring(left, right - left).Trim(trimChars));
return builder;
}
}
Aquí hay algunas pruebas unitarias para ello:
[TestFixture]
public class ManualParserTests
{
[Test]
public void Parse_GivenStringWithNoQuotesAndNoCommas_ShouldReturnThatString()
{
// Arrange
var parser = new ManualParser();
// Act
string[] result = parser.Parse("This is my data").ToArray();
// Assert
Assert.AreEqual(1, result.Length, "Should only be one column returned");
Assert.AreEqual("This is my data", result[0], "Incorrect value is returned");
}
[Test]
public void Parse_GivenStringWithNoQuotesAndOneComma_ShouldReturnTwoColumns()
{
// Arrange
var parser = new ManualParser();
// Act
string[] result = parser.Parse("This is, my data").ToArray();
// Assert
Assert.AreEqual(2, result.Length, "Should be 2 columns returned");
Assert.AreEqual("This is", result[0], "First value is incorrect");
Assert.AreEqual("my data", result[1], "Second value is incorrect");
}
[Test]
public void Parse_GivenStringWithQuotesAndNoCommas_ShouldReturnColumnWithoutQuotes()
{
// Arrange
var parser = new ManualParser();
// Act
string[] result = parser.Parse("\"This is my data\"").ToArray();
// Assert
Assert.AreEqual(1, result.Length, "Should be 1 column returned");
Assert.AreEqual("This is my data", result[0], "Value is incorrect");
}
[Test]
public void Parse_GivenStringWithQuotesAndCommas_ShouldReturnColumnsWithoutQuotes()
{
// Arrange
var parser = new ManualParser();
// Act
string[] result = parser.Parse("\"This is\", my data").ToArray();
// Assert
Assert.AreEqual(2, result.Length, "Should be 2 columns returned");
Assert.AreEqual("This is", result[0], "First value is incorrect");
Assert.AreEqual("my data", result[1], "Second value is incorrect");
}
[Test]
public void Parse_GivenStringWithQuotesContainingCommasAndCommas_ShouldReturnColumnsWithoutQuotes()
{
// Arrange
var parser = new ManualParser();
// Act
string[] result = parser.Parse("\"This, is\", my data").ToArray();
// Assert
Assert.AreEqual(2, result.Length, "Should be 2 columns returned");
Assert.AreEqual("This, is", result[0], "First value is incorrect");
Assert.AreEqual("my data", result[1], "Second value is incorrect");
}
}
Y aquí es una aplicación de ejemplo que he probado el rendimiento con:
class Program
{
static void Main(string[] args)
{
RunTest();
}
private static void RunTest()
{
var parser = new ManualParser();
string csv = Properties.Resources.Csv;
var result = new StringBuilder();
var s = new Stopwatch();
for (int test = 0; test < 3; test++)
{
int lineCount = 0;
s.Start();
for (int i = 0; i < 1000000/50; i++)
{
foreach (var line in csv.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
{
string cur = line + s.ElapsedTicks.ToString();
result.AppendLine(parser.Parse(cur).ToString());
lineCount++;
}
}
s.Stop();
Console.WriteLine("Completed {0} lines in {1}ms", lineCount, s.ElapsedMilliseconds);
s.Reset();
result = new StringBuilder();
}
}
}
¿Tiene que usar expresiones regulares? – rownage
hui, Siéntase libre de a) comentar, b) votar yc) elegir la respuesta que más le ayuda. :) –