]> git.sur5r.net Git - glabels/blob - glabels2/src/merge-text.c
Text merge backend now assumes text files are encoded according to the current locale...
[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 <stdio.h>
26
27 #include "merge-text.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                 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                 };
109
110                 type = g_type_register_static (GL_TYPE_MERGE,
111                                                "glMergeText", &info, 0);
112         }
113
114         return type;
115 }
116
117 static void
118 gl_merge_text_class_init (glMergeTextClass *klass)
119 {
120         GObjectClass *object_class = (GObjectClass *) klass;
121         glMergeClass *merge_class  = (glMergeClass *) klass;
122
123         gl_debug (DEBUG_MERGE, "START");
124
125         parent_class = g_type_class_peek_parent (klass);
126
127         object_class->set_property = gl_merge_text_set_property;
128         object_class->get_property = gl_merge_text_get_property;
129
130         g_object_class_install_property
131                 (object_class,
132                  ARG_DELIM,
133                  g_param_spec_char ("delim", NULL, NULL,
134                                     0, 0x7F, ',',
135                                     (G_PARAM_READABLE | G_PARAM_WRITABLE)));
136
137         object_class->finalize = gl_merge_text_finalize;
138
139         merge_class->get_key_list    = gl_merge_text_get_key_list;
140         merge_class->get_primary_key = gl_merge_text_get_primary_key;
141         merge_class->open            = gl_merge_text_open;
142         merge_class->close           = gl_merge_text_close;
143         merge_class->get_record      = gl_merge_text_get_record;
144         merge_class->copy            = gl_merge_text_copy;
145
146         gl_debug (DEBUG_MERGE, "END");
147 }
148
149 static void
150 gl_merge_text_instance_init (glMergeText *merge_text)
151 {
152         gl_debug (DEBUG_MERGE, "START");
153
154         merge_text->private = g_new0 (glMergeTextPrivate, 1);
155
156         gl_debug (DEBUG_MERGE, "END");
157 }
158
159 static void
160 gl_merge_text_finalize (GObject *object)
161 {
162         gl_debug (DEBUG_MERGE, "START");
163
164         g_return_if_fail (object && GL_IS_MERGE_TEXT (object));
165
166         G_OBJECT_CLASS (parent_class)->finalize (object);
167
168         gl_debug (DEBUG_MERGE, "END");
169 }
170
171 /*--------------------------------------------------------------------------*/
172 /* Set argument.                                                            */
173 /*--------------------------------------------------------------------------*/
174 static void
175 gl_merge_text_set_property (GObject      *object,
176                             guint         param_id,
177                             const GValue *value,
178                             GParamSpec   *pspec)
179 {
180         glMergeText *merge_text;
181
182         merge_text = GL_MERGE_TEXT (object);
183
184         switch (param_id) {
185
186         case ARG_DELIM:
187                 merge_text->private->delim = g_value_get_char (value);
188                 gl_debug (DEBUG_MERGE, "ARG \"delim\" = \"%c\"",
189                           merge_text->private->delim);
190                 break;
191
192         default:
193                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
194                 break;
195
196         }
197
198 }
199
200 /*--------------------------------------------------------------------------*/
201 /* Get argument.                                                            */
202 /*--------------------------------------------------------------------------*/
203 static void
204 gl_merge_text_get_property (GObject     *object,
205                             guint        param_id,
206                             GValue      *value,
207                             GParamSpec  *pspec)
208 {
209         glMergeText *merge_text;
210
211         merge_text = GL_MERGE_TEXT (object);
212
213         switch (param_id) {
214
215         case ARG_DELIM:
216                 g_value_set_char (value, merge_text->private->delim);
217                 break;
218
219         default:
220                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
221                 break;
222
223         }
224
225 }
226
227 /*--------------------------------------------------------------------------*/
228 /* Get key list.                                                            */
229 /*--------------------------------------------------------------------------*/
230 static GList *
231 gl_merge_text_get_key_list (glMerge *merge)
232 {
233         glMergeText   *merge_text;
234         GList         *record_list, *p_rec;
235         glMergeRecord *record;
236         GList         *p_field;
237         gint           i_field, n_fields, n_fields_max = 0;
238         GList         *key_list;
239         
240         /* Field keys are simply column numbers. */
241
242         gl_debug (DEBUG_MERGE, "BEGIN");
243
244         merge_text = GL_MERGE_TEXT (merge);
245
246         record_list = (GList *)gl_merge_get_record_list (merge);
247
248         for ( p_rec=record_list; p_rec!=NULL; p_rec=p_rec->next ) {
249                 record = (glMergeRecord *)p_rec->data;
250
251                 n_fields = 0;
252                 for ( p_field=record->field_list; p_field!=NULL; p_field=p_field->next ) {
253                         n_fields++;
254                 }
255                 if ( n_fields > n_fields_max ) n_fields_max = n_fields;
256         }
257
258         key_list = NULL;
259         for (i_field=1; i_field <= n_fields_max; i_field++) {
260                 key_list = g_list_append (key_list, g_strdup_printf ("%d", i_field));
261         }
262
263
264         gl_debug (DEBUG_MERGE, "END");
265
266         return key_list;
267 }
268
269 /*--------------------------------------------------------------------------*/
270 /* Get "primary" key.                                                       */
271 /*--------------------------------------------------------------------------*/
272 static gchar *
273 gl_merge_text_get_primary_key (glMerge *merge)
274 {
275         /* For now, let's always assume the first column is the primary key. */
276         return g_strdup ("1");
277 }
278
279 /*--------------------------------------------------------------------------*/
280 /* Open merge source.                                                       */
281 /*--------------------------------------------------------------------------*/
282 static void
283 gl_merge_text_open (glMerge *merge)
284 {
285         glMergeText *merge_text;
286         gchar       *src;
287
288         merge_text = GL_MERGE_TEXT (merge);
289
290         src = gl_merge_get_src (merge);
291
292         if (src != NULL) {
293                 merge_text->private->fp = fopen (src, "r");
294         }
295
296         g_free (src);
297 }
298
299 /*--------------------------------------------------------------------------*/
300 /* Close merge source.                                                      */
301 /*--------------------------------------------------------------------------*/
302 static void
303 gl_merge_text_close (glMerge *merge)
304 {
305         glMergeText *merge_text;
306
307         merge_text = GL_MERGE_TEXT (merge);
308
309         if (merge_text->private->fp != NULL) {
310
311                 fclose (merge_text->private->fp);
312                 merge_text->private->fp = NULL;
313
314         }
315 }
316
317 /*--------------------------------------------------------------------------*/
318 /* Get next record from merge source, NULL if no records left (i.e EOF)     */
319 /*--------------------------------------------------------------------------*/
320 static glMergeRecord *
321 gl_merge_text_get_record (glMerge *merge)
322 {
323         glMergeText   *merge_text;
324         gchar          delim;
325         FILE          *fp;
326         glMergeRecord *record;
327         GList         *fields, *p;
328         gint           i_field;
329         glMergeField  *field;
330
331         merge_text = GL_MERGE_TEXT (merge);
332
333         delim = merge_text->private->delim;
334         fp    = merge_text->private->fp;
335
336         if (fp == NULL) {
337                 return NULL;
338         }
339                
340         fields = parse_line (fp, delim);
341         if ( fields == NULL ) {
342                 return NULL;
343         }
344
345         record = g_new0 (glMergeRecord, 1);
346         record->select_flag = TRUE;
347         i_field = 1;
348         for (p=fields; p != NULL; p=p->next) {
349
350                 field = g_new0 (glMergeField, 1);
351                 field->key = g_strdup_printf ("%d", i_field++);
352 #ifndef CSV_ALWAYS_UTF8
353                 field->value = g_locale_to_utf8 (p->data, -1, NULL, NULL, NULL);
354 #else
355                 field->value = g_strdup (p->data);
356 #endif
357
358                 record->field_list = g_list_append (record->field_list, field);
359         }
360         free_fields (&fields);
361
362         return record;
363 }
364
365 /*---------------------------------------------------------------------------*/
366 /* Copy merge_text specific fields.                                          */
367 /*---------------------------------------------------------------------------*/
368 static void
369 gl_merge_text_copy (glMerge *dst_merge,
370                     glMerge *src_merge)
371 {
372         glMergeText *dst_merge_text;
373         glMergeText *src_merge_text;
374
375         dst_merge_text = GL_MERGE_TEXT (dst_merge);
376         src_merge_text = GL_MERGE_TEXT (src_merge);
377
378         dst_merge_text->private->delim = src_merge_text->private->delim;
379 }
380
381 /*---------------------------------------------------------------------------*/
382 /* PRIVATE.  Parse line (quoted values may span multiple lines).             */
383 /*---------------------------------------------------------------------------*/
384 static GList *
385 parse_line (FILE  *fp,
386             gchar  delim )
387 {
388         GList *list = NULL;
389         GString *string;
390         gint c;
391         enum { BEGIN, NORMAL, NORMAL_ESCAPED,
392                QUOTED, QUOTED_ESCAPED, QUOTED_QUOTE1,
393                DONE } state;
394
395         state = BEGIN;
396         string = g_string_new( "" );
397         while ( state != DONE ) {
398                 c=getc (fp);
399
400                 switch (state) {
401
402                 case BEGIN:
403                         switch (c) {
404                         case '\\':
405                                 state = NORMAL_ESCAPED;
406                                 break;
407                         case '"':
408                                 state = QUOTED;
409                                 break;
410                         case '\r':
411                                 /* Strip CR. */
412                                 state = NORMAL;
413                                 break;
414                         case '\n':
415                         case EOF:
416                                 state = DONE;
417                                 break;
418                         default:
419                                 if ( c != delim ) {
420                                         string = g_string_append_c (string, c);
421                                 } else {
422                                         list = g_list_append (list,
423                                                       g_strdup (string->str));
424                                         string = g_string_assign( string, "" );
425                                 }
426                                 state = NORMAL;
427                                 break;
428                         }
429                         break;
430
431                 case NORMAL:
432                         switch (c) {
433                         case '\\':
434                                 state = NORMAL_ESCAPED;
435                                 break;
436                         case '"':
437                                 state = QUOTED;
438                                 break;
439                         case '\r':
440                                 /* Strip CR. */
441                                 break;
442                         case '\n':
443                         case EOF:
444                                 list = g_list_append (list,
445                                                       g_strdup (string->str));
446                                 state = DONE;
447                                 break;
448                         default:
449                                 if ( c != delim ) {
450                                         string = g_string_append_c (string, c);
451                                 } else {
452                                         list = g_list_append (list,
453                                                       g_strdup (string->str));
454                                         string = g_string_assign( string, "" );
455                                 }
456                                 break;
457                         }
458                         break;
459
460                 case NORMAL_ESCAPED:
461                         switch (c) {
462                         case 'n':
463                                 string = g_string_append_c (string, '\n');
464                                 state = NORMAL;
465                                 break;
466                         case 't':
467                                 string = g_string_append_c (string, '\t');
468                                 state = NORMAL;
469                                 break;
470                         case '\r':
471                                 /* Strip CR, stay ESCAPED. */
472                                 break;
473                         case EOF:
474                                 state = DONE;
475                                 break;
476                         default:
477                                 string = g_string_append_c (string, c);
478                                 state = NORMAL;
479                                 break;
480                         }
481                         break;
482
483                 case QUOTED:
484                         switch (c) {
485                         case '\\':
486                                 state = QUOTED_ESCAPED;
487                                 break;
488                         case '"':
489                                 state = QUOTED_QUOTE1;
490                                 break;
491                         case '\r':
492                                 /* Strip CR. */
493                                 break;
494                         case EOF:
495                                 /* File ended mid way through quoted item */
496                                 list = g_list_append (list,
497                                                       g_strdup (string->str));
498                                 state = DONE;
499                                 break;
500                         default:
501                                 string = g_string_append_c (string, c);
502                                 break;
503                         }
504                         break;
505
506                 case QUOTED_ESCAPED:
507                         switch (c) {
508                         case 'n':
509                                 string = g_string_append_c (string, '\n');
510                                 state = QUOTED;
511                                 break;
512                         case 't':
513                                 string = g_string_append_c (string, '\t');
514                                 state = QUOTED;
515                                 break;
516                         case '\r':
517                                 /* Strip CR, stay ESCAPED. */
518                                 break;
519                         case EOF:
520                                 /* File ended mid way through quoted item */
521                                 list = g_list_append (list,
522                                                       g_strdup (string->str));
523                                 state = DONE;
524                                 break;
525                         default:
526                                 string = g_string_append_c (string, c);
527                                 state = QUOTED;
528                                 break;
529                         }
530                         break;
531
532                 case QUOTED_QUOTE1:
533                         switch (c) {
534                         case '"':
535                                 /* insert quotes in string, stay quoted. */
536                                 string = g_string_append_c (string, c);
537                                 state = QUOTED;
538                                 break;
539                         case '\r':
540                                 /* Strip CR, return to NORMAL. */
541                                 state = NORMAL;
542                                 break;
543                         case '\n':
544                         case EOF:
545                                 /* line or file ended after quoted item */
546                                 list = g_list_append (list,
547                                                       g_strdup (string->str));
548                                 state = DONE;
549                                 break;
550                         default:
551                                 if ( c != delim ) {
552                                         string = g_string_append_c (string, c);
553                                 } else {
554                                         list = g_list_append (list,
555                                                       g_strdup (string->str));
556                                         string = g_string_assign( string, "" );
557                                 }
558                                 state = NORMAL;
559                                 break;
560                         }
561                         break;
562
563                 default:
564                         g_assert_not_reached();
565                         break;
566                 }
567
568         }
569         g_string_free( string, TRUE );
570
571         return list;
572 }
573
574 /*---------------------------------------------------------------------------*/
575 /* Free list of fields.                                                      */
576 /*---------------------------------------------------------------------------*/
577 void
578 free_fields (GList ** list)
579 {
580         GList *p;
581
582         for (p = *list; p != NULL; p = p->next) {
583                 g_free (p->data);
584                 p->data = NULL;
585         }
586
587         g_list_free (*list);
588         *list = NULL;
589 }
590