extras/CSVParser: Escaping support
[sdk] / extras / CSVParser.ec
1 #include <stdarg.h>
2
3 public import "ecere"
4
5 public int UnescapeString(char * d, char * s, int len)
6 {
7    int j = 0, k = 0;
8    char ch;
9    while(j < len && (ch = s[j]))
10    {
11       switch(ch)
12       {
13          case '\\':
14             switch((ch = s[++j]))
15             {
16                case 'n': d[k] = '\n'; break;
17                case 't': d[k] = '\t'; break;
18                case 'a': d[k] = '\a'; break;
19                case 'b': d[k] = '\b'; break;
20                case 'f': d[k] = '\f'; break;
21                case 'r': d[k] = '\r'; break;
22                case 'v': d[k] = '\v'; break;
23                case '\\': d[k] = '\\'; break;
24                case '\"': d[k] = '\"'; break;
25                case '\'': d[k] = '\''; break;
26                default: d[k] = '\\'; d[k] = ch;
27             }
28             break;
29          default:
30             d[k] = ch;
31       }
32       j++, k++;
33    }
34    d[k] = '\0';
35    return k;
36 }
37
38 // to be moved in ecere?
39 public class FileHandler
40 {
41    public File file;
42
43    ~FileHandler()
44    {
45       delete file;
46    }
47 }
48
49 public struct CSVParserParameters
50 {
51    char fieldSeparator;
52    char valueQuotes;
53    int expectedFieldCount;
54    bool tolerateNewLineInValues;
55    bool escaped;
56    bool lastFieldEndsWithNewLine;
57    //bool checkNulls;
58    //bool checkCurlies;
59 };
60
61 CSVParserParameters classicParameters = { ',', '\"', 0, false };
62
63 public struct CSVParserState
64 {
65    uint lineNum;
66    uint charNum;
67    uint rowNum;
68    uint fieldNum;
69 };
70
71 public class CSVParser : public FileHandler
72 {
73 public:
74    CSVParserParameters options { ',', '\"', 0, false };
75    CSVParserState info;
76
77    void PrintMessage(typed_object object, ...)
78    {
79       va_list args;
80       char buffer[4096];
81       va_start(args, object);
82       PrintStdArgsToBuffer(buffer, sizeof(buffer), object, args);
83       va_end(args);
84       OnMessage(buffer);
85    }
86
87    virtual void OnMessage(const String message)
88    {
89       ::PrintLn(this._class.name, ": ", message,
90             " lineNum=", info.lineNum,
91             " charNum=", info.charNum,
92             " rowNum=", info.rowNum,
93             " fieldNum=", info.fieldNum);
94    }
95
96    virtual bool OnRowStrings(Array<String> strings);
97
98    virtual void Process()
99    {
100       bool quoted = false, status = true;
101       bool escaped = false;
102       Array<String> values { };
103       bool started = false;
104       int start = 0, end = 0;
105       int readCount = 0;
106       Array<char> buffer { minAllocSize = 4096 };
107
108       info.charNum = 0;
109       info.lineNum = 0;
110       info.rowNum = 0;
111       info.fieldNum = 0;
112
113       while(!file.Eof() && status)
114       {
115          int c, offset = 0;
116
117          if(started)
118          {
119             offset = readCount - start;
120             if(offset > buffer.minAllocSize / 2)
121                buffer.minAllocSize += 4096;
122             memmove(&buffer[0], &buffer[start], offset);
123             end -= start;
124             start = 0;
125          }
126
127          readCount = offset + file.Read(&buffer[offset], 1, buffer.minAllocSize - offset);
128          for(c = offset; c < readCount && status; c++)
129          {
130             char ch = buffer[c];
131             if(quoted)
132             {
133                // For Git import...
134                bool inTextQuote = false;
135
136                if(options.lastFieldEndsWithNewLine && info.fieldNum == options.expectedFieldCount - 1 && ch == '\"' && info.charNum > 0)
137                   inTextQuote = true;
138
139                if(!inTextQuote && !escaped && ch == options.valueQuotes)
140                {
141                   quoted = false;
142                   end = c;
143                }
144                if(options.escaped && ch == '\\')
145                   escaped = true;
146                else
147                   escaped = false;
148             }
149             else
150             {
151                if(ch == options.valueQuotes)
152                {
153                   quoted = true;
154                   start = c + 1;
155                   started = true;
156                }
157                //else if(ch == options.fieldSeparator || ch == '\n')
158                else if(ch == options.fieldSeparator ||
159                      (ch == '\n' && (!options.tolerateNewLineInValues || info.fieldNum >= options.expectedFieldCount-1)))
160                {
161                   if(values.count < options.expectedFieldCount)
162                   {
163                      int len = started ? (end-start) : 0;
164                      String value = new char[len+1];
165                      if(options.escaped)
166                         UnescapeString(value, &buffer[start], len);
167                      else
168                      {
169                         memcpy(value, &buffer[start], len);
170                         value[len] = 0;
171                      }
172                      values.Add(value);
173                   }
174                   start = end = 0;
175                   started = false;
176                   info.fieldNum++;
177                   if(ch == '\n')
178                   {
179                      info.lineNum++;
180                      info.rowNum++;
181                      status = OnRowStrings(values);
182                      values.Free();
183                      info.fieldNum = 0;
184                   }
185                }
186                else if(ch == '\r');
187                else
188                {
189                   if(!started)
190                   {
191                      start = c;
192                      started = true;
193                   }
194                   end = c+1;
195                }
196             }
197             if(ch == '\r' || ch == '\n')
198                info.charNum = 0;
199             else
200                info.charNum++;
201          }
202       }
203       if(end > start)
204       {
205          int len = end-start;
206
207          String value = new char[len+1];
208          if(options.escaped)
209             UnescapeString(value, &buffer[start], len);
210          else
211          {
212             memcpy(value, &buffer[start], len);
213             value[len] = 0;
214          }
215          values.Add(value);
216       }
217       if(values.count && status)
218       {
219          status = OnRowStrings(values);
220          info.rowNum++;
221       }
222       values.Free();
223       delete values;
224    }
225 }