Make data1 to EXPLAIN ignore local tags in root.
[yaz-moved-to-github.git] / retrieval / d1_expout.c
1 /*
2  * Copyright (c) 1995-1997, Index Data.
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: d1_expout.c,v $
7  * Revision 1.9  1998-03-05 08:07:58  adam
8  * Make data1 to EXPLAIN ignore local tags in root.
9  *
10  * Revision 1.8  1998/02/11 11:53:35  adam
11  * Changed code so that it compiles as C++.
12  *
13  * Revision 1.7  1997/12/09 16:18:16  adam
14  * Work on EXPLAIN schema. First implementation of sub-schema facility
15  * in the *.abs files.
16  *
17  * Revision 1.6  1997/11/24 11:33:56  adam
18  * Using function odr_nullval() instead of global ODR_NULLVAL when
19  * appropriate.
20  *
21  * Revision 1.5  1997/11/19 10:30:06  adam
22  * More explain work.
23  *
24  * Revision 1.4  1997/11/18 09:51:08  adam
25  * Removed element num_children from data1_node. Minor changes in
26  * data1 to Explain.
27  *
28  * Revision 1.3  1997/09/17 12:10:36  adam
29  * YAZ version 1.4.
30  *
31  * Revision 1.2  1995/12/14 16:28:30  quinn
32  * More explain stuff.
33  *
34  * Revision 1.1  1995/12/14  11:09:51  quinn
35  * Work on Explain
36  *
37  *
38  */
39
40 #include <assert.h>
41 #include <string.h>
42 #include <stdlib.h>
43
44 #include <log.h>
45 #include <proto.h>
46 #include <data1.h>
47
48 typedef struct {
49     data1_handle dh;
50     ODR o;
51     int select;
52
53     bool_t *false_value;
54     bool_t *true_value;
55 } ExpHandle;
56
57 static int is_numeric_tag (ExpHandle *eh, data1_node *c)
58 {
59     if (!c || c->which != DATA1N_tag)
60         return 0;
61     if (!c->u.tag.element)
62     {
63         logf(LOG_WARN, "Tag %s is local", c->u.tag.tag);
64         return 0;
65     }
66     if (c->u.tag.element->tag->which != DATA1T_numeric)
67     {
68         logf(LOG_WARN, "Tag %s is not numeric", c->u.tag.tag);
69         return 0;
70     }
71     if (eh->select && !c->u.tag.node_selected)
72         return 0;
73     return c->u.tag.element->tag->value.numeric;
74 }
75
76 static int is_data_tag (ExpHandle *eh, data1_node *c)
77 {
78     if (!c || c->which != DATA1N_data)
79         return 0;
80     if (eh->select && !c->u.tag.node_selected)
81         return 0;
82     return 1;
83 }
84
85 static int *f_integer(ExpHandle *eh, data1_node *c)
86 {
87     int *r;
88     char intbuf[64];
89
90     c = c->child;
91     if (!is_data_tag (eh, c) || c->u.data.len > 63)
92         return 0;
93     r = (int *)odr_malloc(eh->o, sizeof(*r));
94     sprintf(intbuf, "%.*s", 63, c->u.data.data);
95     *r = atoi(intbuf);
96     return r;
97 }
98
99 static char *f_string(ExpHandle *eh, data1_node *c)
100 {
101     char *r;
102
103     c = c->child;
104     if (!is_data_tag (eh, c))
105         return 0;
106     r = (char *)odr_malloc(eh->o, c->u.data.len+1);
107     memcpy(r, c->u.data.data, c->u.data.len);
108     r[c->u.data.len] = '\0';
109     return r;
110 }
111
112 static bool_t *f_bool(ExpHandle *eh, data1_node *c)
113 {
114     bool_t *tf;
115     char intbuf[64];
116
117     c = c->child;
118     if (!is_data_tag (eh, c) || c->u.data.len > 63)
119         return 0;
120     tf = (int *)odr_malloc (eh->o, sizeof(*tf));
121     sprintf(intbuf, "%.*s", c->u.data.len, c->u.data.data);
122     *tf = atoi(intbuf);
123     return tf;
124 }
125
126 static Odr_oid *f_oid(ExpHandle *eh, data1_node *c, oid_class oclass)
127 {
128     char oidstr[64];
129     int oid_this[20];
130     oid_value value_for_this;
131
132     c = c->child;
133     if (!is_data_tag (eh, c) || c->u.data.len > 63)
134         return 0;
135     sprintf(oidstr, "%.*s", c->u.data.len, c->u.data.data);
136     value_for_this = oid_getvalbyname(oidstr);
137     if (value_for_this == VAL_NONE)
138         return NULL; /* fix */
139     else
140     {
141         struct oident ident;
142
143         ident.oclass = oclass;
144         ident.proto = PROTO_Z3950;
145         ident.value = value_for_this;
146         
147         oid_ent_to_oid (&ident, oid_this);
148     }
149     return odr_oiddup (eh->o, oid_this);
150 }
151
152 static Z_IntUnit *f_intunit(ExpHandle *eh, data1_node *c)
153 {
154     /* fix */
155     return 0;
156 }
157
158 static Z_HumanString *f_humstring(ExpHandle *eh, data1_node *c)
159 {
160     Z_HumanString *r;
161     Z_HumanStringUnit *u;
162
163     c = c->child;
164     if (!is_data_tag (eh, c))
165         return 0;
166     r = (Z_HumanString *)odr_malloc(eh->o, sizeof(*r));
167     r->num_strings = 1;
168     r->strings = (Z_HumanStringUnit **)odr_malloc(eh->o, sizeof(Z_HumanStringUnit*));
169     r->strings[0] = u = (Z_HumanStringUnit *)odr_malloc(eh->o, sizeof(*u));
170     u->language = 0;
171     u->text = (char *)odr_malloc(eh->o, c->u.data.len+1);
172     memcpy(u->text, c->u.data.data, c->u.data.len);
173     u->text[c->u.data.len] = '\0';
174     return r;
175 }
176
177 static Z_CommonInfo *f_commonInfo(ExpHandle *eh, data1_node *n)
178 {
179     Z_CommonInfo *res = (Z_CommonInfo *)odr_malloc(eh->o, sizeof(*res));
180     data1_node *c;
181
182     res->dateAdded = 0;
183     res->dateChanged = 0;
184     res->expiry = 0;
185     res->humanStringLanguage = 0;
186     res->otherInfo = 0;
187
188     for (c = n->child; c; c = c->next)
189     {
190         switch (is_numeric_tag (eh, c))
191         {
192             case 601: res->dateAdded = f_string(eh, c); break;
193             case 602: res->dateChanged = f_string(eh, c); break;
194             case 603: res->expiry = f_string(eh, c); break;
195             case 604: res->humanStringLanguage = f_string(eh, c); break;
196         }
197     }
198     return res;
199 }
200
201 Z_QueryTypeDetails *f_queryTypeDetails (ExpHandle *eh, data1_node *n)
202 {
203     /* fix */
204     return NULL;
205 }
206
207 Odr_oid **f_oid_seq (ExpHandle *eh, data1_node *n, int *num, oid_class oclass)
208 {
209     Odr_oid **res;
210     data1_node *c;
211     int i;
212
213     *num = 0;
214     for (c = n->child ; c; c = c->next)
215     {
216         if (is_numeric_tag (eh, c) != 1000)
217             continue;
218         ++(*num);
219     }
220     if (!*num)
221         return NULL;
222     res = (int **)odr_malloc (eh->o, sizeof(*res) * (*num));
223     for (c = n->child, i = 0 ; c; c = c->next)
224     {
225         if (is_numeric_tag (eh, c) != 1000)
226             continue;
227         res[i++] = f_oid (eh, c, oclass);
228     }
229     return res;
230 }
231     
232 char **f_string_seq (ExpHandle *eh, data1_node *n, int *num)
233 {
234     char **res;
235     data1_node *c;
236     int i;
237
238     *num = 0;
239     for (c = n->child ; c; c = c->next)
240     {
241         if (!is_numeric_tag (eh, c) != 1001)
242             continue;
243         ++(*num);
244     }
245     if (!*num)
246         return NULL;
247     res = (char **)odr_malloc (eh->o, sizeof(*res) * (*num));
248     for (c = n->child, i = 0 ; c; c = c->next)
249     {
250         if (!is_numeric_tag (eh, c) != 1001)
251             continue;
252         res[i++] = f_string (eh, c);
253     }
254     return res;
255 }
256
257 char **f_humstring_seq (ExpHandle *eh, data1_node *n, int *num)
258 {
259     /* fix */
260     return NULL;
261 }
262
263
264 Z_RpnCapabilities *f_rpnCapabilities (ExpHandle *eh, data1_node *c)
265 {
266     Z_RpnCapabilities *res = (Z_RpnCapabilities *)odr_malloc (eh->o, sizeof(*res));
267
268     res->num_operators = 0;
269     res->operators = NULL;
270     res->resultSetAsOperandSupported = eh->false_value;
271     res->restrictionOperandSupported = eh->false_value;
272     res->proximity = NULL;
273     /* fix */ /* 550 - 560 */
274     return res;
275 }
276
277 Z_QueryTypeDetails **f_queryTypesSupported (ExpHandle *eh, data1_node *c,
278                                             int *num)
279 {
280     data1_node *n;
281     Z_QueryTypeDetails **res;
282     int i;
283
284     *num = 0;
285     for (n = c->child; n; n = n->next)
286     {
287         if (is_numeric_tag(eh, n) != 519)
288             continue;
289         /* fix */ /* 518 and 520 */
290         (*num)++;
291     }
292     if (!*num)
293         return NULL;
294     res = (Z_QueryTypeDetails **)odr_malloc (eh->o, *num * sizeof(*res));
295     i = 0;
296     for (n = c->child; n; n = n->next)
297     {
298         if (is_numeric_tag(eh, n) == 519)
299         {
300             res[i] = (Z_QueryTypeDetails *)odr_malloc (eh->o, sizeof(**res));
301             res[i]->which = Z_QueryTypeDetails_rpn;
302             res[i]->u.rpn = f_rpnCapabilities (eh, n);
303             i++;
304         }
305         else
306             continue;
307         /* fix */ /* 518 and 520 */
308     }
309     return res;
310 }
311
312 static Z_AccessInfo *f_accessInfo(ExpHandle *eh, data1_node *n)
313 {
314     Z_AccessInfo *res = (Z_AccessInfo *)odr_malloc(eh->o, sizeof(*res));
315     data1_node *c;
316
317     res->num_queryTypesSupported = 0;
318     res->queryTypesSupported = 0;
319     res->num_diagnosticsSets = 0;
320     res->diagnosticsSets = 0;
321     res->num_attributeSetIds = 0;
322     res->attributeSetIds = 0;
323     res->num_schemas = 0;
324     res->schemas = 0;
325     res->num_recordSyntaxes = 0;
326     res->recordSyntaxes = 0;
327     res->num_resourceChallenges = 0;
328     res->resourceChallenges = 0;
329     res->restrictedAccess = 0;
330     res->costInfo = 0;
331     res->num_variantSets = 0;
332     res->variantSets = 0;
333     res->num_elementSetNames = 0;
334     res->elementSetNames = 0;
335     res->num_unitSystems = 0;
336     res->unitSystems = 0;
337
338     for (c = n->child; c; c = c->next)
339     {
340         switch (is_numeric_tag (eh, c))
341         {
342         case 501:
343             res->queryTypesSupported =
344                 f_queryTypesSupported (eh, c, &res->num_queryTypesSupported);
345             break;
346         case 503:
347             res->diagnosticsSets =
348                 f_oid_seq(eh, c, &res->num_diagnosticsSets, CLASS_DIAGSET);
349             break;
350         case 505:
351             res->attributeSetIds =
352                 f_oid_seq(eh, c, &res->num_attributeSetIds, CLASS_ATTSET);
353             break;
354         case 507:
355             res->schemas =
356                 f_oid_seq(eh, c, &res->num_schemas, CLASS_SCHEMA);
357             break;
358         case 509:
359             res->recordSyntaxes =
360                 f_oid_seq (eh, c, &res->num_recordSyntaxes, CLASS_RECSYN);
361             break;
362         case 511:
363             res->resourceChallenges =
364                 f_oid_seq (eh, c, &res->num_resourceChallenges, CLASS_RESFORM);
365             break;
366         case 513: res->restrictedAccess = NULL; break; /* fix */
367         case 514: res->costInfo = NULL; break; /* fix */
368         case 515:
369             res->variantSets =
370                 f_oid_seq (eh, c, &res->num_variantSets, CLASS_VARSET);
371             break;
372         case 516:
373             res->elementSetNames =
374                 f_string_seq (eh, c, &res->num_elementSetNames);
375             break;
376         case 517:
377             res->unitSystems = f_string_seq (eh, c, &res->num_unitSystems);
378             break;
379         }
380     }
381     return res;
382 }
383
384 static int *f_recordCount(ExpHandle *eh, data1_node *c, int *which)
385 {
386     int *r= (int *)odr_malloc(eh->o, sizeof(*r));
387     int *wp = which;
388     char intbuf[64];
389
390     c = c->child;
391     if (!is_numeric_tag (eh, c))
392         return 0;
393     if (c->u.tag.element->tag->value.numeric == 210)
394         *wp = Z_Exp_RecordCount_actualNumber;
395     else if (c->u.tag.element->tag->value.numeric == 211)
396         *wp = Z_Exp_RecordCount_approxNumber;
397     else
398         return 0;
399     if (!c->child || c->child->which != DATA1N_data)
400         return 0;
401     sprintf(intbuf, "%.*s", 63, c->child->u.data.data);
402     *r = atoi(intbuf);
403     return r;
404 }
405
406 static Z_ContactInfo *f_contactInfo(ExpHandle *eh, data1_node *n)
407 {
408     /* fix */
409     return 0;
410 }
411
412 static Z_DatabaseList *f_databaseList(ExpHandle *eh, data1_node *n)
413 {
414     data1_node *c;
415     Z_DatabaseList *res;
416     int i = 0;
417     
418     for (c = n->child; c; c = c->next)
419     {
420         if (!is_numeric_tag (eh, c) != 102) 
421             continue;
422         ++i;
423     }
424     if (!i)
425         return NULL;
426
427     res = (Z_DatabaseList *)odr_malloc (eh->o, sizeof(*res));
428     
429     res->num_databases = i;
430     res->databases = (char **)odr_malloc (eh->o, sizeof(*res->databases) * i);
431     i = 0;
432     for (c = n->child; c; c = c->next)
433     {
434         if (!is_numeric_tag (eh, c) != 102)
435             continue;
436         res->databases[i++] = f_string (eh, c);
437     }
438     return res;
439 }
440
441 static Z_TargetInfo *f_targetInfo(ExpHandle *eh, data1_node *n)
442 {
443     Z_TargetInfo *res = (Z_TargetInfo *)odr_malloc(eh->o, sizeof(*res));
444     data1_node *c;
445
446     res->commonInfo = 0;
447     res->name = 0;
448     res->recentNews = 0;
449     res->icon = 0;
450     res->namedResultSets = 0;
451     res->multipleDbSearch = 0;
452     res->maxResultSets = 0;
453     res->maxResultSize = 0;
454     res->maxTerms = 0;
455     res->timeoutInterval = 0;
456     res->welcomeMessage = 0;
457     res->contactInfo = 0;
458     res->description = 0;
459     res->num_nicknames = 0;
460     res->nicknames = 0;
461     res->usageRest = 0;
462     res->paymentAddr = 0;
463     res->hours = 0;
464     res->num_dbCombinations = 0;
465     res->dbCombinations = 0;
466     res->num_addresses = 0;
467     res->addresses = 0;
468     res->commonAccessInfo = 0;
469     
470     for (c = n->child; c; c = c->next)
471     {
472         int i = 0;
473
474         if (!is_numeric_tag (eh, c))
475             continue;
476         switch (c->u.tag.element->tag->value.numeric)
477         {
478         case 600: res->commonInfo = f_commonInfo(eh, c); break;
479         case 102: res->name = f_string(eh, c); break;
480         case 103: res->recentNews = f_humstring(eh, c); break;
481         case 104: res->icon = NULL; break; /* fix */
482         case 105: res->namedResultSets = f_bool(eh, c); break;
483         case 106: res->multipleDbSearch = f_bool(eh, c); break;
484         case 107: res->maxResultSets = f_integer(eh, c); break;
485         case 108: res->maxResultSize = f_integer(eh, c); break;
486         case 109: res->maxTerms = f_integer(eh, c); break;
487         case 110: res->timeoutInterval = f_intunit(eh, c); break;
488         case 111: res->welcomeMessage = f_humstring(eh, c); break;
489         case 112: res->contactInfo = f_contactInfo(eh, c); break;
490         case 113: res->description = f_humstring(eh, c); break;
491         case 114: 
492             res->num_nicknames = 0;
493             for (n = c->child; n; n = n->next)
494             {
495                 if (is_numeric_tag(eh, n) != 102)
496                     continue;
497                 (res->num_nicknames)++;
498             }
499             if (res->num_nicknames)
500                 res->nicknames =
501                     (char **)odr_malloc (eh->o, res->num_nicknames 
502                                 * sizeof(*res->nicknames));
503             for (n = c->child; n; n = n->next)
504             {
505                 if (is_numeric_tag(eh, n) != 102)
506                     continue;
507                 res->nicknames[i++] = f_string (eh, n);
508             }
509             break;
510         case 115: res->usageRest = f_humstring(eh, c); break;
511         case 116: res->paymentAddr = f_humstring(eh, c); break;
512         case 117: res->hours = f_humstring(eh, c); break;
513         case 118:
514             res->num_dbCombinations = 0;
515             for (n = c->child; n; n = n->next)
516             {
517                 if (!is_numeric_tag(eh, n) != 605)
518                     continue;
519                 (res->num_dbCombinations)++;
520             }
521             if (res->num_dbCombinations)
522                 res->dbCombinations =
523                     (Z_DatabaseList **)odr_malloc (eh->o, res->num_dbCombinations
524                                 * sizeof(*res->dbCombinations));
525             for (n = c->child; n; n = n->next)
526             {
527                 if (!is_numeric_tag(eh, n) != 605)
528                     continue;
529                 res->dbCombinations[i++] = f_databaseList (eh, n);
530             }
531             break;
532         case 119: res->addresses = 0; break; /* fix */
533         case 500: res->commonAccessInfo = f_accessInfo(eh, c); break;
534         }
535     }
536     if (!res->namedResultSets)
537         res->namedResultSets = eh->false_value;
538     if (!res->multipleDbSearch)
539         res->multipleDbSearch = eh->false_value;
540     return res;
541 }
542
543 static Z_DatabaseInfo *f_databaseInfo(ExpHandle *eh, data1_node *n)
544 {
545     Z_DatabaseInfo *res = (Z_DatabaseInfo *)odr_malloc(eh->o, sizeof(*res));
546     data1_node *c;
547
548     res->commonInfo = 0;
549     res->name = 0;
550     res->explainDatabase = 0;
551     res->num_nicknames = 0;
552     res->nicknames = 0;
553     res->icon = 0;
554     res->userFee = 0;
555     res->available = 0;
556     res->titleString = 0;
557     res->num_keywords = 0;
558     res->keywords = 0;
559     res->description = 0;
560     res->associatedDbs = 0;
561     res->subDbs = 0;
562     res->disclaimers = 0;
563     res->news = 0;
564     res->recordCount = 0;
565     res->defaultOrder = 0;
566     res->avRecordSize = 0;
567     res->maxRecordSize = 0;
568     res->hours = 0;
569     res->bestTime = 0;
570     res->lastUpdate = 0;
571     res->updateInterval = 0;
572     res->coverage = 0;
573     res->proprietary = 0;
574     res->copyrightText = 0;
575     res->copyrightNotice = 0;
576     res->producerContactInfo = 0;
577     res->supplierContactInfo = 0;
578     res->submissionContactInfo = 0;
579     res->accessInfo = 0;
580     
581     for (c = n->child; c; c = c->next)
582     {
583         int i = 0;
584
585         switch (is_numeric_tag (eh, c))
586         {
587         case 600: res->commonInfo = f_commonInfo(eh, c); break;
588         case 102: res->name = f_string(eh, c); break;
589         case 226: res->explainDatabase = odr_nullval(); break;
590         case 114:
591             res->num_nicknames = 0;
592             for (n = c->child; n; n = n->next)
593             {
594                 if (!is_numeric_tag(eh, n) ||
595                     n->u.tag.element->tag->value.numeric != 102)
596                     continue;
597                 (res->num_nicknames)++;
598             }
599             if (res->num_nicknames)
600                 res->nicknames =
601                     (char **)odr_malloc (eh->o, res->num_nicknames 
602                                 * sizeof(*res->nicknames));
603             for (n = c->child; n; n = n->next)
604             {
605                 if (!is_numeric_tag(eh, n) ||
606                     n->u.tag.element->tag->value.numeric != 102)
607                     continue;
608                 res->nicknames[i++] = f_string (eh, n);
609             }
610             break;
611         case 104: res->icon = 0; break;      /* fix */
612         case 201: res->userFee = f_bool(eh, c); break;
613         case 202: res->available = f_bool(eh, c); break;
614         case 203: res->titleString = f_humstring(eh, c); break;
615         case 227:
616             res->num_keywords = 0;
617             for (n = c->child; n; n = n->next)
618             {
619                 if (!is_numeric_tag(eh, n) != 1000)
620                     continue;
621                 (res->num_keywords)++;
622             }
623             if (res->num_keywords)
624                 res->keywords =
625                     (Z_HumanString **)odr_malloc (eh->o, res->num_keywords 
626                                 * sizeof(*res->keywords));
627             for (n = c->child; n; n = n->next)
628             {
629                 if (!is_numeric_tag(eh, n) != 1000)
630                     continue;
631                 res->keywords[i++] = f_humstring (eh, n);
632             }
633             break;
634         case 113: res->description = f_humstring(eh, c); break;
635         case 205:
636             res->associatedDbs = f_databaseList (eh, c);
637             break;
638         case 206:
639             res->subDbs = f_databaseList (eh, c);
640             break;
641         case 207: res->disclaimers = f_humstring(eh, c); break;
642         case 103: res->news = f_humstring(eh, c); break;
643         case 209: res->recordCount =
644                       f_recordCount(eh, c, &res->recordCount_which); break;
645         case 212: res->defaultOrder = f_humstring(eh, c); break;
646         case 213: res->avRecordSize = f_integer(eh, c); break;
647         case 214: res->maxRecordSize = f_integer(eh, c); break;
648         case 215: res->hours = f_humstring(eh, c); break;
649         case 216: res->bestTime = f_humstring(eh, c); break;
650         case 217: res->lastUpdate = f_string(eh, c); break;
651         case 218: res->updateInterval = f_intunit(eh, c); break;
652         case 219: res->coverage = f_humstring(eh, c); break;
653         case 220: res->proprietary = f_bool(eh, c); break;
654         case 221: res->copyrightText = f_humstring(eh, c); break;
655         case 222: res->copyrightNotice = f_humstring(eh, c); break;
656         case 223: res->producerContactInfo = f_contactInfo(eh, c); break;
657         case 224: res->supplierContactInfo = f_contactInfo(eh, c); break;
658         case 225: res->submissionContactInfo = f_contactInfo(eh, c); break;
659         case 500: res->accessInfo = f_accessInfo(eh, c); break;
660         }
661     }
662     if (!res->userFee)
663         res->userFee = eh->false_value;
664     if (!res->available)
665         res->available = eh->true_value;
666     return res;
667 }
668
669 Z_ExplainRecord *data1_nodetoexplain (data1_handle dh, data1_node *n,
670                                       int select, ODR o)
671 {
672     ExpHandle eh;
673     Z_ExplainRecord *res = (Z_ExplainRecord *)odr_malloc(o, sizeof(*res));
674
675     eh.dh = dh;
676     eh.select = select;
677     eh.o = o;
678     eh.false_value = (int *)odr_malloc(eh.o, sizeof(eh.false_value));
679     *eh.false_value = 0;
680     eh.true_value = (int *)odr_malloc(eh.o, sizeof(eh.true_value));
681     *eh.true_value = 1;
682
683     assert(n->which == DATA1N_root);
684     if (strcmp(n->u.root.type, "explain"))
685     {
686         logf(LOG_WARN, "Attempt to convert a non-Explain record");
687         return 0;
688     }
689     for (n = n->child; n; n = n->next)
690     {
691         switch (is_numeric_tag (&eh, n))
692         {
693         case 1:
694             res->which = Z_Explain_targetInfo;
695             if (!(res->u.targetInfo = f_targetInfo(&eh, n)))
696                 return 0;
697             return res;
698         case 2:
699             res->which = Z_Explain_databaseInfo;
700             if (!(res->u.databaseInfo = f_databaseInfo(&eh, n)))
701                 return 0;
702             return res;
703         }
704     }
705     logf(LOG_WARN, "No category in Explain record");
706     return 0;
707 }