Implemented automatic EXPLAIN database maintenance.
[idzebra-moved-to-github.git] / recctrl / recgrs.c
1 /*
2  * Copyright (C) 1994-1998, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: recgrs.c,v $
7  * Revision 1.20  1998-05-20 10:12:26  adam
8  * Implemented automatic EXPLAIN database maintenance.
9  * Modified Zebra to work with ASN.1 compiled version of YAZ.
10  *
11  * Revision 1.19  1998/03/11 11:19:05  adam
12  * Changed the way sequence numbers are generated.
13  *
14  * Revision 1.18  1998/03/05 08:41:31  adam
15  * Minor changes.
16  *
17  * Revision 1.17  1998/02/10 12:03:06  adam
18  * Implemented Sort.
19  *
20  * Revision 1.16  1998/01/29 13:38:17  adam
21  * Fixed problem with mapping to record with unknown schema.
22  *
23  * Revision 1.15  1998/01/26 10:37:57  adam
24  * Better diagnostics.
25  *
26  * Revision 1.14  1997/11/06 11:41:01  adam
27  * Implemented "begin variant" for the sgml.regx filter.
28  *
29  * Revision 1.13  1997/10/31 12:35:44  adam
30  * Added a few log statements.
31  *
32  * Revision 1.12  1997/10/29 12:02:22  adam
33  * Using oid_ent_to_oid used instead of the non thread-safe oid_getoidbyent.
34  *
35  * Revision 1.11  1997/10/27 14:34:00  adam
36  * Work on generic character mapping depending on "structure" field
37  * in abstract syntax file.
38  *
39  * Revision 1.10  1997/09/18 08:59:21  adam
40  * Extra generic handle for the character mapping routines.
41  *
42  * Revision 1.9  1997/09/17 12:19:21  adam
43  * Zebra version corresponds to YAZ version 1.4.
44  * Changed Zebra server so that it doesn't depend on global common_resource.
45  *
46  * Revision 1.8  1997/09/09 13:38:14  adam
47  * Partial port to WIN95/NT.
48  *
49  * Revision 1.7  1997/09/05 15:30:10  adam
50  * Changed prototype for chr_map_input - added const.
51  * Added support for C++, headers uses extern "C" for public definitions.
52  *
53  * Revision 1.6  1997/09/04 13:54:40  adam
54  * Added MARC filter - type grs.marc.<syntax> where syntax refers
55  * to abstract syntax. New method tellf in retrieve/extract method.
56  *
57  * Revision 1.5  1997/07/15 16:29:03  adam
58  * Initialized dummy variable to keep checker gcc happy.
59  *
60  * Revision 1.4  1997/04/30 08:56:08  quinn
61  * null
62  *
63  * Revision 1.2  1996/10/11  16:06:43  quinn
64  * Revision 1.3  1997/02/24 10:41:50  adam
65  * Cleanup of code and commented out the "end element-end-record" code.
66  *
67  * Revision 1.2  1996/10/11 16:06:43  quinn
68  * Fixed arguments to nodetogr
69  *
70  * Revision 1.1  1996/10/11  10:57:25  adam
71  * New module recctrl. Used to manage records (extract/retrieval).
72  *
73  * Revision 1.29  1996/10/08 10:30:21  quinn
74  * Fixed type mismatch
75  *
76  * Revision 1.28  1996/10/07  16:06:40  quinn
77  * Added SOIF support
78  *
79  * Revision 1.27  1996/06/11  10:54:12  quinn
80  * Relevance work
81  *
82  * Revision 1.26  1996/06/06  12:08:45  quinn
83  * Added showRecord function
84  *
85  * Revision 1.25  1996/06/04  14:18:53  quinn
86  * Charmap work
87  *
88  * Revision 1.24  1996/06/04  13:27:54  quinn
89  * More work on charmapping
90  *
91  * Revision 1.23  1996/06/04  10:19:01  adam
92  * Minor changes - removed include of ctype.h.
93  *
94  * Revision 1.22  1996/06/03  10:15:27  quinn
95  * Various character-mapping.
96  *
97  * Revision 1.21  1996/05/31  13:27:24  quinn
98  * Character-conversion in phrases, too.
99  *
100  * Revision 1.19  1996/05/16  15:31:14  quinn
101  * a7
102  *
103  * Revision 1.18  1996/05/09  07:28:56  quinn
104  * Work towards phrases and multiple registers
105  *
106  * Revision 1.17  1996/05/01  13:46:37  adam
107  * First work on multiple records in one file.
108  * New option, -offset, to the "unread" command in the filter module.
109  *
110  * Revision 1.16  1996/01/17  14:57:54  adam
111  * Prototype changed for reader functions in extract/retrieve. File
112  *  is identified by 'void *' instead of 'int.
113  *
114  * Revision 1.15  1996/01/08  19:15:47  adam
115  * New input filter that works!
116  *
117  * Revision 1.14  1995/12/15  12:36:11  adam
118  * Retrieval calls data1_read_regx when subType is specified.
119  *
120  * Revision 1.13  1995/12/15  12:24:43  quinn
121  * *** empty log message ***
122  *
123  * Revision 1.12  1995/12/15  12:20:28  quinn
124  * *** empty log message ***
125  *
126  * Revision 1.11  1995/12/15  12:07:57  quinn
127  * Changed extraction strategy.
128  *
129  * Revision 1.10  1995/12/14  11:10:48  quinn
130  * Explain work
131  *
132  * Revision 1.9  1995/12/13  17:14:05  quinn
133  * *** empty log message ***
134  *
135  * Revision 1.8  1995/12/13  15:33:18  quinn
136  * *** empty log message ***
137  *
138  * Revision 1.7  1995/12/13  13:45:39  quinn
139  * Changed data1 to use nmem.
140  *
141  * Revision 1.6  1995/12/04  14:22:30  adam
142  * Extra arg to recType_byName.
143  * Started work on new regular expression parsed input to
144  * structured records.
145  *
146  * Revision 1.5  1995/11/28  14:18:37  quinn
147  * Set output_format.
148  *
149  * Revision 1.4  1995/11/21  13:14:49  quinn
150  * Fixed end-of-data-field problem (maybe).
151  *
152  * Revision 1.3  1995/11/15  19:13:09  adam
153  * Work on record management.
154  *
155  */
156
157 #include <stdio.h>
158 #include <assert.h>
159 #include <sys/types.h>
160 #ifndef WINDOWS
161 #include <unistd.h>
162 #endif
163
164 #include <log.h>
165 #include <oid.h>
166
167 #include <recctrl.h>
168 #include "grsread.h"
169
170 #define GRS_MAX_WORD 512
171
172 static data1_node *read_grs_type (struct grs_read_info *p, const char *type)
173 {
174     static struct {
175         char *type;
176         data1_node *(*func)(struct grs_read_info *p);
177     } tab[] = {
178         { "sgml",  grs_read_sgml },
179         { "regx",  grs_read_regx },
180         { "marc",  grs_read_marc },
181         { NULL, NULL }
182     };
183     const char *cp = strchr (type, '.');
184     int i;
185
186     if (cp == NULL || cp == type)
187     {
188         cp = strlen(type) + type;
189         *p->type = 0;
190     }
191     else
192         strcpy (p->type, cp+1);
193     for (i=0; tab[i].type; i++)
194     {
195         if (!memcmp (type, tab[i].type, cp-type))
196             return (tab[i].func)(p);
197     }
198     return NULL;
199 }
200
201 static void grs_init(void)
202 {
203 }
204
205 static int dumpkeys(data1_node *n, struct recExtractCtrl *p, int level)
206 {
207     RecWord wrd;
208     (*p->init)(p, &wrd);      /* set defaults */
209     for (; n; n = n->next)
210     {
211         if (p->flagShowRecords) /* display element description to user */
212         {
213             if (n->which == DATA1N_root)
214             {
215                 printf("%*s", level * 4, "");
216                 printf("Record type: '%s'\n", n->u.root.absyn->name);
217             }
218             else if (n->which == DATA1N_tag)
219             {
220                 data1_element *e;
221
222                 printf("%*s", level * 4, "");
223                 if (!(e = n->u.tag.element))
224                     printf("Local tag: '%s'\n", n->u.tag.tag);
225                 else
226                 {
227                     printf("Elm: '%s' ", e->name);
228                     if (e->tag)
229                     {
230                         data1_tag *t = e->tag;
231
232                         printf("TagNam: '%s' ", t->names->name);
233                         printf("(");
234                         if (t->tagset)
235                             printf("%s[%d],", t->tagset->name, t->tagset->type);
236                         else
237                             printf("?,");
238                         if (t->which == DATA1T_numeric)
239                             printf("%d)", t->value.numeric);
240                         else
241                             printf("'%s')", t->value.string);
242                     }
243                     printf("\n");
244                 }
245             }
246         }
247
248         if (n->child)
249             if (dumpkeys(n->child, p, level + 1) < 0)
250                 return -1;
251
252         if (n->which == DATA1N_data)
253         {
254             data1_node *par = get_parent_tag(p->dh, n);
255             data1_termlist *tlist = 0;
256             data1_datatype dtype = DATA1K_string;
257
258             if (p->flagShowRecords)
259             {
260                 printf("%*s", level * 4, "");
261                 printf("Data: ");
262                 if (n->u.data.len > 32)
263                     printf("'%.24s ... %.6s'\n", n->u.data.data,
264                            n->u.data.data + n->u.data.len-6);
265                 else if (n->u.data.len > 0)
266                     printf("'%.*s'\n", n->u.data.len, n->u.data.data);
267                 else
268                     printf("NULL\n");
269             }
270
271             assert(par);
272
273             /*
274              * cycle up towards the root until we find a tag with an att..
275              * this has the effect of indexing locally defined tags with
276              * the attribute of their ancestor in the record.
277              */
278
279             while (!par->u.tag.element)
280                 if (!par->parent || !(par=get_parent_tag(p->dh, par->parent)))
281                     break;
282             if (!par || !(tlist = par->u.tag.element->termlists))
283                 continue;
284             if (par->u.tag.element->tag)
285                 dtype = par->u.tag.element->tag->kind;
286             for (; tlist; tlist = tlist->next)
287             {
288                 if (p->flagShowRecords)
289                 {
290                     printf("%*sIdx: [%s]", (level + 1) * 4, "",
291                            tlist->structure);
292                     printf("%s:%s [%d]\n",
293                            tlist->att->parent->name,
294                            tlist->att->name, tlist->att->value);
295                 }
296                 else
297                 {
298                     wrd.reg_type = *tlist->structure;
299                     wrd.string = n->u.data.data;
300                     wrd.length = n->u.data.len;
301                     wrd.attrSet = (int) (tlist->att->parent->reference);
302                     wrd.attrUse = tlist->att->locals->local;
303                     (*p->addWord)(&wrd);
304                 }
305             }
306         }
307         if (p->flagShowRecords && n->which == DATA1N_root)
308         {
309             printf("%*s-------------\n\n", level * 4, "");
310         }
311     }
312     return 0;
313 }
314
315 int grs_extract_tree(struct recExtractCtrl *p, data1_node *n)
316 {
317     oident oe;
318     int oidtmp[OID_SIZE];
319
320     oe.proto = PROTO_Z3950;
321     oe.oclass = CLASS_SCHEMA;
322     oe.value = n->u.root.absyn->reference;
323
324     if ((oid_ent_to_oid (&oe, oidtmp)))
325         (*p->addSchema)(p, oidtmp);
326
327     return dumpkeys(n, p, 0);
328 }
329
330 static int grs_extract(struct recExtractCtrl *p)
331 {
332     data1_node *n;
333     NMEM mem;
334     struct grs_read_info gri;
335     oident oe;
336     int oidtmp[OID_SIZE];
337
338     mem = nmem_create (); 
339     gri.readf = p->readf;
340     gri.seekf = p->seekf;
341     gri.tellf = p->tellf;
342     gri.endf = p->endf;
343     gri.fh = p->fh;
344     gri.offset = p->offset;
345     gri.mem = mem;
346     gri.dh = p->dh;
347
348     n = read_grs_type (&gri, p->subType);
349     if (!n)
350         return -1;
351
352     oe.proto = PROTO_Z3950;
353     oe.oclass = CLASS_SCHEMA;
354     oe.value = n->u.root.absyn->reference;
355     if ((oid_ent_to_oid (&oe, oidtmp)))
356         (*p->addSchema)(p, oidtmp);
357
358     if (dumpkeys(n, p, 0) < 0)
359     {
360         data1_free_tree(p->dh, n);
361         return -2;
362     }
363     data1_free_tree(p->dh, n);
364     nmem_destroy(mem);
365     return 0;
366 }
367
368 /*
369  * Return: -1: Nothing done. 0: Ok. >0: Bib-1 diagnostic.
370  */
371 static int process_comp(data1_handle dh, data1_node *n, Z_RecordComposition *c)
372 {
373     data1_esetname *eset;
374     Z_Espec1 *espec = 0;
375     Z_ElementSpec *p;
376
377     switch (c->which)
378     {
379         case Z_RecordComp_simple:
380             if (c->u.simple->which != Z_ElementSetNames_generic)
381                 return 26; /* only generic form supported. Fix this later */
382             if (!(eset = data1_getesetbyname(dh, n->u.root.absyn,
383                 c->u.simple->u.generic)))
384             {
385                 logf(LOG_LOG, "Unknown esetname '%s'", c->u.simple->u.generic);
386                 return 25; /* invalid esetname */
387             }
388             logf(LOG_DEBUG, "Esetname '%s' in simple compspec",
389                 c->u.simple->u.generic);
390             espec = eset->spec;
391             break;
392         case Z_RecordComp_complex:
393             if (c->u.complex->generic)
394             {
395                 /* insert check for schema */
396                 if ((p = c->u.complex->generic->elementSpec))
397                     switch (p->which)
398                     {
399                         case Z_ElementSpec_elementSetName:
400                             if (!(eset =
401                                   data1_getesetbyname(dh,
402                                                       n->u.root.absyn,
403                                                       p->u.elementSetName)))
404                             {
405                                 logf(LOG_LOG, "Unknown esetname '%s'",
406                                     p->u.elementSetName);
407                                 return 25; /* invalid esetname */
408                             }
409                             logf(LOG_DEBUG, "Esetname '%s' in complex compspec",
410                                 p->u.elementSetName);
411                             espec = eset->spec;
412                             break;
413                         case Z_ElementSpec_externalSpec:
414                             if (p->u.externalSpec->which == Z_External_espec1)
415                             {
416                                 logf(LOG_DEBUG, "Got Espec-1");
417                                 espec = p->u.externalSpec-> u.espec1;
418                             }
419                             else
420                             {
421                                 logf(LOG_LOG, "Unknown external espec.");
422                                 return 25; /* bad. what is proper diagnostic? */
423                             }
424                             break;
425                     }
426             }
427             else
428                 return 26; /* fix */
429     }
430     if (espec)
431     {
432         logf (LOG_LOG, "Element: Espec-1 match");
433         return data1_doespec1(dh, n, espec);
434     }
435     else
436     {
437         logf (LOG_DEBUG, "Element: all match");
438         return -1;
439     }
440 }
441
442 static int grs_retrieve(struct recRetrieveCtrl *p)
443 {
444     data1_node *node = 0, *onode = 0;
445     data1_node *dnew;
446     data1_maptab *map;
447     int res, selected = 0;
448     NMEM mem;
449     struct grs_read_info gri;
450     
451     mem = nmem_create();
452     gri.readf = p->readf;
453     gri.seekf = p->seekf;
454     gri.tellf = p->tellf;
455     gri.endf = NULL;
456     gri.fh = p->fh;
457     gri.offset = 0;
458     gri.mem = mem;
459     gri.dh = p->dh;
460
461     logf (LOG_DEBUG, "grs_retrieve");
462     node = read_grs_type (&gri, p->subType);
463     if (!node)
464     {
465         p->diagnostic = 14;
466         nmem_destroy (mem);
467         return 0;
468     }
469     logf (LOG_DEBUG, "grs_retrieve: size");
470     if ((dnew = data1_insert_taggeddata(p->dh, node, node,
471                                        "size", mem)))
472     {
473         dnew->u.data.what = DATA1I_text;
474         dnew->u.data.data = dnew->lbuf;
475         sprintf(dnew->u.data.data, "%d", p->recordSize);
476         dnew->u.data.len = strlen(dnew->u.data.data);
477     }
478
479     logf (LOG_DEBUG, "grs_retrieve: score");
480     if (p->score >= 0 && (dnew =
481                           data1_insert_taggeddata(p->dh, node,
482                                                   node, "rank",
483                                                   mem)))
484     {
485         dnew->u.data.what = DATA1I_num;
486         dnew->u.data.data = dnew->lbuf;
487         sprintf(dnew->u.data.data, "%d", p->score);
488         dnew->u.data.len = strlen(dnew->u.data.data);
489     }
490
491     logf (LOG_DEBUG, "grs_retrieve: localControlNumber");
492     if ((dnew = data1_insert_taggeddata(p->dh, node, node,
493                                        "localControlNumber", mem)))
494     {
495         dnew->u.data.what = DATA1I_text;
496         dnew->u.data.data = dnew->lbuf;
497         sprintf(dnew->u.data.data, "%d", p->localno);
498         dnew->u.data.len = strlen(dnew->u.data.data);
499     }
500
501     logf (LOG_DEBUG, "grs_retrieve: schemaIdentifier");
502     if (p->input_format == VAL_GRS1 && node->u.root.absyn &&
503         node->u.root.absyn->reference != VAL_NONE)
504     {
505         oident oe;
506         Odr_oid *oid;
507         int oidtmp[OID_SIZE];
508
509         oe.proto = PROTO_Z3950;
510         oe.oclass = CLASS_SCHEMA;
511         oe.value = node->u.root.absyn->reference;
512
513         if ((oid = oid_ent_to_oid (&oe, oidtmp)))
514         {
515             char tmp[128];
516             data1_handle dh = p->dh;
517             char *p = tmp;
518             int *ii;
519
520             for (ii = oid; *ii >= 0; ii++)
521             {
522                 if (p != tmp)
523                     *(p++) = '.';
524                 sprintf(p, "%d", *ii);
525                 p += strlen(p);
526             }
527             *(p++) = '\0';
528
529             if ((dnew = data1_insert_taggeddata(dh, node, node,
530                                                "schemaIdentifier", mem)))
531             {
532                 dnew->u.data.what = DATA1I_oid;
533                 dnew->u.data.data = nmem_malloc(mem, p - tmp);
534                 memcpy(dnew->u.data.data, tmp, p - tmp);
535                 dnew->u.data.len = p - tmp;
536             }
537         }
538     }
539
540     logf (LOG_DEBUG, "grs_retrieve: schema mapping");
541     /*
542      * Does the requested format match a known schema-mapping? (this reflects
543      * the overlap of schema and formatting which is inherent in the MARC
544      * family)
545      * NOTE: This should look at the schema-specification in the compspec
546      * as well.
547      */
548     for (map = node->u.root.absyn->maptabs; map; map = map->next)
549         if (map->target_absyn_ref == p->input_format)
550         {
551             onode = node;
552             if (!(node = data1_map_record(p->dh, onode, map, mem)))
553             {
554                 p->diagnostic = 14;
555                 nmem_destroy (mem);
556                 return 0;
557             }
558             break;
559         }
560
561     logf (LOG_DEBUG, "grs_retrieve: element spec");
562     if (p->comp && (res = process_comp(p->dh, node, p->comp)) > 0)
563     {
564         p->diagnostic = res;
565         if (onode)
566             data1_free_tree(p->dh, onode);
567         data1_free_tree(p->dh, node);
568         nmem_destroy(mem);
569         return 0;
570     }
571     else if (p->comp && !res)
572         selected = 1;
573
574     logf (LOG_DEBUG, "grs_retrieve: transfer syntax mapping");
575     switch (p->output_format = (p->input_format != VAL_NONE ?
576         p->input_format : VAL_SUTRS))
577     {
578         data1_marctab *marctab;
579         int dummy;
580
581         case VAL_GRS1:
582             dummy = 0;
583             if (!(p->rec_buf = data1_nodetogr(p->dh, node, selected,
584                                               p->odr, &dummy)))
585                 p->diagnostic = 238; /* not available in requested syntax */
586             else
587                 p->rec_len = -1;
588             break;
589         case VAL_EXPLAIN:
590             if (!(p->rec_buf = data1_nodetoexplain(p->dh, node, selected,
591                                                    p->odr)))
592                 p->diagnostic = 238;
593             else
594                 p->rec_len = -1;
595             break;
596         case VAL_SUMMARY:
597             if (!(p->rec_buf = data1_nodetosummary(p->dh, node, selected,
598                                                    p->odr)))
599                 p->diagnostic = 238;
600             else
601                 p->rec_len = -1;
602             break;
603         case VAL_SUTRS:
604             if (!(p->rec_buf = data1_nodetobuf(p->dh, node, selected,
605                 (int*)&p->rec_len)))
606                 p->diagnostic = 238;
607             break;
608         case VAL_SOIF:
609             if (!(p->rec_buf = data1_nodetosoif(p->dh, node, selected,
610                                                 (int*)&p->rec_len)))
611                 p->diagnostic = 238;
612             break;
613         default:
614             if (!node->u.root.absyn)
615             {
616                 p->diagnostic = 238;
617                 break;
618             }
619             for (marctab = node->u.root.absyn->marc; marctab;
620                 marctab = marctab->next)
621                 if (marctab->reference == p->input_format)
622                     break;
623             if (!marctab)
624             {
625                 p->diagnostic = 238;
626                 break;
627             }
628             if (!(p->rec_buf = data1_nodetomarc(p->dh, marctab, node,
629                                                 selected,
630                                                 (int*)&p->rec_len)))
631             {
632                 p->diagnostic = 238;
633                 break;
634             }
635     }
636     if (node)
637         data1_free_tree(p->dh, node);
638     if (onode)
639         data1_free_tree(p->dh, onode);
640     nmem_destroy(mem);
641     return 0;
642 }
643
644 static struct recType grs_type =
645 {
646     "grs",
647     grs_init,
648     grs_extract,
649     grs_retrieve
650 };
651
652 RecType recTypeGrs = &grs_type;