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