csv - हरण - संयोजक चिन्ह




एक सीएसवी फ़ाइल में अल्पविराम से निपटना (14)

मैं एक सीएसवी फ़ाइल को कैसे संभालना है, उसके बारे में सुझावों की तलाश कर रहा हूं, फिर हमारे ग्राहकों द्वारा अपलोड किया गया है, और उसमें कंपनी के नाम की तरह मूल्य में कॉमा हो सकता है।

हम जिन विचारों को देख रहे हैं उनमें से कुछ हैं: उद्धृत पहचानकर्ता (मूल्य "," मान "," आदि) या एक का उपयोग कर | एक अल्पविराम के बजाय। सबसे बड़ी समस्या यह है कि हमें इसे आसान बनाना है, या ग्राहक इसे नहीं करेगा।


2017 के लिए, सीएसवी पूरी तरह से निर्दिष्ट है - आरएफसी 4180।

यह एक बहुत ही सामान्य विनिर्देश है, और पूरी तरह से कई पुस्तकालयों ( github.com/Flinesoft/CSVImporter ) द्वारा कवर किया गया है।

बस किसी भी आसानी से उपलब्ध सीएसवी लाइब्रेरी का उपयोग करें - जो आरएफसी 4180 कहना है।

वास्तव में सीएसवी प्रारूप और कॉमा को संभालने के लिए एक कल्पना है:

लाइन ब्रेक (सीआरएलएफ), डबल कोट्स और कॉमा युक्त फ़ील्ड डबल-कोट्स में संलग्न किए जाने चाहिए।

http://tools.ietf.org/html/rfc4180

तो, मूल्य foo और bar,baz , आप यह करते हैं:

foo,"bar,baz"

विचार करने के लिए एक और महत्वपूर्ण आवश्यकता (spec से भी):

यदि फ़ील्ड को घेरने के लिए डबल-कोट्स का उपयोग किया जाता है, तो किसी फ़ील्ड के अंदर दिखाई देने वाला डबल-कोट एक और डबल कोट के साथ इसे से बचकर अवगत होना चाहिए। उदाहरण के लिए:

"aaa","b""bb","ccc"

आप इस तरह सीएसवी फ़ाइल पढ़ सकते हैं।

यह विभाजन का उपयोग करता है और रिक्त स्थान का ख्याल रखता है।

ArrayList List = new ArrayList();
static ServerSocket Server;
static Socket socket;
static ArrayList<Object> list = new ArrayList<Object>();


public static void ReadFromXcel() throws FileNotFoundException
{   
    File f = new File("Book.csv");
    Scanner in = new Scanner(f);
    int count  =0;
    String[] date;
    String[] name;
    String[] Temp = new String[10];
    String[] Temp2 = new String[10];
    String[] numbers;
    ArrayList<String[]> List = new ArrayList<String[]>();
    HashMap m = new HashMap();

         in.nextLine();
         date = in.nextLine().split(",");
         name = in.nextLine().split(",");
         numbers = in.nextLine().split(",");
         while(in.hasNext())
         {
             String[] one = in.nextLine().split(",");
             List.add(one);
         }
         int xount = 0;
         //Making sure the lines don't start with a blank
         for(int y = 0; y<= date.length-1; y++)
         {
             if(!date[y].equals(""))
             {   
                 Temp[xount] = date[y];
                 Temp2[xount] = name[y];
                 xount++;
             }
         }

         date = Temp;
         name =Temp2;
         int counter = 0;
         while(counter < List.size())
         {
             String[] list = List.get(counter);
             String sNo = list[0];
             String Surname = list[1];
             String Name = list[2];
             for(int x = 3; x < list.length; x++)
             {           
                 m.put(numbers[x], list[x]);
             }
            Object newOne = new newOne(sNo, Name, Surname, m, false);
             StudentList.add(s);
             System.out.println(s.sNo);
             counter++;
         }

आप वैकल्पिक "delimiters" जैसे ";" का उपयोग कर सकते हैं या "|" लेकिन सबसे सरल शायद उद्धरण हो सकता है जो अधिकांश (सभ्य) सीएसवी पुस्तकालयों और सबसे सभ्य स्प्रैडशीट्स द्वारा समर्थित है।

सीएसवी डिलीमीटर पर अधिक जानकारी और डिलीमीटर का वर्णन करने और उद्धरण देने के लिए मानक प्रारूप के लिए एक नमूना इस वेबपृष्ठ को देखने के लिए


किसी भी अच्छी तरह से बनाए गए सीएसवी (.net) - CsvHelper निपटने के लिए न्यूजेट के माध्यम से एक लाइब्रेरी उपलब्ध है

कक्षा में मानचित्र करने के लिए उदाहरण:

var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>();

व्यक्तिगत फ़ील्ड पढ़ने के लिए उदाहरण:

var csv = new CsvReader( textReader );
while( csv.Read() )
{
    var intField = csv.GetField<int>( 0 );
    var stringField = csv.GetField<string>( 1 );
    var boolField = csv.GetField<bool>( "HeaderName" );
}

क्लाइंट को फ़ाइल प्रारूप को चलाने दें:
, मानक फ़ील्ड डेलीमीटर है, " मानक मान है जो फ़ील्ड से बचने के लिए उपयोग किया जाता है जिसमें एक डिलीमीटर, उद्धरण या रेखा समाप्त होती है।

खेतों के लिए (उदाहरण के लिए) # उपयोग करने के लिए और ' भागने के लिए:

var csv = new CsvReader( textReader );
csv.Configuration.Delimiter = "#";
csv.Configuration.Quote = ''';
// read the file however meets your needs

अधिक दस्तावेज़ीकरण


जैसा कि अन्य ने कहा है, आपको उद्धरणों को शामिल करने वाले मूल्यों से बचने की आवश्यकता है। यहां सीए में एक छोटा सीएसवी रीडर है जो एम्बेडेड कोट्स और कैरिज रिटर्न समेत उद्धृत मानों का समर्थन करता है।

वैसे, यह यूनिट-परीक्षण कोड है। मैं इसे अभी पोस्ट कर रहा हूं क्योंकि यह सवाल बहुत कुछ प्रतीत होता है और अन्य सरल सीएसवी समर्थन करते समय अन्य लाइब्रेरी नहीं चाहते हैं।

आप इसे निम्नानुसार उपयोग कर सकते हैं:

using System;
public class test
{
    public static void Main()
    {
        using ( CsvReader reader = new CsvReader( "data.csv" ) )
        {
            foreach( string[] values in reader.RowEnumerator )
            {
                Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
            }
        }
        Console.ReadLine();
    }
}

यहां कक्षाएं हैं। ध्यान दें कि आप वैध सीएसवी लिखने के लिए Csv.Escape फ़ंक्शन का भी उपयोग कर सकते हैं।

using System.IO;
using System.Text.RegularExpressions;

public sealed class CsvReader : System.IDisposable
{
    public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
    {
    }

    public CsvReader( Stream stream )
    {
        __reader = new StreamReader( stream );
    }

    public System.Collections.IEnumerable RowEnumerator
    {
        get {
            if ( null == __reader )
                throw new System.ApplicationException( "I can't start reading without CSV input." );

            __rowno = 0;
            string sLine;
            string sNextLine;

            while ( null != ( sLine = __reader.ReadLine() ) )
            {
                while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
                    sLine += "\n" + sNextLine;

                __rowno++;
                string[] values = rexCsvSplitter.Split( sLine );

                for ( int i = 0; i < values.Length; i++ )
                    values[i] = Csv.Unescape( values[i] );

                yield return values;
            }

            __reader.Close();
        }
    }

    public long RowIndex { get { return __rowno; } }

    public void Dispose()
    {
        if ( null != __reader ) __reader.Dispose();
    }

    //============================================


    private long __rowno = 0;
    private TextReader __reader;
    private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
    private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}

public static class Csv
{
    public static string Escape( string s )
    {
        if ( s.Contains( QUOTE ) )
            s = s.Replace( QUOTE, ESCAPED_QUOTE );

        if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
            s = QUOTE + s + QUOTE;

        return s;
    }

    public static string Unescape( string s )
    {
        if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
        {
            s = s.Substring( 1, s.Length - 2 );

            if ( s.Contains( ESCAPED_QUOTE ) )
                s = s.Replace( ESCAPED_QUOTE, QUOTE );
        }

        return s;
    }


    private const string QUOTE = "\"";
    private const string ESCAPED_QUOTE = "\"\"";
    private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}

जैसा कि हार्पो के उत्तर में मेरी टिप्पणी में बताया गया है, उनका समाधान अच्छा है और ज्यादातर मामलों में काम करता है, हालांकि कुछ परिदृश्यों में जब कॉमा एक-दूसरे के निकट सीधे होते हैं तो यह अल्पविरामों पर विभाजित होने में विफल रहता है।

यह रेगेक्स स्ट्रिंग के कारण अप्रत्याशित रूप से वर्टैबिम स्ट्रिंग के रूप में व्यवहार कर रहा है। इस व्यवहार को सही करने के लिए, रेगेक्स स्ट्रिंग में सभी "वर्णों को वर्टबैम से बचने के बिना मैन्युअल रूप से बचने की आवश्यकता है।

अर्थात। Regex मैन्युअल escapes का उपयोग कर यह होना चाहिए:

",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"

जो अनुवाद करता है ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"

एक वर्टैबिम स्ट्रिंग का उपयोग करते समय @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" यह व्यवहार करता है जैसा कि आप देख सकते हैं कि क्या आप रेगेक्स डीबग करते हैं:

",(?=(?:[^"]*"[^"]*")*(?![^"]*"))"

तो संक्षेप में, मैं हैरपो के समाधान की सलाह देता हूं, लेकिन इस छोटे गोचा के लिए बाहर देखो!

यदि यह त्रुटि होती है (यदि आपके पास कॉलम की पूर्व-ज्ञात संख्या है) तो मैंने आपको सूचित करने के लिए सीएसवी रीडर में एक छोटा वैकल्पिक असफलता शामिल किया है:

if (_expectedDataLength > 0 && values.Length != _expectedDataLength) 
throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length));

इसे कन्स्ट्रक्टर के माध्यम से इंजेक्शन दिया जा सकता है:

public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
    _expectedDataLength = expectedDataLength;
}

फ़ील्ड को अलग करने के लिए एक टैब वर्ण (\ t) का उपयोग करें।


माइक्रोसॉफ्ट के लिए एक संदर्भ जोड़ें। VisualBasic (हाँ, यह VisualBasic कहता है लेकिन यह सी # में भी काम करता है - याद रखें कि अंत में यह सिर्फ आईएल है)।

CSV फ़ाइल को पार्स करने के लिए Microsoft.VisualBasic.FileIO.TextFieldParser क्लास का उपयोग करें यहां नमूना कोड है:

 Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv")
 parser.TextFieldType = FieldType.Delimited
 parser.SetDelimiters(",")      

   While Not parser.EndOfData         
      'Processing row             
      Dim fields() As String = parser.ReadFields         
      For Each field As String In fields             
         'TODO: Process field                   

      Next      
      parser.Close()
   End While 

मुझे लगता है कि इस समस्या का सबसे आसान समाधान ग्राहक को एक्सेल में सीएसवी खोलने के लिए है, और फिर ctrl + r को सभी अल्पविरामों को बदलने के लिए जो भी पहचानकर्ता आप चाहते हैं। यह ग्राहक के लिए बहुत आसान है और आपकी पसंद के डिलीमीटर को पढ़ने के लिए आपके कोड में केवल एक ही बदलाव की आवश्यकता है।


मैं आमतौर पर इसे अपने सीएसवी फाइलों में नियमित रूप से पार्सिंग करता हूं। मान लें कि 'लाइन' वैरिएबल एक सीएसवी फ़ाइल के भीतर एक पंक्ति है और सभी कॉलम के मान डबल कोट्स में संलग्न हैं। नीचे दी गई दो पंक्तियां निष्पादित करने के बाद, आपको 'मान' संग्रह में सीएसवी कॉलम मिलेंगे।

// The below two lines will split the columns as well as trim the DBOULE QUOTES around values but NOT within them
    string trimmedLine = line.Trim(new char[] { '\"' });
    List<string> values = trimmedLine.Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList();

यदि आप एक * निक्स-सिस्टम पर हैं , तो sed तक पहुंच है और केवल आपके सीएसवी के एक विशिष्ट क्षेत्र में एक या अधिक अवांछित कॉमा हो सकते हैं, आप निम्न एक-लाइनर का उपयोग इन्हें " आरएफसी 4180 धारा 2 प्रस्ताव करता है:

sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile

किन क्षेत्र पर अवांछित कॉमा (ओं) हो सकता है, इस पर निर्भर करता है कि आपको रेगेक्स (और प्रतिस्थापन) के कैप्चरिंग समूहों को बदलना / विस्तार करना है।
उपरोक्त उदाहरण उद्धरण चिह्नों में चौथे क्षेत्र (छः में से) को घेर लेगा।

इन --in-place-option साथ संयोजन में आप इन परिवर्तनों को सीधे फ़ाइल पर लागू कर सकते हैं।

सही रेगेक्स को "निर्माण" करने के लिए, अनुसरण करने के लिए एक सरल सिद्धांत है:

  1. आपके सीएसवी के हर क्षेत्र के लिए जो अनचाहे कॉमा के साथ मैदान से पहले आता है, आप एक [^,]*, लिखते हैं [^,]*, और उन्हें एक कैप्चरिंग समूह में एक साथ रख देते हैं।
  2. उस फ़ील्ड के लिए जिसमें अवांछित कॉमा (नों) लिखते हैं (.*)
  3. अनचाहे कॉमा (ओं) के साथ मैदान के बाद हर क्षेत्र के लिए आप एक लिखते हैं ,.* और उन्हें एक कैप्चरिंग समूह में एक साथ रख दें।

विशिष्ट फ़ील्ड के आधार पर विभिन्न संभावित रेगेक्स / प्रतिस्थापन का संक्षिप्त अवलोकन यहां दिया गया है। यदि नहीं दिया गया है, प्रतिस्थापन \1"\2"\3

([^,]*)(,.*)                     #first field, regex
"\1"\2                           #first field, substitution

(.*,)([^,]*)                     #last field, regex
\1"\2"                           #last field, substitution


([^,]*,)(.*)(,.*,.*,.*)          #second field (out of five fields)
([^,]*,[^,]*,)(.*)(,.*)          #third field (out of four fields)
([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields)

यदि आप अवांछित कॉमा को उद्धरण चिह्नों के साथ संलग्न करने के बजाय sed साथ हटाना चाहते हैं तो इस उत्तर का संदर्भ लें।


यदि आप पहिया को फिर से शुरू करना चाहते हैं, तो निम्नलिखित आपके लिए काम कर सकता है:

public static IEnumerable<string> SplitCSV(string line)
{
    var s = new StringBuilder();
    bool escaped = false, inQuotes = false;
    foreach (char c in line)
    {
        if (c == ',' && !inQuotes)
        {
            yield return s.ToString();
            s.Clear();
        }
        else if (c == '\\' && !escaped)
        {
            escaped = true;
        }
        else if (c == '"' && !escaped)
        {
            inQuotes = !inQuotes;
        }
        else
        {
            escaped = false;
            s.Append(c);
        }
    }
    yield return s.ToString();
}

यूरोप में हमें इस समस्या से पहले इस समस्या को पहले होना चाहिए। यूरोप में हम दशमलव बिंदु के लिए सभी कॉमा का उपयोग करते हैं। नीचे यह संख्याएं देखें:

| American      | Europe        |
| ------------- | ------------- |
| 0.5           | 0,5           |
| 3.14159265359 | 3,14159265359 |
| 17.54         | 17,54         |
| 175,186.15    | 175.186,15    |

तो सीएसवी फाइलों के लिए अल्पविराम विभाजक का उपयोग करना संभव नहीं है। इस कारण से, यूरोप में सीएसवी फाइलों को अर्धविराम ( ; ) से अलग किया जाता है।

माइक्रोसॉफ्ट एक्सेल जैसे प्रोग्राम अर्धविराम से फाइलें पढ़ सकते हैं और विभाजक से स्विच करना संभव है। आप एक टैब ( \t ) को विभाजक के रूप में भी उपयोग कर सकते हैं। रात्रिभोज उपयोगकर्ता से यह जवाब देखें।


सबसे पहले, आइए खुद से पूछें, "हमें सीएसवी फाइलों के लिए अलग-अलग कॉमा को संभालने की आवश्यकता क्यों महसूस होती है?"

मेरे लिए, जवाब है, "क्योंकि जब मैं एक CSV फ़ाइल में डेटा निर्यात करता हूं, तो एक फ़ील्ड में कॉमा गायब हो जाते हैं और मेरा फ़ील्ड कई फ़ील्ड में अलग हो जाता है जहां अल्पविराम मूल डेटा में दिखाई देता है।" (ऐसा इसलिए है क्योंकि अल्पविराम सीएसवी क्षेत्र विभाजक चरित्र है।)

आपकी स्थिति के आधार पर, अर्ध कोलन का उपयोग सीएसवी फील्ड विभाजक के रूप में भी किया जा सकता है।

मेरी आवश्यकताओं को देखते हुए, मैं एक चरित्र का उपयोग कर सकता हूं, उदाहरण के लिए, एकल कम-9 उद्धरण चिह्न, जो अल्पविराम की तरह दिखता है।

तो, यहां यह है कि आप इसे कैसे कर सकते हैं:

// Replace special CSV characters with single low-9 quotation mark
func Scrub(a interface{}) string {
    s := fmt.Sprint(a)
    s = strings.Replace(s, ",", "‚", -1)
    s = strings.Replace(s, ";", "‚", -1)
    return s
}

प्रतिस्थापन फ़ंक्शन में दूसरा कॉमा दिखने वाला वर्ण दशमलव 8218 है।

ध्यान रखें कि यदि आपके पास ऐसे ग्राहक हैं जिनके पास असीसी-पाठ पाठक हो सकते हैं कि यह decima 8218 वर्ण अल्पविराम की तरह नहीं दिखेगा। यदि यह आपका मामला है, तो मैं आरएफसी 4128 प्रति डबल कोट्स के साथ अल्पविराम (या अर्धविराम) के साथ क्षेत्र के आसपास की सिफारिश करता हूं: https://tools.ietf.org/html/rfc4180





csv