ecere/gui/GuiApplication: Fixed OS X compilation
[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                   if(buffer[c+1] == options.valueQuotes)
142                      c++;
143                   else
144                   {
145                      quoted = false;
146                      end = c;
147                   }
148                }
149                if(!escaped && options.escaped && ch == '\\')
150                   escaped = true;
151                else
152                   escaped = false;
153             }
154             else
155             {
156                if(ch == options.valueQuotes)
157                {
158                   quoted = true;
159                   start = c + 1;
160                   started = true;
161                }
162                //else if(ch == options.fieldSeparator || ch == '\n')
163                else if(ch == options.fieldSeparator ||
164                      (ch == '\n' && (!options.tolerateNewLineInValues || info.fieldNum >= options.expectedFieldCount-1)))
165                {
166                   if(values.count < options.expectedFieldCount)
167                   {
168                      int len = started ? (end-start) : 0;
169                      String value = new char[len+1];
170                      if(options.escaped)  // Escaped with a backslash
171                         UnescapeString(value, &buffer[start], len);
172                      else
173                      {
174                         String dq;
175                         memcpy(value, &buffer[start], len);
176                         value[len] = 0;
177                         while((dq = strstr(value, "\"\"")))
178                         {
179                            memmove(dq + 1, dq + 2, len - (uint)(dq + 2 - value) + 1);
180                            len--;
181                            value[len] = 0;
182                         }
183                      }
184                      values.Add(value);
185                   }
186                   start = end = 0;
187                   started = false;
188                   info.fieldNum++;
189                   if(ch == '\n')
190                   {
191                      info.lineNum++;
192                      info.rowNum++;
193                      status = OnRowStrings(values);
194                      values.Free();
195                      info.fieldNum = 0;
196                   }
197                }
198                else if(ch == '\r');
199                else
200                {
201                   if(!started)
202                   {
203                      start = c;
204                      started = true;
205                   }
206                   end = c+1;
207                }
208             }
209             if(ch == '\r' || ch == '\n')
210                info.charNum = 0;
211             else
212                info.charNum++;
213          }
214       }
215       if(end > start)
216       {
217          int len = end-start;
218
219          String value = new char[len+1];
220          if(options.escaped)
221             UnescapeString(value, &buffer[start], len);
222          else
223          {
224             String dq;
225             memcpy(value, &buffer[start], len);
226             value[len] = 0;
227             while((dq = strstr(value, "\"\"")))
228             {
229                memmove(dq + 1, dq + 2, len - (uint)(dq + 2 - value) + 1);
230                len--;
231                value[len] = 0;
232             }
233          }
234          values.Add(value);
235       }
236       if(values.count && status)
237       {
238          status = OnRowStrings(values);
239          info.rowNum++;
240       }
241       values.Free();
242       delete values;
243    }
244 }