1 #include "csv_parser.h"
6 /* External declarations from Bison/Flex */
7 extern int yyparse(void);
10 extern int yylex_destroy(void);
12 /* Global parser state - used to communicate with Bison parser */
13 static csv_document_t *g_current_document = NULL;
14 static csv_parser_t *g_current_parser = NULL;
15 static bool g_parse_error = false;
16 static char g_error_message[256] = "";
18 /* CSV parser structure */
20 char error_message[256];
24 /* Helper functions */
25 static csv_field_t *convert_field_list(field_node_t *field_nodes);
26 static csv_record_t *create_record_from_fields(field_node_t *field_nodes);
27 static void free_document_internal(csv_document_t *doc);
29 /* Functions called by the Bison parser */
30 void csv_parser_add_record(field_node_t *fields);
31 void csv_parser_set_error(const char *error);
35 csv_parser_t *csv_parser_create(void)
37 csv_parser_t *parser = malloc(sizeof(csv_parser_t));
42 parser->error_message[0] = '\0';
43 parser->has_error = false;
48 void csv_parser_destroy(csv_parser_t *parser)
55 csv_error_t csv_parser_parse_string(csv_parser_t *parser, const char *input, csv_document_t **document)
57 if (!parser || !input || !document) {
58 return CSV_ERROR_INVALID_PARAM;
63 /* Create a temporary file from the string input */
64 FILE *temp_file = tmpfile();
66 strncpy(parser->error_message, "Failed to create temporary file", sizeof(parser->error_message) - 1);
67 parser->has_error = true;
71 /* Write input to temporary file */
72 if (fputs(input, temp_file) == EOF) {
74 strncpy(parser->error_message, "Failed to write to temporary file", sizeof(parser->error_message) - 1);
75 parser->has_error = true;
79 /* Reset file position to beginning */
82 /* Parse from the temporary file */
83 csv_error_t result = csv_parser_parse_file(parser, temp_file, document);
89 csv_error_t csv_parser_parse_file(csv_parser_t *parser, FILE *file, csv_document_t **document)
91 if (!parser || !file || !document) {
92 return CSV_ERROR_INVALID_PARAM;
96 parser->has_error = false;
97 parser->error_message[0] = '\0';
99 /* Initialize global state */
100 g_current_document = malloc(sizeof(csv_document_t));
101 if (!g_current_document) {
102 strncpy(parser->error_message, "Memory allocation failed", sizeof(parser->error_message) - 1);
103 parser->has_error = true;
104 return CSV_ERROR_MEMORY;
107 g_current_document->records = NULL;
108 g_current_document->record_count = 0;
109 g_current_document->header = NULL;
110 g_current_parser = parser;
111 g_parse_error = false;
112 g_error_message[0] = '\0';
114 /* Set up the lexer input */
117 /* Parse the input */
118 int parse_result = yyparse();
120 /* Clean up lexer state */
123 /* Check for errors */
124 if (parse_result != 0 || g_parse_error) {
125 if (g_error_message[0] != '\0') {
126 strncpy(parser->error_message, g_error_message, sizeof(parser->error_message) - 1);
128 strncpy(parser->error_message, "Parse error", sizeof(parser->error_message) - 1);
130 parser->has_error = true;
132 free_document_internal(g_current_document);
133 g_current_document = NULL;
134 g_current_parser = NULL;
135 return CSV_ERROR_PARSE;
138 /* Success - transfer ownership of document */
139 *document = g_current_document;
140 g_current_document = NULL;
141 g_current_parser = NULL;
146 const char *csv_parser_get_error(csv_parser_t *parser)
149 return "Invalid parser";
152 return parser->has_error ? parser->error_message : "No error";
155 void csv_document_free(csv_document_t *document)
157 free_document_internal(document);
160 const char *csv_error_string(csv_error_t error)
165 case CSV_ERROR_MEMORY:
166 return "Memory allocation error";
167 case CSV_ERROR_PARSE:
168 return "Parse error";
169 case CSV_ERROR_INVALID_PARAM:
170 return "Invalid parameter";
174 return "Unknown error";
178 /* Data access helper functions */
180 bool csv_document_has_header(const csv_document_t *document)
182 return document && document->header != NULL;
185 csv_record_t *csv_document_get_first_record(const csv_document_t *document)
187 return document ? document->records : NULL;
190 csv_record_t *csv_record_get_next(const csv_record_t *record)
192 return record ? record->next : NULL;
195 csv_field_t *csv_record_get_first_field(const csv_record_t *record)
197 return record ? record->fields : NULL;
200 csv_field_t *csv_field_get_next(const csv_field_t *field)
202 return field ? field->next : NULL;
205 const char *csv_field_get_content(const csv_field_t *field)
207 return field ? field->content : NULL;
210 /* Internal helper functions */
212 static csv_field_t *convert_field_list(field_node_t *field_nodes)
218 csv_field_t *first_field = NULL;
219 csv_field_t *last_field = NULL;
221 field_node_t *current_node = field_nodes;
222 while (current_node) {
223 csv_field_t *field = malloc(sizeof(csv_field_t));
225 /* Clean up already allocated fields */
226 while (first_field) {
227 csv_field_t *next = first_field->next;
228 free(first_field->content);
235 /* Copy content (take ownership) */
236 field->content = current_node->content;
237 current_node->content = NULL; /* Transfer ownership */
244 last_field->next = field;
248 current_node = current_node->next;
254 static csv_record_t *create_record_from_fields(field_node_t *field_nodes)
256 csv_record_t *record = malloc(sizeof(csv_record_t));
261 record->fields = convert_field_list(field_nodes);
265 record->field_count = 0;
266 csv_field_t *current = record->fields;
268 record->field_count++;
269 current = current->next;
275 static void free_document_internal(csv_document_t *doc)
283 csv_field_t *field = doc->header->fields;
285 csv_field_t *next = field->next;
286 free(field->content);
294 csv_record_t *record = doc->records;
296 csv_record_t *next_record = record->next;
298 csv_field_t *field = record->fields;
300 csv_field_t *next_field = field->next;
301 free(field->content);
307 record = next_record;
313 /* Functions called by the Bison parser */
315 void csv_parser_add_record(field_node_t *fields)
317 if (!g_current_document || g_parse_error) {
321 /* Skip empty records (single empty field) */
322 if (fields && !fields->next && fields->content && fields->content[0] == '\0') {
326 csv_record_t *record = create_record_from_fields(fields);
328 g_parse_error = true;
329 strncpy(g_error_message, "Memory allocation failed while creating record", sizeof(g_error_message) - 1);
333 /* Add to document */
334 if (!g_current_document->records) {
335 g_current_document->records = record;
337 /* Find the last record and append */
338 csv_record_t *last = g_current_document->records;
345 g_current_document->record_count++;
348 void csv_parser_set_error(const char *error)
350 g_parse_error = true;
352 strncpy(g_error_message, error, sizeof(g_error_message) - 1);
353 g_error_message[sizeof(g_error_message) - 1] = '\0';
357 csv_error_t csv_document_set_first_record_as_header(csv_document_t *document)
359 if (!document || !document->records) {
360 return CSV_ERROR_INVALID_PARAM;
363 /* Move first record to header */
364 document->header = document->records;
365 document->records = document->records->next;
366 document->header->next = NULL;
367 document->record_count--;