OSDN Git Service

CsvReaderのパーサ追加,プロトタイプ指定・型引数省略に対応
[karinto/karinto.git] / Karinto / CsvReader.cs
1 /*\r
2  *      Karinto Library Project\r
3  *\r
4  *      This software is distributed under a zlib-style license.\r
5  *      See license.txt for more information.\r
6  */\r
7 \r
8 using System;\r
9 using System.IO;\r
10 using System.Collections.Generic;\r
11 using System.Text;\r
12 using System.Text.RegularExpressions;\r
13 using System.Data;\r
14 \r
15 namespace Karinto\r
16 {\r
17     public class CsvReader : CsvReader<DataTable>\r
18     {\r
19         private CsvReader()\r
20         { \r
21         }\r
22 \r
23         public CsvReader(DataTable prototype)\r
24             : base(prototype)\r
25         {\r
26         }\r
27     }\r
28 \r
29     public class CsvReader<TDataTable> where TDataTable : DataTable, new()\r
30     {\r
31         #region private fields\r
32 \r
33         private string separator;\r
34         private Regex sepPattern;\r
35         private TDataTable prototype;\r
36         private DataColumnCollection cols;\r
37 \r
38         public delegate bool Parser(ref string input, out object output);\r
39         static private Dictionary<Type, Parser> parsers;\r
40         private Parser[] lineParser;\r
41 \r
42         #endregion\r
43 \r
44         #region constructors\r
45         static CsvReader()\r
46         {\r
47             parsers = new Dictionary<Type, Parser>();\r
48             parsers[typeof(Boolean)] = ParseBoolean;\r
49             parsers[typeof(Double)] = ParseDouble;\r
50             parsers[typeof(Single)] = ParseSingle;\r
51             parsers[typeof(Decimal)] = ParseDecimal;\r
52             parsers[typeof(Int32)] = ParseInt32;\r
53             parsers[typeof(Int64)] = ParseInt64;\r
54         }\r
55 \r
56         public CsvReader()\r
57             : this(new TDataTable())\r
58         {\r
59         }\r
60 \r
61         public CsvReader(TDataTable prototype)\r
62         {\r
63             Separator = @",";\r
64             this.prototype = (TDataTable)prototype.Clone();\r
65             cols = prototype.Columns;\r
66             lineParser = new Parser[cols.Count];\r
67             for (int i = 0; i < cols.Count; ++i)\r
68             {\r
69                 Type t = cols[i].DataType;\r
70                 if (parsers[t] != null)\r
71                 {\r
72                     lineParser[i] = parsers[t];\r
73                 }\r
74                 else if (t == typeof(DateTime))\r
75                 {\r
76                     lineParser[i] = ParseDateTime;\r
77                 }\r
78                 else\r
79                 {\r
80                     lineParser[i] = ParseString;\r
81                 }\r
82             }\r
83         }\r
84         #endregion\r
85 \r
86         #region properties\r
87 \r
88         /// <summary>\r
89         ///     区切り文字\r
90         /// </summary>\r
91         public string Separator\r
92         {\r
93             get\r
94             {\r
95                 return separator;\r
96             }\r
97             set\r
98             {\r
99                 separator = value;\r
100                 sepPattern = new Regex(separator, RegexOptions.Compiled);\r
101             }\r
102         }\r
103 \r
104         #endregion\r
105 \r
106         #region public methods\r
107 \r
108         public TDataTable Read(FilePath path)\r
109         {\r
110             TDataTable table = (TDataTable)prototype.Clone();\r
111             try\r
112             {\r
113                 using (FileStream fs = new FileStream(\r
114                     path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))\r
115                 {\r
116                     using (StreamReader reader = new StreamReader(fs))\r
117                     {\r
118                         while (reader.Peek() >= 0)\r
119                         {\r
120                             DataRow row = table.NewRow();\r
121                             string line = reader.ReadLine();\r
122                             SetRow(row, line);\r
123                             if (row.HasErrors)\r
124                             {\r
125                                 if (table.Rows.Count < 1)\r
126                                 {\r
127                                     SetHeader(table.Columns, line);\r
128                                 }\r
129                                 continue;\r
130                             }\r
131                             table.Rows.Add(row);\r
132                         }\r
133                     }\r
134                 }\r
135             }\r
136             catch (Exception ex)\r
137             {\r
138                 throw ex;\r
139             }\r
140             return table;\r
141         }\r
142 \r
143         public TDataTable ReadAll(FilePath path)\r
144         {\r
145             throw new NotImplementedException();\r
146             return null;\r
147         }\r
148 \r
149         public Parser GetParser(int column)\r
150         {\r
151             return lineParser[column];\r
152         }\r
153 \r
154         public Parser GetParser(string column)\r
155         {\r
156             return lineParser[prototype.Columns.IndexOf(column)];\r
157         }\r
158 \r
159         public Parser GetParser(DataColumn column)\r
160         {\r
161             return lineParser[prototype.Columns.IndexOf(column.ColumnName)];\r
162         }\r
163 \r
164         static public Parser GetParser(Type type)\r
165         {\r
166             return parsers[type];\r
167         }\r
168 \r
169         public void SetParser(int column, Parser parser)\r
170         {\r
171             lineParser[column] = parser;\r
172         }\r
173 \r
174         public void SetParser(string column, Parser parser)\r
175         {\r
176             lineParser[prototype.Columns.IndexOf(column)] = parser;\r
177         }\r
178 \r
179         public void SetParser(DataColumn column, Parser parser)\r
180         { \r
181             lineParser[prototype.Columns.IndexOf(column.ColumnName)] = parser;\r
182         }\r
183 \r
184         static public void SetParser(Type type, Parser parser)\r
185         {\r
186             parsers[type] = parser;\r
187         }\r
188 \r
189 \r
190 \r
191         #endregion\r
192 \r
193         #region private methods\r
194 \r
195         private void SetRow(DataRow row, string line)\r
196         {\r
197             for (int i = 0; i < lineParser.Length; ++i)\r
198             {\r
199                 object value;\r
200                 if (lineParser[i](ref line, out value))\r
201                 {\r
202                     row[i] = value;\r
203                 }\r
204                 else\r
205                 {\r
206                     row.RowError = line;\r
207                 }\r
208                 Match m = sepPattern.Match(line);\r
209                 line = line.Substring(m.Length);\r
210             }\r
211             return;\r
212         }\r
213 \r
214         private void SetHeader(DataColumnCollection columns, string line)\r
215         {\r
216             for (int i = 0; i < columns.Count; ++i)\r
217             {\r
218                 object value;\r
219                 if (ParseString(ref line, out value))\r
220                 {\r
221                     string name = value as string;\r
222                     Match comment = Regex.Match(name, @"^#\s*");\r
223                     columns[i].Caption = name.Substring(comment.Length);\r
224                 }\r
225                 else\r
226                 {\r
227                     return;\r
228                 }\r
229                 Match m = sepPattern.Match(line);\r
230                 line = line.Substring(m.Length);\r
231             }\r
232         }\r
233 \r
234         #region parsers\r
235 \r
236         static private bool ParseBoolean(ref string input, out object output)\r
237         {\r
238             Match m = Regex.Match(input, \r
239                         "^" + Boolean.TrueString, RegexOptions.IgnoreCase);\r
240             if (!m.Success)\r
241             {\r
242                 m = Regex.Match(input, \r
243                         "^" + Boolean.FalseString, RegexOptions.IgnoreCase);\r
244                 if (!m.Success)\r
245                 {\r
246                     output = false;\r
247                     return false;\r
248                 }\r
249             }\r
250             output = true;\r
251             input = input.Substring(m.Length);\r
252             return true;\r
253         }\r
254 \r
255         static private bool ParseDouble(ref string input, out object output)\r
256         {\r
257             Match m = RegexSet.DecimalFloat.Match(input);\r
258             if (!m.Success)\r
259             {\r
260                 output = 0;\r
261                 return false;\r
262             }\r
263             output = Double.Parse(m.Groups[1].Value);\r
264             input = input.Substring(m.Length);\r
265             return true;\r
266         }\r
267 \r
268         static private bool ParseSingle(ref string input, out object output)\r
269         {\r
270             Match m = RegexSet.DecimalFloat.Match(input);\r
271             if (!m.Success)\r
272             {\r
273                 output = 0;\r
274                 return false;\r
275             }\r
276             output = Single.Parse(m.Groups[1].Value);\r
277             input = input.Substring(m.Length);\r
278             return true;\r
279         }\r
280 \r
281         static private bool ParseDecimal(ref string input, out object output)\r
282         {\r
283             Match m = RegexSet.DecimalFloat.Match(input);\r
284             if (!m.Success)\r
285             {\r
286                 output = 0;\r
287                 return false;\r
288             }\r
289             output = Decimal.Parse(m.Groups[1].Value);\r
290             input = input.Substring(m.Length);\r
291             return true;\r
292         }\r
293 \r
294         static private bool ParseInt32(ref string input, out object output)\r
295         {\r
296             Match m = RegexSet.DecimalFloat.Match(input);\r
297             if (!m.Success)\r
298             {\r
299                 output = 0;\r
300                 return false;\r
301             }\r
302             output = (Int32)Double.Parse(m.Groups[1].Value);\r
303             input = input.Substring(m.Length);\r
304             return true;\r
305         }\r
306 \r
307         static private bool ParseInt64(ref string input, out object output)\r
308         {\r
309             Match m = RegexSet.DecimalFloat.Match(input);\r
310             if (!m.Success)\r
311             {\r
312                 output = 0;\r
313                 return false;\r
314             }\r
315             output = (Int64)Double.Parse(m.Groups[1].Value);\r
316             input = input.Substring(m.Length);\r
317             return true;\r
318         }\r
319 \r
320         private bool ParseDateTime(ref string input, out object output)\r
321         {\r
322             object value;\r
323             ParseString(ref input, out value);\r
324             DateTime dt;\r
325             if (DateTime.TryParse(value as string, out dt))\r
326             {\r
327                 output = dt;\r
328                 return true;\r
329             }\r
330             output = DBNull.Value;\r
331             return false;\r
332         }\r
333 \r
334         private bool ParseString(ref string input, out object output)\r
335         {\r
336             Match quotedMatch = RegexSet.QuotedString.Match(input);\r
337             if (quotedMatch.Success)\r
338             {\r
339                 string content = quotedMatch.Groups[1].Value;\r
340                 output = content.Replace("\"\"", "\"");\r
341                 input = input.Substring(quotedMatch.Length);\r
342                 return true;\r
343             }\r
344 \r
345             Match m = sepPattern.Match(input);\r
346             if (m.Success)\r
347             {\r
348                 output = input.Substring(0, m.Index);\r
349                 input = input.Substring(m.Index);\r
350             }\r
351             else\r
352             {\r
353                 output = input;\r
354                 input = "";\r
355             }\r
356             return true;\r
357         }\r
358         #endregion\r
359 \r
360         #endregion\r
361     }\r
362 }\r