Branch data Line data Source code
1 : : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : : /*************************************************************************
3 : : *
4 : : * Copyright (c) 2011 Kohei Yoshida
5 : : *
6 : : * Permission is hereby granted, free of charge, to any person
7 : : * obtaining a copy of this software and associated documentation
8 : : * files (the "Software"), to deal in the Software without
9 : : * restriction, including without limitation the rights to use,
10 : : * copy, modify, merge, publish, distribute, sublicense, and/or sell
11 : : * copies of the Software, and to permit persons to whom the
12 : : * Software is furnished to do so, subject to the following
13 : : * conditions:
14 : : *
15 : : * The above copyright notice and this permission notice shall be
16 : : * included in all copies or substantial portions of the Software.
17 : : *
18 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 : : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 : : * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 : : * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 : : * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 : : * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 : : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 : : * OTHER DEALINGS IN THE SOFTWARE.
26 : : *
27 : : ************************************************************************/
28 : :
29 : : #ifndef __ORCUS_CSV_PARSER_HPP__
30 : : #define __ORCUS_CSV_PARSER_HPP__
31 : :
32 : : #define ORCUS_DEBUG_CSV 0
33 : :
34 : : #include <cstdlib>
35 : : #include <cstring>
36 : : #include <exception>
37 : : #include <string>
38 : : #include <cassert>
39 : : #include <sstream>
40 : :
41 : : #if ORCUS_DEBUG_CSV
42 : : #include <iostream>
43 : : using std::cout;
44 : : using std::endl;
45 : : #endif
46 : :
47 : : namespace orcus {
48 : :
49 : 87 : struct csv_parser_config
50 : : {
51 : : std::string delimiters;
52 : : char text_qualifier;
53 : : bool trim_cell_value:1;
54 : :
55 : 87 : csv_parser_config() :
56 : 87 : trim_cell_value(true) {}
57 : : };
58 : :
59 : : class csv_parse_error : public std::exception
60 : : {
61 : : std::string m_msg;
62 : : public:
63 [ # # ]: 0 : csv_parse_error(const std::string& msg) : m_msg(msg) {}
64 [ # # ]: 0 : virtual ~csv_parse_error() throw() {}
65 : 0 : virtual const char* what() const throw() { return m_msg.c_str(); }
66 : : };
67 : :
68 : : template<typename _Handler>
69 : 87 : class csv_parser
70 : : {
71 : : public:
72 : : typedef _Handler handler_type;
73 : :
74 : : csv_parser(const char* p, size_t n, handler_type& hdl, const csv_parser_config& config);
75 : : void parse();
76 : :
77 : : private:
78 : 34059 : bool has_char() const { return m_pos < m_length; }
79 : 609 : bool has_next() const { return m_pos + 1 < m_length; }
80 : : void next();
81 : : char cur_char() const;
82 : : char next_char() const;
83 : :
84 : : bool is_delim(char c) const;
85 : : bool is_text_qualifier(char c) const;
86 : :
87 : : // handlers
88 : : void row();
89 : : void cell();
90 : : void quoted_cell();
91 : :
92 : : void parse_cell_with_quote(const char* p0, size_t len0);
93 : : void skip_blanks();
94 : :
95 : : void init_cell_buf();
96 : : void append_to_cell_buf(const char* p, size_t len);
97 : :
98 : : /**
99 : : * Push cell value to the handler.
100 : : */
101 : : void push_cell_value(const char* p, size_t n);
102 : :
103 : 333 : static bool is_blank(char c)
104 : : {
105 [ + - ][ - + ]: 333 : return c == ' ' || c == '\t';
[ + - ][ - + ]
106 : : }
107 : :
108 : : private:
109 : : handler_type& m_handler;
110 : : const csv_parser_config& m_config;
111 : : std::string m_cell_buf;
112 : : const char* mp_char;
113 : : size_t m_pos;
114 : : size_t m_length;
115 : : size_t m_cell_buf_size;
116 : : };
117 : :
118 : : template<typename _Handler>
119 : 87 : csv_parser<_Handler>::csv_parser(const char* p, size_t n, handler_type& hdl, const csv_parser_config& config) :
120 : 87 : m_handler(hdl), m_config(config), mp_char(p), m_pos(0), m_length(n) {}
121 : :
122 : : template<typename _Handler>
123 : 87 : void csv_parser<_Handler>::parse()
124 : : {
125 : : #if ORCUS_DEBUG_CSV
126 : : const char* p = mp_char;
127 : : for (size_t i = m_pos; i < m_length; ++i, ++p)
128 : : std::cout << *p;
129 : : std::cout << std::endl;
130 : : #endif
131 : :
132 : 87 : m_handler.begin_parse();
133 [ + + ][ + + ]: 942 : while (has_char())
134 : 855 : row();
135 : 87 : m_handler.end_parse();
136 : 87 : }
137 : :
138 : : template<typename _Handler>
139 : 33174 : void csv_parser<_Handler>::next()
140 : : {
141 : 33174 : ++m_pos;
142 : 33174 : ++mp_char;
143 : 33174 : }
144 : :
145 : : template<typename _Handler>
146 : 51645 : char csv_parser<_Handler>::cur_char() const
147 : : {
148 : 51645 : return *mp_char;
149 : : }
150 : :
151 : : template<typename _Handler>
152 : 609 : char csv_parser<_Handler>::next_char() const
153 : : {
154 : 609 : return *(mp_char+1);
155 : : }
156 : :
157 : : template<typename _Handler>
158 : 21048 : bool csv_parser<_Handler>::is_delim(char c) const
159 : : {
160 : 21048 : return m_config.delimiters.find(c) != std::string::npos;
161 : : }
162 : :
163 : : template<typename _Handler>
164 : 20577 : bool csv_parser<_Handler>::is_text_qualifier(char c) const
165 : : {
166 : 20577 : return m_config.text_qualifier == c;
167 : : }
168 : :
169 : : template<typename _Handler>
170 : 855 : void csv_parser<_Handler>::row()
171 : : {
172 : 855 : m_handler.begin_row();
173 : 8685 : while (true)
174 : : {
175 [ - + ][ + + ]: 9540 : if (is_text_qualifier(cur_char()))
176 : 267 : quoted_cell();
177 : : else
178 : 9273 : cell();
179 : :
180 [ - + ][ - + ]: 9540 : if (!has_char())
181 : : {
182 : 0 : m_handler.end_row();
183 : 855 : return;
184 : : }
185 : :
186 : 9540 : char c = cur_char();
187 [ + + + + ]: 9540 : if (c == '\n')
188 : : {
189 : 855 : next();
190 : : #if ORCUS_DEBUG_CSV
191 : : cout << "(LF)" << endl;
192 : : #endif
193 : 855 : m_handler.end_row();
194 : 855 : return;
195 : : }
196 : :
197 : : assert(is_delim(c));
198 : 8685 : next();
199 [ + - - + ]: 8685 : if(m_config.trim_cell_value)
200 : 18 : skip_blanks();
201 : : }
202 : : }
203 : :
204 : : template<typename _Handler>
205 : 9273 : void csv_parser<_Handler>::cell()
206 : : {
207 : 9273 : const char* p = mp_char;
208 : 9273 : size_t len = 0;
209 : 9273 : char c = cur_char();
210 [ + + ][ + + ]: 21870 : while (c != '\n' && !is_delim(c))
[ + + ][ + + ]
[ + + ][ + + ]
211 : : {
212 : 12597 : ++len;
213 : 12597 : next();
214 [ - + - + ]: 12597 : if (!has_char())
215 : 0 : break;
216 : 12597 : c = cur_char();
217 : : }
218 : :
219 [ - + ][ + + ]: 9273 : if (!len)
220 : 7146 : p = NULL;
221 : :
222 : 9273 : push_cell_value(p, len);
223 : 9273 : }
224 : :
225 : : template<typename _Handler>
226 : 267 : void csv_parser<_Handler>::quoted_cell()
227 : : {
228 : : #if ORCUS_DEBUG_CSV
229 : : using namespace std;
230 : : cout << "--- quoted cell" << endl;
231 : : #endif
232 : 267 : char c = cur_char();
233 : : assert(is_text_qualifier(c));
234 : 267 : next(); // Skip the opening quote.
235 [ # # - + ]: 267 : if (!has_char())
236 : 0 : return;
237 : :
238 : 267 : const char* p0 = mp_char;
239 : 267 : size_t len = 1;
240 [ # # ][ + - ]: 3093 : for (; has_char(); next(), ++len)
241 : : {
242 : 3093 : c = cur_char();
243 : : #if ORCUS_DEBUG_CSV
244 : : cout << "'" << c << "'" << endl;
245 : : #endif
246 [ # # + + ]: 3093 : if (!is_text_qualifier(c))
247 : 2826 : continue;
248 : :
249 : : // current char is a quote. Check if the next char is also a text
250 : : // qualifier.
251 : :
252 [ # # ][ # # ]: 267 : if (has_next() && is_text_qualifier(next_char()))
[ # # ][ + - ]
[ + + ][ + + ]
253 : : {
254 : 156 : next();
255 : 156 : parse_cell_with_quote(p0, len);
256 : 156 : return;
257 : : }
258 : :
259 : : // Closing quote.
260 : 111 : m_handler.cell(p0, len-1);
261 : 111 : next();
262 : 111 : skip_blanks();
263 : 111 : return;
264 : : }
265 : :
266 : : // Stream ended prematurely. Handle it gracefully.
267 : 0 : m_handler.cell(p0, len);
268 : 0 : next();
269 : 267 : skip_blanks();
270 : : }
271 : :
272 : : template<typename _Handler>
273 : 156 : void csv_parser<_Handler>::parse_cell_with_quote(const char* p0, size_t len0)
274 : : {
275 : : #if ORCUS_DEBUG_CSV
276 : : using namespace std;
277 : : cout << "--- parse cell with quote" << endl;
278 : : #endif
279 : : assert(is_text_qualifier(cur_char()));
280 : :
281 : : // Push the preceding chars to the temp buffer.
282 : 156 : init_cell_buf();
283 : 156 : append_to_cell_buf(p0, len0);
284 : :
285 : : // Parse the rest, until the closing quote.
286 : 156 : next();
287 : 156 : const char* p_cur = mp_char;
288 : 156 : size_t cur_len = 0;
289 [ # # ][ + - ]: 7335 : for (; has_char(); next(), ++cur_len)
290 : : {
291 : 7335 : char c = cur_char();
292 : : #if ORCUS_DEBUG_CSV
293 : : cout << "'" << c << "'" << endl;
294 : : #endif
295 [ # # + + ]: 7335 : if (!is_text_qualifier(c))
296 : 6993 : continue;
297 : :
298 [ # # ][ # # ]: 342 : if (has_next() && is_text_qualifier(next_char()))
[ # # ][ + - ]
[ + + ][ + + ]
299 : : {
300 : : // double quotation. Copy the current segment to the cell buffer.
301 : 186 : append_to_cell_buf(p_cur, cur_len);
302 : :
303 : 186 : next(); // to the 2nd quote.
304 : 186 : p_cur = mp_char;
305 : 186 : cur_len = 0;
306 : 186 : continue;
307 : : }
308 : :
309 : : // closing quote. Flush the current segment to the cell
310 : : // buffer, push the value to the handler, and exit normally.
311 : 156 : append_to_cell_buf(p_cur, cur_len);
312 : :
313 : 156 : m_handler.cell(&m_cell_buf[0], m_cell_buf_size);
314 : 156 : next();
315 : 156 : skip_blanks();
316 : 156 : return;
317 : : }
318 : :
319 : : // Stream ended prematurely.
320 [ # # ][ # # ]: 0 : throw csv_parse_error("stream ended prematurely while parsing quoted cell.");
[ # # ][ # # ]
321 : : }
322 : :
323 : : template<typename _Handler>
324 : 285 : void csv_parser<_Handler>::skip_blanks()
325 : : {
326 [ + - ][ + - ]: 285 : for (; has_char(); next())
327 : : {
328 [ + - ][ + - ]: 285 : if (!is_blank(*mp_char))
329 : 285 : break;
330 : : }
331 : 285 : }
332 : :
333 : : template<typename _Handler>
334 : 156 : void csv_parser<_Handler>::init_cell_buf()
335 : : {
336 : 156 : m_cell_buf_size = 0;
337 : 156 : }
338 : :
339 : : template<typename _Handler>
340 : 498 : void csv_parser<_Handler>::append_to_cell_buf(const char* p, size_t len)
341 : : {
342 : 498 : size_t size_needed = m_cell_buf_size + len;
343 [ # # ][ + + ]: 498 : if (m_cell_buf.size() < size_needed)
344 : 57 : m_cell_buf.resize(size_needed);
345 : :
346 : 498 : char* p_dest = &m_cell_buf[m_cell_buf_size];
347 : 498 : std::strncpy(p_dest, p, len);
348 : 498 : m_cell_buf_size += len;
349 : 498 : }
350 : :
351 : : template<typename _Handler>
352 : 9273 : void csv_parser<_Handler>::push_cell_value(const char* p, size_t n)
353 : : {
354 : 9273 : size_t len = n;
355 : :
356 [ + - ][ - + ]: 9273 : if (m_config.trim_cell_value)
357 : : {
358 : : // Trim any leading blanks.
359 [ + - ][ # # ]: 24 : for (size_t i = 0; i < n; ++i, --len, ++p)
360 : : {
361 [ + - ][ # # ]: 24 : if (!is_blank(*p))
362 : 24 : break;
363 : : }
364 : :
365 : : // Trim any trailing blanks.
366 [ + - ][ # # ]: 24 : if (len)
367 : : {
368 : 24 : const char* p_end = p + (len-1);
369 [ + - ][ # # ]: 48 : for (; p != p_end; --p_end, --len)
370 : : {
371 [ + - ][ # # ]: 24 : if (!is_blank(*p_end))
372 : 24 : break;
373 : : }
374 : : }
375 : : }
376 : :
377 : 9273 : m_handler.cell(p, len);
378 : : #if ORCUS_DEBUG_CSV
379 : : cout << "(cell:'" << std::string(p, len) << "')" << endl;
380 : : #endif
381 : 9273 : }
382 : :
383 : : }
384 : :
385 : : #endif
386 : :
387 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|