2013-04-25 11 views
10

ile çıkışsız çift tırnak içeren alanlar ile başa çıkmak TextFieldParser kullanarak bir CSV dosyasını almaya çalışıyorum. Belirli bir CSV dosyası, standart olmayan biçimlendirmesi nedeniyle sorunlara neden oluyor. Söz konusu CSV'nin alanları çift tırnak içine alınmış. Sorun, belirli bir alan içinde ek bir çift çıkışsız çift tırnak olduğunda görünür.TextFieldParser

Sorunu vurgulayan aşırı basitleştirilmiş bir test vakası. Çalıştığım gerçek CSV dosyaları, hepsi aynı şekilde biçimlendirilmiş değil ve düzinelerce alandan oluşuyor, bunlardan herhangi biri muhtemelen bu zor biçimlendirme sorunlarını içeriyor olabilir.

TextReader reader = new StringReader("\"Row\",\"Test String\"\n" + 
    "\"1\",\"This is a test string. It is parsed correctly.\"\n" + 
    "\"2\",\"This is a test string with a comma, which is parsed correctly\"\n" + 
    "\"3\",\"This is a test string with double \"\"double quotes\"\". It is parsed correctly\"\n" + 
    "\"4\",\"This is a test string with 'single quotes'. It is parsed correctly\"\n" + 
    "5,This is a test string with fields that aren't enclosed in double quotes. It is parsed correctly.\n" + 
    "\"6\",\"This is a test string with single \"double quotes\". It can't be parsed.\""); 

using (TextFieldParser parser = new TextFieldParser(reader)) 
{ 
    parser.Delimiters = new[] { "," }; 
    while (!parser.EndOfData) 
    { 
     string[] fields= parser.ReadFields(); 
     Console.WriteLine("This line was parsed as:\n{0},{1}", 
      fields[0], fields[1]); 
    } 
} 

düzgün TextFieldParser kullanarak biçimlendirme bu tip bir CSV ayrıştırmak için yine de var mı?

+5

Düzeltmeye çalışmamanız çok önemlidir. Bu uzun bir süre için kötü verilerden sizi sorumlu kılacaktır. Yanlış biçimlendirilmiş olduğu için dosyayı reddedin. Bu konuda sizi rahatsız ediyorsa, RFC-4180 uyumlu olmadığını belirtin. Bunu kolayca düzeltebilecek başka bir programcı var. –

+2

@HansPassant Bu, ideal ve "uygun" eylem rotası olsa da, çoğu zaman bir seçeneğimiz yok, örneğin kontrolümüz olmayan bir API'den dosya alırken veya önemli ve ihtiyaç duyduğumuz bir müşteride sadece "çalışmasını" sağlamak. – richard

cevap

5

o hatalı biçimlendirilmiş verileri ayrıştırmak için sizin sorumluluğunuz olmadığı. Bununla birlikte, Robustness Principle ile uyumlu olarak, bu durumla karşılaşan bir kişi belirli türdeki hatalı biçimlendirilmiş verileri işlemeye çalışabilir. Aşağıda yazdığım kod, soruda belirtilen veri kümesinde çalışır. Temel olarak, hatalı biçimlendirilmiş satırdaki ayrıştırıcı hatasını saptar, ilk karaktere göre çift tırnak sarılı olup olmadığını belirler ve sonra tüm sarma çift tırnaklarını elle böler/keser.

using (TextFieldParser parser = new TextFieldParser(reader)) 
{ 
    parser.Delimiters = new[] { "," }; 

    while (!parser.EndOfData) 
    { 
     string[] fields = null; 
     try 
     { 
      fields = parser.ReadFields(); 
     } 
     catch (MalformedLineException ex) 
     { 
      if (parser.ErrorLine.StartsWith("\"")) 
      { 
       var line = parser.ErrorLine.Substring(1, parser.ErrorLine.Length - 2); 
       fields = line.Split(new string[] { "\",\"" }, StringSplitOptions.None); 
      } 
      else 
      { 
       throw; 
      } 
     } 
     Console.WriteLine("This line was parsed as:\n{0},{1}", fields[0], fields[1]); 
    } 
} 

I ise, (örneğin bir alan değeri olan, çift tırnak bitişik virgül), ancak bu tür bir örnek, muhtemelen tam anlamıyla ayrıştırılamaz olacaktır başarısız bir patolojik örnek uydurmak mümkündür eminim Soruda verilen problem çizgisi, yanlış biçimlendirilmesine rağmen çözülebilirdir.

+2

Bu soruyu yayınladığımdan beri neredeyse tam iki yıl olduğu göz önüne alındığında, bunun ilk sorunumu çözüp çözemeyeceğinden emin değilim. Hans'ın önerisiyle devam ettim ve bir dosyaya daha yakın bir dosya talep ettim. Bu örnek vakalarımı çözdüğünden ve bir cevabı asla kabul etmediğim için, devam edip cevabınızı kabul ediyorum. Teşekkürler, beni başka bir DenverCoder9 olmaktan kurtardınız - https://xkcd.com/979/ – sglantz

+2

Ah, zorunlu XKCD :) Evet, eski bir soru açtığımı biliyordum, ama sizinkiyle çok benzer bir sorunum vardı ve Bir çözüm bulduğumda bunu paylaşmanın iyi olacağını düşündüm. –

0

Sadece el ile yapmak daha kolay olabilir ve kesinlikle daha fazla kontrol verecekti:

Düzenleme:

using System.IO; 

string[] csvFile = File.ReadAllLines(pathToCsv); 
foreach (string line in csvFile) 
{ 
    // get the first comma in the line 
    // everything before this index is the row number 
    // everything after is the row value 
    int firstCommaIndex = line.IndexOf(','); 

    //Note: SubString used here is (startIndex, length) 
    string row = line.Substring(0, firstCommaIndex+1); 
    string rowValue = line.Substring(firstCommaIndex+1).Trim(); 

    Console.WriteLine("This line was parsed as:\n{0},{1}", 
      row, rowValue); 
} 

: En açıklık Örneğin , ben hala elle ayrıştırılmasını taşıma öneririz alanlarda virgül izin vermeyen bir jenerik CSV için:

using System.IO; 

string[] csvFile = File.ReadAllLines(pathToCsv); 
foreach (string line in csvFile) 
{ 
    string[] fields = line.Split(','); 
    Console.WriteLine("This line was parsed as:\n{0},{1}", 
      fields[0], fields[1]); 
} 
+0

Örneğimi, TextFieldParser ürününü neden kullanacağımın açık olmadığı noktasında basitleştirmiş olabilirim. Bir virgül üzerinde basit bir bölünme, TextFieldParser kullanıldığında mevcut olmayan çeşitli farklı sorunların ortaya çıkmasına neden olacaktır. Ana örnek, belirli bir metin değeri içinde bir virgülün varlığıdır. TextFieldParser'ı kullanmanın yararlarını vurgulamak için daha karmaşık bir test dizisiyle sorunu güncelleyeceğim. – sglantz

+0

@sglantz: Güncellenen kod örneği. Hala bunu el ile yapmanın maksimum kontrolünü alacağınızı öneriyorum. Sorunu CSV ve diğer ayrıştırıcılarla buldum, oldukça genel olması için programlanmış olsa bile, onlarla çalışmayan verilere sahip olmak kolay. Yeni kod örneğinin okunması daha kolay olduğunu düşünüyorum, ancak C# normal ifadeleri ve Match sınıfını kullanarak da başarılabilir. –

+0

Yine de örnek, uğraştığım CSV dosyalarının karmaşıklığını hala taşımıyor gibi görünüyor. Sürekli olarak biçimlendirilmemişler. Virgül ve tırnaklar, dosyadaki 20'den fazla alanda görünebilir. TextFieldParser, farklı formatların çeşitliliği ile uğraşırken elle ayırma ve hatta normal ifadeler çok hızlı bir şekilde karmaşıklaşırken bu tutarsızlığı ele almakta iyidir. – sglantz

-1

HasFieldsEnclosed ayarlayın Dosyaya okumadan önce InQuotes = true TextFieldParser nesnesinde.

+1

Bu seçenek açılabilir ve TextParser yine de metni ayrıştıramaz. Sorun, alanların tırnak içine alınmaması değil, alanlar içerisinde iki alıntı kullanılarak uygun şekilde kaçmayan tırnaklar var. – sglantz

0

Çalışma Çözüm:

Hans passant tavsiyesine katılıyorum
using (TextFieldParser csvReader = new TextFieldParser(csv_file_path)) 
      { 
       csvReader.SetDelimiters(new string[] { "," }); 
       csvReader.HasFieldsEnclosedInQuotes = false; 
       string[] colFields = csvReader.ReadFields(); 

       while (!csvReader.EndOfData) 
       { 
        string[] fieldData = csvReader.ReadFields(); 
        for (i = 0; i < fieldData.Length; i++) 
        { 
         if (fieldData[i] == "") 
         { 
          fieldData[i] = null; 
         } 
         else 
         { 
          if (fieldData[i][0] == '"' && fieldData[i][fieldData[i].Length - 1] == '"') 
          { 
           fieldData[i] = fieldData[i].Substring(1, fieldData[i].Length - 2); 
          } 
         } 
        } 
        csvData.Rows.Add(fieldData); 
        } 
      } 
+0

Bu, alan değerlerinin içinde virgüller varsa işe yaramaz. –

+0

Açıklığa kavuşturmak için , test dizesi # 2 içindeki virgül alanların yanlış bölünmesine neden olur. –

0

HasFieldsEnclosedInQuotes = true değerini ayarlamıyorsanız, sonuçta (,) virgül içeriyorsa, sonuç sütunları daha fazla olacaktır. örn "Sütun1", "Süt2", "Col3" "Test1", 100, "Test1, Test2" "Test2", 200, "Test22" Bu dosya 3 sütun olmalıdır ancak ayrıştırma sırasında alacak 4 yanlış olan alanlar.