1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
// This code is in the public domain. I can't remember where I found it on the Web, but it
// didn't come with any license.
using System;
using System.Collections;
using System.IO;
using System.Text;
namespace CSVReader {
/// <summary>
/// A data-reader style interface for reading CSV files.
/// </summary>
public class CSVReader : IDisposable {
#region Private variables
private Stream stream;
private StreamReader reader;
#endregion
/// <summary>
/// Create a new reader for the given stream.
/// </summary>
/// <param name="s">The stream to read the CSV from.</param>
public CSVReader(Stream s) : this(s, null) { }
/// <summary>
/// Create a new reader for the given stream and encoding.
/// </summary>
/// <param name="s">The stream to read the CSV from.</param>
/// <param name="enc">The encoding used.</param>
public CSVReader(Stream s, Encoding enc) {
this.stream = s;
if (!s.CanRead) {
throw new CSVReaderException("Could not read the given CSV stream!");
}
reader = (enc != null) ? new StreamReader(s, enc) : new StreamReader(s);
}
/// <summary>
/// Creates a new reader for the given text file path.
/// </summary>
/// <param name="filename">The name of the file to be read.</param>
public CSVReader(string filename) : this(filename, null) { }
/// <summary>
/// Creates a new reader for the given text file path and encoding.
/// </summary>
/// <param name="filename">The name of the file to be read.</param>
/// <param name="enc">The encoding used.</param>
public CSVReader(string filename, Encoding enc)
: this(new FileStream(filename, FileMode.Open), enc) { }
/// <summary>
/// Returns the fields for the next row of CSV data (or null if at eof)
/// </summary>
/// <returns>A string array of fields or null if at the end of file.</returns>
public string[] GetCSVLine() {
string data = reader.ReadLine();
if (data == null) return null;
if (data.Length == 0) return new string[0];
ArrayList result = new ArrayList();
ParseCSVFields(result, data);
return (string[])result.ToArray(typeof(string));
}
// Parses the CSV fields and pushes the fields into the result arraylist
private void ParseCSVFields(ArrayList result, string data) {
int pos = -1;
while (pos < data.Length)
result.Add(ParseCSVField(data, ref pos));
}
// Parses the field at the given position of the data, modified pos to match
// the first unparsed position and returns the parsed field
private string ParseCSVField(string data, ref int startSeparatorPosition) {
if (startSeparatorPosition == data.Length-1) {
startSeparatorPosition++;
// The last field is empty
return "";
}
int fromPos = startSeparatorPosition + 1;
// Determine if this is a quoted field
if (data[fromPos] == '"') {
// If we're at the end of the string, let's consider this a field that
// only contains the quote
if (fromPos == data.Length-1) {
fromPos++;
return "\"";
}
// Otherwise, return a string of appropriate length with double quotes collapsed
// Note that FSQ returns data.Length if no single quote was found
int nextSingleQuote = FindSingleQuote(data, fromPos+1);
startSeparatorPosition = nextSingleQuote+1;
return data.Substring(fromPos+1, nextSingleQuote-fromPos-1).Replace("\"\"", "\"");
}
// The field ends in the next comma or EOL
int nextComma = data.IndexOf(',', fromPos);
if (nextComma == -1) {
startSeparatorPosition = data.Length;
return data.Substring(fromPos);
}
else {
startSeparatorPosition = nextComma;
return data.Substring(fromPos, nextComma-fromPos);
}
}
// Returns the index of the next single quote mark in the string
// (starting from startFrom)
private int FindSingleQuote(string data, int startFrom) {
int i = startFrom-1;
while (++i < data.Length)
if (data[i] == '"') {
// If this is a double quote, bypass the chars
if (i < data.Length-1 && data[i+1] == '"') {
i++;
continue;
}
else
return i;
}
// If no quote found, return the end value of i (data.Length)
return i;
}
/// <summary>
/// Disposes the CSVReader. The underlying stream is closed.
/// </summary>
public void Dispose() {
// Closing the reader closes the underlying stream, too
if (reader != null) reader.Close();
else if (stream != null)
stream.Close(); // In case we failed before the reader was constructed
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Exception class for CSVReader exceptions.
/// </summary>
public class CSVReaderException : ApplicationException {
/// <summary>
/// Constructs a new exception object with the given message.
/// </summary>
/// <param name="message">The exception message.</param>
public CSVReaderException(string message) : base(message) { }
}
}
|