]> git.sur5r.net Git - glabels/blob - glabels2/src/merge-text.c
2005-04-16 Jim Evins <evins@snaught.com>
[glabels] / glabels2 / src / merge-text.c
1 /*
2  *  (GLABELS) Label and Business Card Creation program for GNOME
3  *
4  *  merge_text.c:  text-file merge backend module
5  *
6  *  Copyright (C) 2001  Jim Evins <evins@snaught.com>.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21  */
22
23 #include <config.h>
24
25 #include "merge-text.h"
26
27 #include <stdio.h>
28
29 #include "debug.h"
30
31 #define LINE_BUF_LEN 1024
32
33 /*===========================================*/
34 /* Private types                             */
35 /*===========================================*/
36
37 struct _glMergeTextPrivate {
38         gchar             delim;
39         FILE             *fp;
40 };
41
42 enum {
43         LAST_SIGNAL
44 };
45
46 enum {
47         ARG_0,
48         ARG_DELIM,
49 };
50
51 /*===========================================*/
52 /* Private globals                           */
53 /*===========================================*/
54
55 static glMergeClass *parent_class = NULL;
56
57
58 /*===========================================*/
59 /* Local function prototypes                 */
60 /*===========================================*/
61
62 static void           gl_merge_text_class_init      (glMergeTextClass *klass);
63 static void           gl_merge_text_instance_init   (glMergeText      *object);
64 static void           gl_merge_text_finalize        (GObject          *object);
65
66 static void           gl_merge_text_set_property    (GObject          *object,
67                                                      guint             param_id,
68                                                      const GValue     *value,
69                                                      GParamSpec       *pspec);
70
71 static void           gl_merge_text_get_property    (GObject          *object,
72                                                      guint             param_id,
73                                                      GValue           *value,
74                                                      GParamSpec       *pspec);
75
76 static GList         *gl_merge_text_get_key_list    (glMerge          *merge);
77 static gchar         *gl_merge_text_get_primary_key (glMerge          *merge);
78 static void           gl_merge_text_open            (glMerge          *merge);
79 static void           gl_merge_text_close           (glMerge          *merge);
80 static glMergeRecord *gl_merge_text_get_record      (glMerge          *merge);
81 static void           gl_merge_text_copy            (glMerge          *dst_merge,
82                                                      glMerge          *src_merge);
83
84 static GList         *parse_line                    (FILE             *fp,
85                                                      gchar             delim);
86 static void           free_fields                   (GList           **fields);
87
88 \f
89 /*****************************************************************************/
90 /* Boilerplate object stuff.                                                 */
91 /*****************************************************************************/
92 GType
93 gl_merge_text_get_type (void)
94 {
95         static GType type = 0;
96
97         if (!type) {
98                 static const GTypeInfo info = {
99                         sizeof (glMergeTextClass),
100                         NULL,
101                         NULL,
102                         (GClassInitFunc) gl_merge_text_class_init,
103                         NULL,
104                         NULL,
105                         sizeof (glMergeText),
106                         0,
107                         (GInstanceInitFunc) gl_merge_text_instance_init,
108                         NULL
109                 };
110
111                 type = g_type_register_static (GL_TYPE_MERGE,
112                                                "glMergeText", &info, 0);
113         }
114
115         return type;
116 }
117
118 static void
119 gl_merge_text_class_init (glMergeTextClass *klass)
120 {
121         GObjectClass *object_class = (GObjectClass *) klass;
122         glMergeClass *merge_class  = (glMergeClass *) klass;
123
124         gl_debug (DEBUG_MERGE, "START");
125
126         parent_class = g_type_class_peek_parent (klass);
127
128         object_class->set_property = gl_merge_text_set_property;
129         object_class->get_property = gl_merge_text_get_property;
130
131         g_object_class_install_property
132                 (object_class,
133                  ARG_DELIM,
134                  g_param_spec_char ("delim", NULL, NULL,
135                                     0, 0x7F, ',',
136                                     (G_PARAM_READABLE | G_PARAM_WRITABLE)));
137
138         object_class->finalize = gl_merge_text_finalize;
139
140         merge_class->get_key_list    = gl_merge_text_get_key_list;
141         merge_class->get_primary_key = gl_merge_text_get_primary_key;
142         merge_class->open            = gl_merge_text_open;
143         merge_class->close           = gl_merge_text_close;
144         merge_class->get_record      = gl_merge_text_get_record;
145         merge_class->copy            = gl_merge_text_copy;
146
147         gl_debug (DEBUG_MERGE, "END");
148 }
149
150 static void
151 gl_merge_text_instance_init (glMergeText *merge_text)
152 {
153         gl_debug (DEBUG_MERGE, "START");
154
155         merge_text->private = g_new0 (glMergeTextPrivate, 1);
156
157         gl_debug (DEBUG_MERGE, "END");
158 }
159
160 static void
161 gl_merge_text_finalize (GObject *object)
162 {
163         gl_debug (DEBUG_MERGE, "START");
164
165         g_return_if_fail (object && GL_IS_MERGE_TEXT (object));
166
167         G_OBJECT_CLASS (parent_class)->finalize (object);
168
169         gl_debug (DEBUG_MERGE, "END");
170 }
171
172 /*--------------------------------------------------------------------------*/
173 /* Set argument.                                                            */
174 /*--------------------------------------------------------------------------*/
175 static void
176 gl_merge_text_set_property (GObject      *object,
177                             guint         param_id,
178                             const GValue *value,
179                             GParamSpec   *pspec)
180 {
181         glMergeText *merge_text;
182
183         merge_text = GL_MERGE_TEXT (object);
184
185         switch (param_id) {
186
187         case ARG_DELIM:
188                 merge_text->private->delim = g_value_get_char (value);
189                 gl_debug (DEBUG_MERGE, "ARG \"delim\" = \"%c\"",
190                           merge_text->private->delim);
191                 break;
192
193         default:
194                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
195                 break;
196
197         }
198
199 }
200
201 /*--------------------------------------------------------------------------*/
202 /* Get argument.                                                            */
203 /*--------------------------------------------------------------------------*/
204 static void
205 gl_merge_text_get_property (GObject     *object,
206                             guint        param_id,
207                             GValue      *value,
208                             GParamSpec  *pspec)
209 {
210         glMergeText *merge_text;
211
212         merge_text = GL_MERGE_TEXT (object);
213
214         switch (param_id) {
215
216         case ARG_DELIM:
217                 g_value_set_char (value, merge_text->private->delim);
218                 break;
219
220         default:
221                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
222                 break;
223
224         }
225
226 }
227
228 /*--------------------------------------------------------------------------*/
229 /* Get key list.                                                            */
230 /*--------------------------------------------------------------------------*/
231 static GList *
232 gl_merge_text_get_key_list (glMerge *merge)
233 {
234         glMergeText   *merge_text;
235         GList         *record_list, *p_rec;
236         glMergeRecord *record;
237         GList         *p_field;
238         gint           i_field, n_fields, n_fields_max = 0;
239         GList         *key_list;
240         
241         /* Field keys are simply column numbers. */
242
243         gl_debug (DEBUG_MERGE, "BEGIN");
244
245         merge_text = GL_MERGE_TEXT (merge);
246
247         record_list = (GList *)gl_merge_get_record_list (merge);
248
249         for ( p_rec=record_list; p_rec!=NULL; p_rec=p_rec->next ) {
250                 record = (glMergeRecord *)p_rec->data;
251
252                 n_fields = 0;
253                 for ( p_field=record->field_list; p_field!=NULL; p_field=p_field->next ) {
254                         n_fields++;
255                 }
256                 if ( n_fields > n_fields_max ) n_fields_max = n_fields;
257         }
258
259         key_list = NULL;
260         for (i_field=1; i_field <= n_fields_max; i_field++) {
261                 key_list = g_list_append (key_list, g_strdup_printf ("%d", i_field));
262         }
263
264
265         gl_debug (DEBUG_MERGE, "END");
266
267         return key_list;
268 }
269
270 /*--------------------------------------------------------------------------*/
271 /* Get "primary" key.                                                       */
272 /*--------------------------------------------------------------------------*/
273 static gchar *
274 gl_merge_text_get_primary_key (glMerge *merge)
275 {
276         /* For now, let's always assume the first column is the primary key. */
277         return g_strdup ("1");
278 }
279
280 /*--------------------------------------------------------------------------*/
281 /* Open merge source.                                                       */
282 /*--------------------------------------------------------------------------*/
283 static void
284 gl_merge_text_open (glMerge *merge)
285 {
286         glMergeText *merge_text;
287         gchar       *src;
288
289         merge_text = GL_MERGE_TEXT (merge);
290
291         src = gl_merge_get_src (merge);
292
293         if (src != NULL) {
294                 merge_text->private->fp = fopen (src, "r");
295         }
296
297         g_free (src);
298 }
299
300 /*--------------------------------------------------------------------------*/
301 /* Close merge source.                                                      */
302 /*--------------------------------------------------------------------------*/
303 static void
304 gl_merge_text_close (glMerge *merge)
305 {
306         glMergeText *merge_text;
307
308         merge_text = GL_MERGE_TEXT (merge);
309
310         if (merge_text->private->fp != NULL) {
311
312                 fclose (merge_text->private->fp);
313                 merge_text->private->fp = NULL;
314
315         }
316 }
317
318 /*--------------------------------------------------------------------------*/
319 /* Get next record from merge source, NULL if no records left (i.e EOF)     */
320 /*--------------------------------------------------------------------------*/
321 static glMergeRecord *
322 gl_merge_text_get_record (glMerge *merge)
323 {
324         glMergeText   *merge_text;
325         gchar          delim;
326         FILE          *fp;
327         glMergeRecord *record;
328         GList         *fields, *p;
329         gint           i_field;
330         glMergeField  *field;
331
332         merge_text = GL_MERGE_TEXT (merge);
333
334         delim = merge_text->private->delim;
335         fp    = merge_text->private->fp;
336
337         if (fp == NULL) {
338                 return NULL;
339         }
340                
341         fields = parse_line (fp, delim);
342         if ( fields == NULL ) {
343                 return NULL;
344         }
345
346         record = g_new0 (glMergeRecord, 1);
347         record->select_flag = TRUE;
348         i_field = 1;
349         for (p=fields; p != NULL; p=p->next) {
350
351                 field = g_new0 (glMergeField, 1);
352                 field->key = g_strdup_printf ("%d", i_field++);
353 #ifndef CSV_ALWAYS_UTF8
354                 field->value = g_locale_to_utf8 (p->data, -1, NULL, NULL, NULL);
355 #else
356                 field->value = g_strdup (p->data);
357 #endif
358
359                 record->field_list = g_list_append (record->field_list, field);
360         }
361         free_fields (&fields);
362
363         return record;
364 }
365
366 /*---------------------------------------------------------------------------*/
367 /* Copy merge_text specific fields.                                          */
368 /*---------------------------------------------------------------------------*/
369 static void
370 gl_merge_text_copy (glMerge *dst_merge,
371                     glMerge *src_merge)
372 {
373         glMergeText *dst_merge_text;
374         glMergeText *src_merge_text;
375
376         dst_merge_text = GL_MERGE_TEXT (dst_merge);
377         src_merge_text = GL_MERGE_TEXT (src_merge);
378
379         dst_merge_text->private->delim = src_merge_text->private->delim;
380 }
381
382 /*---------------------------------------------------------------------------*/
383 /* PRIVATE.  Parse line (quoted values may span multiple lines).             */
384 /*---------------------------------------------------------------------------*/
385 static GList *
386 parse_line (FILE  *fp,
387             gchar  delim )
388 {
389         GList *list = NULL;
390         GString *string;
391         gint c;
392         enum { BEGIN, NORMAL, NORMAL_ESCAPED,
393                QUOTED, QUOTED_ESCAPED, QUOTED_QUOTE1,
394                DONE } state;
395
396         state = BEGIN;
397         string = g_string_new( "" );
398         while ( state != DONE ) {
399                 c=getc (fp);
400
401                 switch (state) {
402
403                 case BEGIN:
404                         switch (c) {
405                         case '\\':
406                                 state = NORMAL_ESCAPED;
407                                 break;
408                         case '"':
409                                 state = QUOTED;
410                                 break;
411                         case '\r':
412                                 /* Strip CR. */
413                                 state = NORMAL;
414                                 break;
415                         case '\n':
416                                 /* treat as one empty field. */
417                                 list = g_list_append (list,
418                                                       g_strdup (""));
419                                 state = DONE;
420                                 break;
421                         case EOF:
422                                 state = DONE;
423                                 break;
424                         default:
425                                 if ( c != delim ) {
426                                         string = g_string_append_c (string, c);
427                                 } else {
428                                         list = g_list_append (list,
429                                                       g_strdup (string->str));
430                                         string = g_string_assign( string, "" );
431                                 }
432                                 state = NORMAL;
433                                 break;
434                         }
435                         break;
436
437                 case NORMAL:
438                         switch (c) {
439                         case '\\':
440                                 state = NORMAL_ESCAPED;
441                                 break;
442                         case '"':
443                                 state = QUOTED;
444                                 break;
445                         case '\r':
446                                 /* Strip CR. */
447                                 break;
448                         case '\n':
449                         case EOF:
450                                 list = g_list_append (list,
451                                                       g_strdup (string->str));
452                                 state = DONE;
453                                 break;
454                         default:
455                                 if ( c != delim ) {
456                                         string = g_string_append_c (string, c);
457                                 } else {
458                                         list = g_list_append (list,
459                                                       g_strdup (string->str));
460                                         string = g_string_assign( string, "" );
461                                 }
462                                 break;
463                         }
464                         break;
465
466                 case NORMAL_ESCAPED:
467                         switch (c) {
468                         case 'n':
469                                 string = g_string_append_c (string, '\n');
470                                 state = NORMAL;
471                                 break;
472                         case 't':
473                                 string = g_string_append_c (string, '\t');
474                                 state = NORMAL;
475                                 break;
476                         case '\r':
477                                 /* Strip CR, stay ESCAPED. */
478                                 break;
479                         case EOF:
480                                 state = DONE;
481                                 break;
482                         default:
483                                 string = g_string_append_c (string, c);
484                                 state = NORMAL;
485                                 break;
486                         }
487                         break;
488
489                 case QUOTED:
490                         switch (c) {
491                         case '\\':
492                                 state = QUOTED_ESCAPED;
493                                 break;
494                         case '"':
495                                 state = QUOTED_QUOTE1;
496                                 break;
497                         case '\r':
498                                 /* Strip CR. */
499                                 break;
500                         case EOF:
501                                 /* File ended mid way through quoted item */
502                                 list = g_list_append (list,
503                                                       g_strdup (string->str));
504                                 state = DONE;
505                                 break;
506                         default:
507                                 string = g_string_append_c (string, c);
508                                 break;
509                         }
510                         break;
511
512                 case QUOTED_ESCAPED:
513                         switch (c) {
514                         case 'n':
515                                 string = g_string_append_c (string, '\n');
516                                 state = QUOTED;
517                                 break;
518                         case 't':
519                                 string = g_string_append_c (string, '\t');
520                                 state = QUOTED;
521                                 break;
522                         case '\r':
523                                 /* Strip CR, stay ESCAPED. */
524                                 break;
525                         case EOF:
526                                 /* File ended mid way through quoted item */
527                                 list = g_list_append (list,
528                                                       g_strdup (string->str));
529                                 state = DONE;
530                                 break;
531                         default:
532                                 string = g_string_append_c (string, c);
533                                 state = QUOTED;
534                                 break;
535                         }
536                         break;
537
538                 case QUOTED_QUOTE1:
539                         switch (c) {
540                         case '"':
541                                 /* insert quotes in string, stay quoted. */
542                                 string = g_string_append_c (string, c);
543                                 state = QUOTED;
544                                 break;
545                         case '\r':
546                                 /* Strip CR, return to NORMAL. */
547                                 state = NORMAL;
548                                 break;
549                         case '\n':
550                         case EOF:
551                                 /* line or file ended after quoted item */
552                                 list = g_list_append (list,
553                                                       g_strdup (string->str));
554                                 state = DONE;
555                                 break;
556                         default:
557                                 if ( c != delim ) {
558                                         string = g_string_append_c (string, c);
559                                 } else {
560                                         list = g_list_append (list,
561                                                       g_strdup (string->str));
562                                         string = g_string_assign( string, "" );
563                                 }
564                                 state = NORMAL;
565                                 break;
566                         }
567                         break;
568
569                 default:
570                         g_assert_not_reached();
571                         break;
572                 }
573
574         }
575         g_string_free( string, TRUE );
576
577         return list;
578 }
579
580 /*---------------------------------------------------------------------------*/
581 /* Free list of fields.                                                      */
582 /*---------------------------------------------------------------------------*/
583 void
584 free_fields (GList ** list)
585 {
586         GList *p;
587
588         for (p = *list; p != NULL; p = p->next) {
589                 g_free (p->data);
590                 p->data = NULL;
591         }
592
593         g_list_free (*list);
594         *list = NULL;
595 }
596