Added support of the complex indexing for MARC records (whithout documentation yet).
[idzebra-moved-to-github.git] / recctrl / marcread.c
1 /* $Id: marcread.c,v 1.18 2003-02-28 12:33:39 oleg Exp $
2    Copyright (C) 1995,1996,1997,1998,1999,2000,2001,2002
3    Index Data Aps
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Zebra; see the file LICENSE.zebra.  If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20 02111-1307, USA.
21 */
22
23 #include <stdio.h>
24 #include <ctype.h>
25 #include <assert.h>
26
27 #include <yaz/log.h>
28 #include <yaz/yaz-util.h>
29 #include <yaz/marcdisp.h>
30 #include "grsread.h"
31 #include "marcomp.h"
32 #include "inline.h"
33
34 #define MARC_DEBUG 0
35 #define MARCOMP_DEBUG 0
36
37 static data1_node *grs_read_iso2709 (struct grs_read_info *p)
38 {
39     char buf[100000];
40     int entry_p;
41     int record_length;
42     int indicator_length;
43     int identifier_length;
44     int base_address;
45     int length_data_entry;
46     int length_starting;
47     int length_implementation;
48     int read_bytes;
49 #if MARC_DEBUG
50     FILE *outf = stdout;
51 #endif
52     data1_node *res_root, *res_top;
53     char *absynName;
54     data1_marctab *marctab;
55
56     if ((*p->readf)(p->fh, buf, 5) != 5)
57         return NULL;
58     record_length = atoi_n (buf, 5);
59     if (record_length < 25)
60     {
61         logf (LOG_WARN, "MARC record length < 25, is %d", record_length);
62         return NULL;
63     }
64     /* read remaining part - attempt to read one byte furhter... */
65     read_bytes = (*p->readf)(p->fh, buf+5, record_length-4);
66     if (read_bytes < record_length-5)
67     {
68         logf (LOG_WARN, "Couldn't read whole MARC record");
69         return NULL;
70     }
71     if (read_bytes == record_length - 4)
72     {
73         off_t cur_offset = (*p->tellf)(p->fh);
74         if (cur_offset <= 27)
75             return NULL;
76         if (p->endf)
77             (*p->endf)(p->fh, cur_offset - 1);
78     }
79     absynName = p->type;
80     res_root = data1_mk_root (p->dh, p->mem, absynName);
81     if (!res_root)
82     {
83         yaz_log (LOG_WARN, "cannot read MARC without an abstract syntax");
84         return 0;
85     }
86     res_top = data1_mk_tag (p->dh, p->mem, absynName, 0, res_root);
87
88     if (marctab = res_root->u.root.absyn->marc)
89     {
90         memcpy(marctab->leader, buf, 24);
91         memcpy(marctab->implementation_codes, buf+6, 4);
92         marctab->implementation_codes[4] = '\0';
93         memcpy(marctab->user_systems, buf+17, 3);
94         marctab->user_systems[3] = '\0';
95     }
96
97     if (marctab && marctab->force_indicator_length >= 0)
98         indicator_length = marctab->force_indicator_length;
99     else
100         indicator_length = atoi_n (buf+10, 1);
101     if (marctab && marctab->force_identifier_length >= 0)
102         identifier_length = marctab->force_identifier_length;
103     else
104         identifier_length = atoi_n (buf+11, 1);
105     base_address = atoi_n (buf+12, 4);
106
107     length_data_entry = atoi_n (buf+20, 1);
108     length_starting = atoi_n (buf+21, 1);
109     length_implementation = atoi_n (buf+22, 1);
110
111     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
112         entry_p += 3+length_data_entry+length_starting;
113     base_address = entry_p+1;
114     for (entry_p = 24; buf[entry_p] != ISO2709_FS; )
115     {
116         int data_length;
117         int data_offset;
118         int end_offset;
119         int i, i0;
120         char tag[4];
121         data1_node *res;
122         data1_node *parent = res_top;
123
124         memcpy (tag, buf+entry_p, 3);
125         entry_p += 3;
126         tag[3] = '\0';
127
128
129         /* generate field node */
130         res = data1_mk_tag_n (p->dh, p->mem, tag, 3, 0 /* attr */, parent);
131
132 #if MARC_DEBUG
133         fprintf (outf, "%s ", tag);
134 #endif
135         data_length = atoi_n (buf+entry_p, length_data_entry);
136         entry_p += length_data_entry;
137         data_offset = atoi_n (buf+entry_p, length_starting);
138         entry_p += length_starting;
139         i = data_offset + base_address;
140         end_offset = i+data_length-1;
141
142         if (memcmp (tag, "00", 2) && indicator_length)
143         {
144             /* generate indicator node */
145 #if MARC_DEBUG
146             int j;
147 #endif
148             res = data1_mk_tag_n (p->dh, p->mem, 
149                                   buf+i, indicator_length, 0 /* attr */, res);
150 #if MARC_DEBUG
151             for (j = 0; j<indicator_length; j++)
152                 fprintf (outf, "%c", buf[j+i]);
153 #endif
154             i += indicator_length;
155         }
156         parent = res;
157         /* traverse sub fields */
158         i0 = i;
159         while (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS && i < end_offset)
160         {
161
162             if (!memcmp(tag, "4", 1) && (!yaz_matchstr(absynName, "UNIMARC")||
163                 !yaz_matchstr(absynName, "RUSMARC")))
164             {
165                 int go = 1;
166                 data1_node *res =
167                     data1_mk_tag_n (p->dh, p->mem,
168                                     buf+i+1, identifier_length-1, 
169                                     0 /* attr */, parent);
170                 i += identifier_length;
171                 i0 = i;
172                 do {
173                     while (buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
174                          buf[i] != ISO2709_FS && i < end_offset)
175                     {
176                         i++;
177                     }
178                     if (!memcmp(buf+i+1, "1", 1) && i<end_offset)
179                     {
180                         go = 0;
181                     }
182                     else
183                     {
184                         buf[i] = '$';
185                     }               
186                 } while (go && i < end_offset);
187                 
188                 data1_mk_text_n (p->dh, p->mem, buf + i0, i - i0, res);
189                 i0 = i;
190             }
191             else if (memcmp (tag, "00", 2) && identifier_length)
192             {
193                 data1_node *res =
194                     data1_mk_tag_n (p->dh, p->mem,
195                                     buf+i+1, identifier_length-1, 
196                                     0 /* attr */, parent);
197 #if MARC_DEBUG
198                 fprintf (outf, " $"); 
199                 for (j = 1; j<identifier_length; j++)
200                     fprintf (outf, "%c", buf[j+i]);
201                 fprintf (outf, " ");
202 #endif
203                 i += identifier_length;
204                 i0 = i;
205                 while (buf[i] != ISO2709_RS && buf[i] != ISO2709_IDFS &&
206                      buf[i] != ISO2709_FS && i < end_offset)
207                 {
208 #if MARC_DEBUG
209                     fprintf (outf, "%c", buf[i]);
210 #endif
211                     i++;
212                 }
213                 data1_mk_text_n (p->dh, p->mem, buf + i0, i - i0, res);
214                 i0 = i;
215             }
216             else
217             {
218 #if MARC_DEBUG
219                 fprintf (outf, "%c", buf[i]);
220 #endif
221                 i++;
222             }
223         }
224         if (i > i0)
225         {
226             data1_mk_text_n (p->dh, p->mem, buf + i0, i - i0, parent);
227         }
228 #if MARC_DEBUG
229         fprintf (outf, "\n");
230         if (i < end_offset)
231             fprintf (outf, "-- separator but not at end of field\n");
232         if (buf[i] != ISO2709_RS && buf[i] != ISO2709_FS)
233             fprintf (outf, "-- no separator at end of field\n");
234 #endif
235     }
236     return res_root;
237 }
238 /*
239  * Locate some data under this node. This routine should handle variants
240  * prettily.
241  */
242 static char *get_data(data1_node *n, int *len)
243 {
244     char *r;
245
246     while (n)
247     {
248         if (n->which == DATA1N_data)
249         {
250             int i;
251             *len = n->u.data.len;
252
253             for (i = 0; i<*len; i++)
254                 if (!d1_isspace(n->u.data.data[i]))
255                     break;
256             while (*len && d1_isspace(n->u.data.data[*len - 1]))
257                 (*len)--;
258             *len = *len - i;
259             if (*len > 0)
260                 return n->u.data.data + i;
261         }
262         if (n->which == DATA1N_tag)
263             n = n->child;
264         else if (n->which == DATA1N_data)
265             n = n->next;
266         else
267             break;      
268     }
269     r = "";
270     *len = strlen(r);
271     return r;
272 }
273 static char *tr(char *s, int c1, int c2)
274 {
275     char *p = s;
276     
277     while(*p)
278     {
279         if (*p == c1)
280             *p = c2;
281         p++;
282     }
283     return s;
284 }
285 static data1_node *lookup_subfield(data1_node *node, const char *name)
286 {
287     data1_node *p;
288     
289     for (p=node; p; p=p->next)
290     {
291         if (!yaz_matchstr(p->u.tag.tag, name))
292             return p;
293     }
294     return 0;
295 }
296 static inline_subfield *lookup_inline_subfield(inline_subfield *pisf, const char *name)
297 {
298     inline_subfield *p;
299     
300     for (p=pisf; p; p=p->next)
301     {
302         if (!yaz_matchstr(p->name, name))
303             return p;
304     }
305     return 0;
306 }
307 static inline_subfield *cat_inline_subfield(mc_subfield *psf, char *buf, inline_subfield *pisf)
308 {
309     mc_subfield *p;
310     
311     for (p = psf; p && pisf; p = p->next)
312     {
313         if (p->which == MC_SF)
314         {
315             inline_subfield *found = lookup_inline_subfield(pisf, p->name);
316             
317             if (found)
318             {
319                 if (strcmp(p->prefix, "_"))
320                 {
321                     strcat(strcat(buf, " "), p->prefix);
322                 }
323                 if (p->interval.start == -1)
324                 {
325                     strcat(buf, found->data);
326                 }
327                 else
328                 {
329                     strncat(buf, found->data+p->interval.start,
330                         p->interval.end-p->interval.start+1);
331                 }
332                 if (strcmp(p->suffix, "_"))
333                 {
334                     strcat(strcat(buf, p->suffix), " ");
335                 }
336 #if MARCOMP_DEBUG
337                 logf(LOG_LOG, "cat_inline_subfield(): add subfield $%s", found->name);
338 #endif          
339                 pisf = found->next;
340             }
341         }
342         else if (p->which == MC_SFVARIANT)
343         {
344             inline_subfield *next;
345             
346             do {
347                 next = cat_inline_subfield(p->u.child, buf, pisf);
348                 if (next == pisf)
349                     break;
350                 pisf = next;
351             } while (pisf);
352         }
353         else if (p->which == MC_SFGROUP)
354         {
355             mc_subfield *pp;
356             int found;
357             
358             for (pp = p->u.child, found = 0; pp; pp = pp->next)
359             {
360                 if (!yaz_matchstr(pisf->name, p->name))
361                 {
362                     found = 1;
363                     break;
364                 }
365             }
366             if (found)
367             {
368                 strcat(buf, " (");
369                 pisf = cat_inline_subfield(p->u.child, buf, pisf);
370                 strcat(buf, ") ");
371             }
372         }
373     }
374     return pisf; 
375 }
376 static void cat_inline_field(mc_field *pf, char *buf, data1_node *subfield)
377 {
378     
379     if (!pf || !subfield)
380         return;
381
382     for (;subfield; subfield = subfield->next)
383     {
384         int len;
385         inline_field *pif = inline_parse(get_data(subfield,&len));
386         
387         if (pif && !yaz_matchstr(pif->name, pf->name))
388         {
389             if (!pf->list && pif->list)
390             {
391                 strcat(buf, pif->list->data);
392             }
393             else
394             {
395                 int ind1, ind2;
396
397                 /*
398                     check indicators
399                 */
400
401                 ind1 = (pif->ind1[0] == ' ') ? '_':pif->ind1[0];
402                 ind2 = (pif->ind2[0] == ' ') ? '_':pif->ind2[0];
403     
404                 if (((pf->ind1[0] == '.') || (ind1 == pf->ind1[0])) &&
405                     ((pf->ind2[0] == '.') || (ind2 == pf->ind2[0])))
406                 {
407                     cat_inline_subfield(pf->list, buf, pif->list);
408                     
409                     /*
410                         add separator for inline fields
411                     */
412                     if (strlen(buf))
413                     {
414                         strcat(buf, "\n");
415                     }
416                 }
417                 else
418                 {
419                     logf(LOG_WARN, "In-line field %s missed -- indicators does not match", pif->name);
420                 }
421             }
422         }
423         inline_destroy_field(pif);
424     }
425 #if MARCOMP_DEBUG    
426     logf(LOG_LOG, "cat_inline_field(): got buffer {%s}", buf);
427 #endif
428 }
429 static data1_node *cat_subfield(mc_subfield *psf, char *buf, data1_node *subfield)
430 {
431     mc_subfield *p;
432     
433     for (p = psf; p && subfield; p = p->next)
434     {
435         if (p->which == MC_SF)
436         {
437             data1_node *found = lookup_subfield(subfield, p->name);
438             
439             if (found)
440             {
441                 int len;
442                 
443                 if (strcmp(p->prefix, "_"))
444                 {
445                     strcat(strcat(buf, " "), p->prefix);
446                 }
447                 
448                 if (p->u.in_line)
449                 {
450                     cat_inline_field(p->u.in_line, buf, found);
451                 }
452                 else if (p->interval.start == -1)
453                 {
454                     strcat(buf, get_data(found, &len));
455                 }
456                 else
457                 {
458                     strncat(buf, get_data(found, &len)+p->interval.start,
459                         p->interval.end-p->interval.start+1);
460                 }
461                 if (strcmp(p->suffix, "_"))
462                 {
463                     strcat(strcat(buf, p->suffix), " ");
464                 }
465 #if MARCOMP_DEBUG               
466                 logf(LOG_LOG, "cat_subfield(): add subfield $%s", found->u.tag.tag);
467 #endif          
468                 subfield = found->next;
469             }
470         }
471         else if (p->which == MC_SFVARIANT)
472         {
473             data1_node *next;
474             do {
475                 next = cat_subfield(p->u.child, buf, subfield);
476                 if (next == subfield)
477                     break;
478                 subfield = next;
479             } while (subfield);
480         }
481         else if (p->which == MC_SFGROUP)
482         {
483             mc_subfield *pp;
484             int found;
485             
486             for (pp = p->u.child, found = 0; pp; pp = pp->next)
487             {
488                 if (!yaz_matchstr(subfield->u.tag.tag, pp->name))
489                 {
490                     found = 1;
491                     break;
492                 }
493             }
494             if (found)
495             {
496                 strcat(buf, " (");
497                 subfield = cat_subfield(p->u.child, buf, subfield);
498                 strcat(buf, ") ");
499             }
500         }
501     }
502     return subfield;
503 }
504 static data1_node *cat_field(struct grs_read_info *p, mc_field *pf, char *buf, data1_node *field)
505 {
506     data1_node *subfield;
507     int ind1, ind2;
508     
509     if (!pf || !field)
510         return 0;
511
512     
513     if (yaz_matchstr(field->u.tag.tag, pf->name))
514         return field->next;
515
516     subfield = field->child;
517     
518     if (!subfield)
519         return field->next;
520
521     /*
522         check subfield without indicators
523     */
524     
525     if (!pf->list && subfield->which == DATA1N_data)
526     {
527         int len;
528
529         strcat(buf, get_data(field, &len));
530 #if MARCOMP_DEBUG
531         logf(LOG_LOG, "cat_field(): got buffer {%s}", buf);
532 #endif
533         return field->next;
534     }
535     
536     /*
537         check indicators
538     */
539
540     ind1 = (subfield->u.tag.tag[0] == ' ') ? '_':subfield->u.tag.tag[0];
541     ind2 = (subfield->u.tag.tag[1] == ' ') ? '_':subfield->u.tag.tag[1];
542     
543     if (!(
544         ((pf->ind1[0] == '.') || (ind1 == pf->ind1[0])) &&
545         ((pf->ind2[0] == '.') || (ind2 == pf->ind2[0]))
546         ))
547     {
548 #if MARCOMP_DEBUG
549         logf(LOG_WARN, "Field %s missed -- does not match indicators", field->u.tag.tag);
550 #endif
551         return field->next;
552     }
553     
554     subfield = subfield->child;
555     
556     if (!subfield)
557         return field->next;
558
559     cat_subfield(pf->list, buf, subfield);
560
561 #if MARCOMP_DEBUG    
562     logf(LOG_LOG, "cat_field(): got buffer {%s}", buf);
563 #endif
564     
565     return field->next;    
566 }
567 static int is_empty(char *s)
568 {
569     char *p = s;
570     
571     for (p = s; *p; p++)
572     {
573         if (!isspace(*p))
574             return 0;
575     }
576     return 1;
577 }
578 static void parse_data1_tree(struct grs_read_info *p, const char *mc_stmnt, data1_node *root)
579 {
580     data1_marctab *marctab = root->u.root.absyn->marc;
581     data1_node *top = root->child;
582     data1_node *field;
583     mc_context *c;
584     mc_field *pf;
585     char buf[1000000];
586     
587     c = mc_mk_context(mc_stmnt+3);
588     
589     if (!c)
590         return;
591         
592     pf = mc_getfield(c);
593     
594     if (!pf)
595     {
596         mc_destroy_context(c);
597         return;
598     }
599 #if MARCOMP_DEBUG    
600     logf(LOG_LOG, "parse_data1_tree(): statement -{%s}", mc_stmnt);
601 #endif
602     if (!yaz_matchstr(pf->name, "ldr"))
603     {
604         data1_node *new;
605 #if MARCOMP_DEBUG
606         logf(LOG_LOG,"parse_data1_tree(): try LEADER from {%d} to {%d} positions",
607             pf->interval.start, pf->interval.end);
608 #endif  
609         new = data1_mk_tag_n(p->dh, p->mem, mc_stmnt, strlen(mc_stmnt), 0, top);
610         data1_mk_text_n(p->dh, p->mem, marctab->leader+pf->interval.start,
611             pf->interval.end-pf->interval.start+1, new);
612     }
613     else
614     {
615         field=top->child;
616         
617         while(field)
618         {
619             if (!yaz_matchstr(field->u.tag.tag, pf->name))
620             {
621                 data1_node *new;
622                 char *pb = buf;
623 #if MARCOMP_DEBUG               
624                 logf(LOG_LOG, "parse_data1_tree(): try field {%s}", field->u.tag.tag);
625 #endif          
626                 *buf = '\0';        
627                 field = cat_field(p, pf, buf, field);
628                 
629                 for (pb = strtok(pb, "\n"); pb; pb = strtok(NULL, "\n"))
630                 {
631                         if (!is_empty(pb))
632                         {
633                                 new = data1_mk_tag_n(p->dh, p->mem, mc_stmnt, strlen(mc_stmnt), 0, top);
634                                 data1_mk_text_n(p->dh, p->mem, pb, strlen(pb), new);
635                         }
636                 }
637             }
638             else
639             {
640                 field = field->next;
641             }
642         }
643     }
644     mc_destroy_field(pf);
645     mc_destroy_context(c);
646 }
647
648 data1_node *grs_read_marc(struct grs_read_info *p)
649 {
650     data1_node *root = grs_read_iso2709(p);
651     data1_element *e;
652
653     if (!root)
654         return 0;
655         
656     for (e=root->u.root.absyn->main_elements; e; e=e->next)
657     {
658         data1_tag *tag = e->tag;
659         
660         if (tag && tag->which == DATA1T_string &&
661             !yaz_matchstr(tag->value.string, "mc?"))
662                 parse_data1_tree(p, tag->value.string, root);
663     }
664     return root;
665 }
666 static void *grs_init_marc(void)
667 {
668     return 0;
669 }
670
671 static void grs_destroy_marc(void *clientData)
672 {
673 }
674
675 static struct recTypeGrs marc_type = {
676     "marc",
677     grs_init_marc,
678     grs_destroy_marc,
679     grs_read_marc
680 };
681
682 RecTypeGrs recTypeGrs_marc = &marc_type;