Ответ 1
Лучший ответ в большинстве случаев, вероятно, @Jim Mischel's. TextFieldParser
кажется, именно то, что вы хотите для большинства обычных случаев - хотя он странным образом живет в пространстве имен Microsoft.VisualBasic
! Но этот случай не является обычным.
В прошлый раз, когда я столкнулся с вариацией этого вопроса, где мне нужно что-то необычное, я смущенно отказался от регулярных выражений и бросил быку чар за чаром. Иногда это не так неправильно, чтобы сделать. Расщепление строки не так сложно, если вы нажимаете байт.
Поэтому я переписал для этого случая как расширение строки. Я думаю, что это близко.
Заметьте, что "I was pooping in the door "Stinky", so I'll be damn",
это особенно неприятный случай. Без *** STINKY CONDITION ***
, приведенного ниже, вы получите, что I was pooping in the door "Stinky
как одно значение, so I'll be damn"
как другое.
Единственный способ добиться большего успеха, чем в любом анонимном странном случае сплиттера/экранирования, состоит в том, чтобы иметь какой-то алгоритм определения "обычного" числа столбцов в каждой строке, а затем проверять, в этом случае, поля фиксированной длины, такие как Ваша запись состояния AK
или другой возможный ориентир как своего рода нормализация обратного останова для нонконформистских столбцов. Но эта серьезная сумасшедшая логика, которая, вероятно, не нужна, так же увлекательно, как и кодирование. Как указывает @Vash, вам лучше следовать некоторому стандарту и писать немного более осторожно.
Но проблема здесь, вероятно, проще, чем это. Единственный лексически значимый случай - это случай в вашем примере - ",
- двойная кавычка, запятая, а затем пробел. Так, что проверяет код *** STINKY CONDITION ***
. Несмотря на это, этот код становится противнее чем я хотел бы, что означает, что у вас есть когда-либо странные крайние случаи, такие как "This is also stinky," afab","Now what?"
Черт, даже "A,"B","C"
не работают в этом коде сейчас, iirc, так как я рассматриваю начальные и конечные символы как экранированные pre- и постфиксированные. вернуться к комментарию @Vash!
Извиняюсь за все скобки за однострочные операторы if
, но я сейчас застрял в мире StyleCop. Я не обязательно предлагаю вам использовать это - то, что strictEscapeToSplitEvaluation
плюс STINKY CONDITION делает это немного сложным. Но стоит иметь в виду, что обычный синтаксический анализатор csv, который разбирается в кавычках, значительно проще в плане утомительности, но в остальном тривиален.
namespace YourFavoriteNamespace
{
using System;
using System.Collections.Generic;
using System.Text;
public static class Extensions
{
public static Queue<string> SplitSeeingQuotes(this string valToSplit, char splittingChar = ',', char escapeChar = '"',
bool strictEscapeToSplitEvaluation = true, bool captureEndingNull = false)
{
Queue<string> qReturn = new Queue<string>();
StringBuilder stringBuilder = new StringBuilder();
bool bInEscapeVal = false;
for (int i = 0; i < valToSplit.Length; i++)
{
if (!bInEscapeVal)
{
// Escape values must come immediately after a split.
// abc,"b,ca",cab has an escaped comma.
// abc,b"ca,c"ab does not.
if (escapeChar == valToSplit[i] && (!strictEscapeToSplitEvaluation || (i == 0 || (i != 0 && splittingChar == valToSplit[i - 1]))))
{
bInEscapeVal = true; // not capturing escapeChar as part of value; easy enough to change if need be.
}
else if (splittingChar == valToSplit[i])
{
qReturn.Enqueue(stringBuilder.ToString());
stringBuilder = new StringBuilder();
}
else
{
stringBuilder.Append(valToSplit[i]);
}
}
else
{
// Can't use switch b/c we're comparing to a variable, I believe.
if (escapeChar == valToSplit[i])
{
// Repeated escape always reduces to one escape char in this logic.
// So if you wanted "I'm ""double quote"" crazy!" to come out with
// the double double quotes, you're toast.
if (i + 1 < valToSplit.Length && escapeChar == valToSplit[i + 1])
{
i++;
stringBuilder.Append(escapeChar);
}
else if (!strictEscapeToSplitEvaluation)
{
bInEscapeVal = false;
}
// *** STINKY CONDITION ***
// Kinda defense, since only '", ' really makes sense.
else if ('"' == escapeChar && i + 2 < valToSplit.Length &&
valToSplit[i + 1] == ',' && valToSplit[i + 2] == ' ')
{
i = i+2;
stringBuilder.Append("\", ");
}
// *** EO STINKY CONDITION ***
else if (i+1 == valToSplit.Length || (i + 1 < valToSplit.Length && valToSplit[i + 1] == splittingChar))
{
bInEscapeVal = false;
}
else
{
stringBuilder.Append(escapeChar);
}
}
else
{
stringBuilder.Append(valToSplit[i]);
}
}
}
// NOTE: The 'captureEndingNull' flag is not tested.
// Catch null final entry? "abc,cab,bca," could be four entries, with the last an empty string.
if ((captureEndingNull && splittingChar == valToSplit[valToSplit.Length-1]) || (stringBuilder.Length > 0))
{
qReturn.Enqueue(stringBuilder.ToString());
}
return qReturn;
}
}
}
Вероятно, стоит упомянуть, что в "ответе", который вы дали себе, нет проблемы "вонючего" в строке примера. ; ^)
[Понимая, что мы спустя три года после того, как вы спросили,] я скажу, что ваш пример не такой безумный, как это делают люди. Я вижу желание обрабатывать escape-символы (в данном случае, "
) как escape-символы только в том случае, если они являются первым значением после разделительного символа или, после нахождения открывающего escape, останавливаются только в том случае, если вы находите escape-символ перед разделителем; в этом случае, сплиттер, очевидно ,
.
Если строка вашего csv - abc,bc"a,ca"b
, я ожидаю, что это означает, что у нас есть три значения: abc
, bc"a
и ca"b
.
То же самое в вашей "The sample ("adasdad") asdada"
- кавычки, которые не начинаются и не заканчиваются в значении ячейки, не являются escape-символами и не обязательно должны быть удвоены для сохранения значения. Таким образом, я добавил флаг strictEscapeToSplitEvaluation
здесь.
Наслаждаться. ; ^)