Reformat: delete trailing whitespace
[idzebra-moved-to-github.git] / index / mod_alvis.c
1 /* This file is part of the Zebra server.
2    Copyright (C) 1994-2011 Index Data
3
4 Zebra is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18 */
19
20 #if HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 #include <stdio.h>
24 #include <assert.h>
25 #include <ctype.h>
26
27 #include <yaz/diagbib1.h>
28 #include <yaz/tpath.h>
29 #include <yaz/oid_db.h>
30
31 #include <libxml/xmlversion.h>
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include <libxml/xmlIO.h>
35 #include <libxml/xmlreader.h>
36 #include <libxslt/transform.h>
37 #include <libxslt/xsltutils.h>
38
39 #if YAZ_HAVE_EXSLT
40 #include <libexslt/exslt.h>
41 #endif
42
43 #include <idzebra/util.h>
44 #include <idzebra/recctrl.h>
45
46 struct filter_schema {
47     const char *name;
48     const char *identifier;
49     const char *stylesheet;
50     struct filter_schema *next;
51     const char *default_schema;
52     /* char default_schema; */
53     xsltStylesheetPtr stylesheet_xsp;
54 };
55
56 struct filter_info {
57     xmlDocPtr doc;
58     char *fname;
59     char *full_name;
60     const char *profile_path;
61     int split_level;
62     const char *split_path;
63     ODR odr;
64     struct filter_schema *schemas;
65     xmlTextReaderPtr reader;
66 };
67
68 #define ZEBRA_SCHEMA_XSLT_NS "http://indexdata.dk/zebra/xslt/1"
69
70 #define XML_STRCMP(a,b)   strcmp((char*)a, b)
71 #define XML_STRLEN(a) strlen((char*)a)
72
73 static const char *zebra_xslt_ns = ZEBRA_SCHEMA_XSLT_NS;
74
75 static void set_param_str(const char **params, const char *name,
76                           const char *value, ODR odr)
77 {
78     char *quoted = odr_malloc(odr, 3 + strlen(value));
79     sprintf(quoted, "'%s'", value);
80     while (*params)
81         params++;
82     params[0] = name;
83     params[1] = quoted;
84     params[2] = 0;
85 }
86
87 static void set_param_int(const char **params, const char *name,
88                           zint value, ODR odr)
89 {
90     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
91     while (*params)
92         params++;
93     sprintf(quoted, "'" ZINT_FORMAT "'", value);
94     params[0] = name;
95     params[1] = quoted;
96     params[2] = 0;
97 }
98
99 #define ENABLE_INPUT_CALLBACK 0
100
101 #if ENABLE_INPUT_CALLBACK
102 static int zebra_xmlInputMatchCallback (char const *filename)
103 {
104     yaz_log(YLOG_LOG, "match %s", filename);
105     return 0;
106 }
107
108 static void * zebra_xmlInputOpenCallback (char const *filename)
109 {
110     return 0;
111 }
112
113 static int zebra_xmlInputReadCallback (void * context, char * buffer, int len)
114 {
115     return 0;
116 }
117
118 static int zebra_xmlInputCloseCallback (void * context)
119 {
120     return 0;
121 }
122 #endif
123
124 static void *filter_init(Res res, RecType recType)
125 {
126     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
127     tinfo->reader = 0;
128     tinfo->fname = 0;
129     tinfo->full_name = 0;
130     tinfo->profile_path = 0;
131     tinfo->split_level = 0;
132     tinfo->split_path = 0;
133     tinfo->odr = odr_createmem(ODR_ENCODE);
134     tinfo->doc = 0;
135     tinfo->schemas = 0;
136
137 #if YAZ_HAVE_EXSLT
138     exsltRegisterAll();
139 #endif
140
141 #if ENABLE_INPUT_CALLBACK
142     xmlRegisterDefaultInputCallbacks();
143     xmlRegisterInputCallbacks(zebra_xmlInputMatchCallback,
144                               zebra_xmlInputOpenCallback,
145                               zebra_xmlInputReadCallback,
146                               zebra_xmlInputCloseCallback);
147 #endif
148     return tinfo;
149 }
150
151 static int attr_content(struct _xmlAttr *attr, const char *name,
152                         const char **dst_content)
153 {
154     if (!XML_STRCMP(attr->name, name) && attr->children
155         && attr->children->type == XML_TEXT_NODE)
156     {
157         *dst_content = (const char *)(attr->children->content);
158         return 1;
159     }
160     return 0;
161 }
162
163 static void destroy_schemas(struct filter_info *tinfo)
164 {
165     struct filter_schema *schema = tinfo->schemas;
166     while (schema)
167     {
168         struct filter_schema *schema_next = schema->next;
169         if (schema->stylesheet_xsp)
170             xsltFreeStylesheet(schema->stylesheet_xsp);
171         xfree(schema);
172         schema = schema_next;
173     }
174     tinfo->schemas = 0;
175     xfree(tinfo->fname);
176     if (tinfo->doc)
177         xmlFreeDoc(tinfo->doc);
178     tinfo->doc = 0;
179 }
180
181 static ZEBRA_RES create_schemas(struct filter_info *tinfo, const char *fname)
182 {
183     char tmp_full_name[1024];
184     xmlNodePtr ptr;
185     tinfo->fname = xstrdup(fname);
186
187     if (yaz_filepath_resolve(tinfo->fname, tinfo->profile_path,
188                              NULL, tmp_full_name))
189         tinfo->full_name = xstrdup(tmp_full_name);
190     else
191         tinfo->full_name = xstrdup(tinfo->fname);
192
193     yaz_log(YLOG_LOG, "alvis filter: loading config file %s", tinfo->full_name);
194
195     tinfo->doc = xmlParseFile(tinfo->full_name);
196
197     if (!tinfo->doc)
198     {
199         yaz_log(YLOG_WARN, "alvis filter: could not parse config file %s",
200                 tinfo->full_name);
201
202         return ZEBRA_FAIL;
203     }
204
205     ptr = xmlDocGetRootElement(tinfo->doc);
206     if (!ptr || ptr->type != XML_ELEMENT_NODE
207         || XML_STRCMP(ptr->name, "schemaInfo"))
208     {
209         yaz_log(YLOG_WARN,
210                 "alvis filter:  config file %s :"
211                 " expected root element <schemaInfo>",
212                 tinfo->full_name);
213         return ZEBRA_FAIL;
214     }
215
216     for (ptr = ptr->children; ptr; ptr = ptr->next)
217     {
218         if (ptr->type != XML_ELEMENT_NODE)
219             continue;
220         if (!XML_STRCMP(ptr->name, "schema"))
221         {
222             struct _xmlAttr *attr;
223             struct filter_schema *schema = xmalloc(sizeof(*schema));
224             schema->name = 0;
225             schema->identifier = 0;
226             schema->stylesheet = 0;
227             schema->default_schema = 0;
228             schema->next = tinfo->schemas;
229             schema->stylesheet_xsp = 0;
230             tinfo->schemas = schema;
231             for (attr = ptr->properties; attr; attr = attr->next)
232             {
233                 attr_content(attr, "identifier", &schema->identifier);
234                 attr_content(attr, "name", &schema->name);
235                 attr_content(attr, "stylesheet", &schema->stylesheet);
236                 attr_content(attr, "default", &schema->default_schema);
237             }
238             /*yaz_log(YLOG_LOG, "XSLT add %s %s %s",
239               schema->name, schema->identifier, schema->stylesheet); */
240
241             /* find requested schema */
242
243             if (schema->stylesheet)
244             {
245                 char tmp_xslt_full_name[1024];
246                 if (!yaz_filepath_resolve(schema->stylesheet, tinfo->profile_path,
247                                           NULL, tmp_xslt_full_name))
248                 {
249                     yaz_log(YLOG_WARN,
250                             "alvis filter: stylesheet %s not found in path %s",
251                             schema->stylesheet, tinfo->profile_path);
252                     return ZEBRA_FAIL;
253                 }
254                 schema->stylesheet_xsp
255                     = xsltParseStylesheetFile((const xmlChar*) tmp_xslt_full_name);
256                 if (!schema->stylesheet_xsp)
257                 {
258                     yaz_log(YLOG_WARN,
259                             "alvis filter: could not parse xslt stylesheet %s",
260                             tmp_xslt_full_name);
261                     return ZEBRA_FAIL;
262                 }
263             }
264         }
265         else if (!XML_STRCMP(ptr->name, "split"))
266         {
267             struct _xmlAttr *attr;
268             for (attr = ptr->properties; attr; attr = attr->next)
269             {
270                 const char *split_level_str = 0;
271                 attr_content(attr, "level", &split_level_str);
272                 tinfo->split_level =
273                     split_level_str ? atoi(split_level_str) : 0;
274             }
275         }
276         else
277         {
278             yaz_log(YLOG_WARN, "Bad element %s in %s", ptr->name, fname);
279             return ZEBRA_FAIL;
280         }
281     }
282     return ZEBRA_OK;
283 }
284
285 static struct filter_schema *lookup_schema(struct filter_info *tinfo,
286                                            const char *est)
287 {
288     struct filter_schema *schema;
289
290     for (schema = tinfo->schemas; schema; schema = schema->next)
291     {
292         /* find requested schema */
293         if (est)
294         {
295             if (schema->identifier && !strcmp(schema->identifier, est))
296                 return schema;
297
298             if (schema->name && !strcmp(schema->name, est))
299                 return schema;
300         }
301         /* or return default schema if defined */
302         else if (schema->default_schema)
303             return schema;
304     }
305
306     /* return first schema if no default schema defined */
307     if (tinfo->schemas)
308         return tinfo->schemas;
309
310     return 0;
311 }
312
313 static ZEBRA_RES filter_config(void *clientData, Res res, const char *args)
314 {
315     struct filter_info *tinfo = clientData;
316     if (!args || !*args)
317     {
318         yaz_log(YLOG_WARN, "alvis filter: need config file");
319         return ZEBRA_FAIL;
320     }
321
322     if (tinfo->fname && !strcmp(args, tinfo->fname))
323         return ZEBRA_OK;
324
325     tinfo->profile_path = res_get(res, "profilePath");
326     yaz_log(YLOG_LOG, "alvis filter: profilePath %s", tinfo->profile_path);
327
328     destroy_schemas(tinfo);
329     return create_schemas(tinfo, args);
330 }
331
332 static void filter_destroy(void *clientData)
333 {
334     struct filter_info *tinfo = clientData;
335     destroy_schemas(tinfo);
336     xfree(tinfo->full_name);
337     if (tinfo->reader)
338         xmlFreeTextReader(tinfo->reader);
339     odr_destroy(tinfo->odr);
340     xfree(tinfo);
341 }
342
343 static int ioread_ex(void *context, char *buffer, int len)
344 {
345     struct recExtractCtrl *p = context;
346     return p->stream->readf(p->stream, buffer, len);
347 }
348
349 static int ioclose_ex(void *context)
350 {
351     return 0;
352 }
353
354 static void index_cdata(struct filter_info *tinfo, struct recExtractCtrl *ctrl,
355                         xmlNodePtr ptr, RecWord *recWord)
356 {
357     for(; ptr; ptr = ptr->next)
358     {
359         index_cdata(tinfo, ctrl, ptr->children, recWord);
360         if (ptr->type != XML_TEXT_NODE)
361             continue;
362         recWord->term_buf = (const char *)ptr->content;
363         recWord->term_len = XML_STRLEN(ptr->content);
364         (*ctrl->tokenAdd)(recWord);
365     }
366 }
367
368 static void index_node(struct filter_info *tinfo,  struct recExtractCtrl *ctrl,
369                        xmlNodePtr ptr, RecWord *recWord)
370 {
371     for(; ptr; ptr = ptr->next)
372     {
373         index_node(tinfo, ctrl, ptr->children, recWord);
374         if (ptr->type != XML_ELEMENT_NODE || !ptr->ns ||
375             XML_STRCMP(ptr->ns->href, zebra_xslt_ns))
376             continue;
377         if (!XML_STRCMP(ptr->name, "index"))
378         {
379             const char *name_str = 0;
380             const char *type_str = 0;
381             const char *xpath_str = 0;
382             struct _xmlAttr *attr;
383             for (attr = ptr->properties; attr; attr = attr->next)
384             {
385                 attr_content(attr, "name", &name_str);
386                 attr_content(attr, "xpath", &xpath_str);
387                 attr_content(attr, "type", &type_str);
388             }
389             if (name_str)
390             {
391                 const char *prev_type = recWord->index_type; /* save default type */
392
393                 if (type_str && *type_str)
394                     recWord->index_type = (const char *) type_str; /* type was given */
395                 recWord->index_name = name_str;
396                 index_cdata(tinfo, ctrl, ptr->children, recWord);
397
398                 recWord->index_type = prev_type;     /* restore it again */
399             }
400         }
401     }
402 }
403
404 static void index_record(struct filter_info *tinfo,struct recExtractCtrl *ctrl,
405                          xmlNodePtr ptr, RecWord *recWord)
406 {
407     const char *type_str = "update";
408
409     if (ptr && ptr->type == XML_ELEMENT_NODE && ptr->ns &&
410         !XML_STRCMP(ptr->ns->href, zebra_xslt_ns)
411         && !XML_STRCMP(ptr->name, "record"))
412     {
413         const char *id_str = 0;
414         const char *rank_str = 0;
415         struct _xmlAttr *attr;
416         for (attr = ptr->properties; attr; attr = attr->next)
417         {
418             attr_content(attr, "type", &type_str);
419             attr_content(attr, "id", &id_str);
420             attr_content(attr, "rank", &rank_str);
421         }
422         if (id_str)
423             sscanf(id_str, "%255s", ctrl->match_criteria);
424
425         if (rank_str)
426             ctrl->staticrank = atozint(rank_str);
427         ptr = ptr->children;
428     }
429
430     if (!strcmp("update", type_str))
431         index_node(tinfo, ctrl, ptr, recWord);
432     else if (!strcmp("delete", type_str))
433          yaz_log(YLOG_WARN, "alvis filter delete: to be implemented");
434     else
435          yaz_log(YLOG_WARN, "alvis filter: unknown record type '%s'",
436                  type_str);
437 }
438
439 static int extract_doc(struct filter_info *tinfo, struct recExtractCtrl *p,
440                        xmlDocPtr doc)
441 {
442     RecWord recWord;
443     const char *params[10];
444     xmlChar *buf_out;
445     int len_out;
446
447     struct filter_schema *schema = lookup_schema(tinfo, zebra_xslt_ns);
448
449     params[0] = 0;
450     set_param_str(params, "schema", zebra_xslt_ns, tinfo->odr);
451
452     (*p->init)(p, &recWord);
453
454     if (schema && schema->stylesheet_xsp)
455     {
456         xmlNodePtr root_ptr;
457         xmlDocPtr resDoc =
458             xsltApplyStylesheet(schema->stylesheet_xsp,
459                                 doc, params);
460         if (p->flagShowRecords)
461         {
462             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
463             fwrite(buf_out, len_out, 1, stdout);
464             xmlFree(buf_out);
465         }
466         root_ptr = xmlDocGetRootElement(resDoc);
467         if (root_ptr)
468             index_record(tinfo, p, root_ptr, &recWord);
469         else
470         {
471             yaz_log(YLOG_WARN, "No root for index XML record."
472                     " split_level=%d stylesheet=%s",
473                     tinfo->split_level, schema->stylesheet);
474         }
475         xmlFreeDoc(resDoc);
476     }
477     xmlDocDumpMemory(doc, &buf_out, &len_out);
478     if (p->flagShowRecords)
479         fwrite(buf_out, len_out, 1, stdout);
480     if (p->setStoreData)
481         (*p->setStoreData)(p, buf_out, len_out);
482     xmlFree(buf_out);
483
484     xmlFreeDoc(doc);
485     return RECCTRL_EXTRACT_OK;
486 }
487
488 static int extract_split(struct filter_info *tinfo, struct recExtractCtrl *p)
489 {
490     int ret;
491
492     if (p->first_record)
493     {
494         if (tinfo->reader)
495             xmlFreeTextReader(tinfo->reader);
496         tinfo->reader = xmlReaderForIO(ioread_ex, ioclose_ex,
497                                        p /* I/O handler */,
498                                        0 /* URL */,
499                                        0 /* encoding */,
500                                        XML_PARSE_XINCLUDE
501                                        | XML_PARSE_NOENT
502                                        | XML_PARSE_NONET);
503     }
504     if (!tinfo->reader)
505         return RECCTRL_EXTRACT_ERROR_GENERIC;
506
507     ret = xmlTextReaderRead(tinfo->reader);
508     while (ret == 1)
509     {
510         int type = xmlTextReaderNodeType(tinfo->reader);
511         int depth = xmlTextReaderDepth(tinfo->reader);
512         if (type == XML_READER_TYPE_ELEMENT && tinfo->split_level == depth)
513         {
514             xmlNodePtr ptr = xmlTextReaderExpand(tinfo->reader);
515             if (ptr)
516             {
517                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
518                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
519
520                 xmlDocSetRootElement(doc, ptr2);
521
522                 return extract_doc(tinfo, p, doc);
523             }
524             else
525             {
526                 xmlFreeTextReader(tinfo->reader);
527                 tinfo->reader = 0;
528                 return RECCTRL_EXTRACT_ERROR_GENERIC;
529             }
530         }
531         ret = xmlTextReaderRead(tinfo->reader);
532     }
533     xmlFreeTextReader(tinfo->reader);
534     tinfo->reader = 0;
535     return RECCTRL_EXTRACT_EOF;
536 }
537
538 static int extract_full(struct filter_info *tinfo, struct recExtractCtrl *p)
539 {
540     if (p->first_record) /* only one record per stream */
541     {
542        xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
543                                  0 /* URL */,
544                                  0 /* encoding */,
545                                  XML_PARSE_XINCLUDE
546                                  | XML_PARSE_NOENT
547                                  | XML_PARSE_NONET);
548        if (!doc)
549            return RECCTRL_EXTRACT_ERROR_GENERIC;
550        /* else {
551            xmlNodePtr root = xmlDocGetRootElement(doc);
552             if (!root)
553                 return RECCTRL_EXTRACT_ERROR_GENERIC;
554                 } */
555
556        return extract_doc(tinfo, p, doc);
557     }
558     else
559        return RECCTRL_EXTRACT_EOF;
560 }
561
562 static int filter_extract(void *clientData, struct recExtractCtrl *p)
563 {
564     struct filter_info *tinfo = clientData;
565
566     odr_reset(tinfo->odr);
567     if (tinfo->split_level == 0 || p->setStoreData == 0)
568         return extract_full(tinfo, p);
569     else
570         return extract_split(tinfo, p);
571 }
572
573 static int ioread_ret(void *context, char *buffer, int len)
574 {
575     struct recRetrieveCtrl *p = context;
576     return p->stream->readf(p->stream, buffer, len);
577 }
578
579 static int ioclose_ret(void *context)
580 {
581     return 0;
582 }
583
584 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
585 {
586     /* const char *esn = zebra_xslt_ns; */
587     const char *esn = 0;
588     const char *params[32];
589     struct filter_info *tinfo = clientData;
590     xmlDocPtr resDoc;
591     xmlDocPtr doc;
592     struct filter_schema *schema;
593
594     if (p->comp)
595     {
596         if (p->comp->which == Z_RecordComp_simple
597             && p->comp->u.simple->which == Z_ElementSetNames_generic)
598         {
599             esn = p->comp->u.simple->u.generic;
600         }
601         else if (p->comp->which == Z_RecordComp_complex
602                  && p->comp->u.complex->generic->elementSpec
603                  && p->comp->u.complex->generic->elementSpec->which ==
604                  Z_ElementSpec_elementSetName)
605         {
606             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
607         }
608     }
609     schema = lookup_schema(tinfo, esn);
610     if (!schema)
611     {
612         p->diagnostic =
613             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
614         return 0;
615     }
616
617     params[0] = 0;
618     set_param_int(params, "id", p->localno, p->odr);
619     if (p->fname)
620         set_param_str(params, "filename", p->fname, p->odr);
621     if (p->staticrank >= 0)
622         set_param_int(params, "rank", p->staticrank, p->odr);
623
624     if (esn)
625         set_param_str(params, "schema", esn, p->odr);
626     else
627         if (schema->name)
628             set_param_str(params, "schema", schema->name, p->odr);
629         else if (schema->identifier)
630             set_param_str(params, "schema", schema->identifier, p->odr);
631         else
632             set_param_str(params, "schema", "", p->odr);
633
634     if (p->score >= 0)
635         set_param_int(params, "score", p->score, p->odr);
636     set_param_int(params, "size", p->recordSize, p->odr);
637
638     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
639                     0 /* URL */,
640                     0 /* encoding */,
641                     XML_PARSE_XINCLUDE | XML_PARSE_NOENT | XML_PARSE_NONET);
642     if (!doc)
643     {
644         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
645         return 0;
646     }
647
648     if (!schema->stylesheet_xsp)
649         resDoc = doc;
650     else
651     {
652         resDoc = xsltApplyStylesheet(schema->stylesheet_xsp,
653                                      doc, params);
654         xmlFreeDoc(doc);
655     }
656     if (!resDoc)
657     {
658         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
659     }
660     else if (!p->input_format
661              || !oid_oidcmp(p->input_format, yaz_oid_recsyn_xml))
662     {
663         xmlChar *buf_out;
664         int len_out;
665
666         if (schema->stylesheet_xsp)
667             xsltSaveResultToString(&buf_out, &len_out, resDoc,
668                                    schema->stylesheet_xsp);
669         else
670             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
671
672         p->output_format = yaz_oid_recsyn_xml;
673         p->rec_len = len_out;
674         p->rec_buf = odr_malloc(p->odr, p->rec_len);
675         memcpy(p->rec_buf, buf_out, p->rec_len);
676         xmlFree(buf_out);
677     }
678     else if (!oid_oidcmp(p->output_format, yaz_oid_recsyn_sutrs))
679     {
680         xmlChar *buf_out;
681         int len_out;
682
683         if (schema->stylesheet_xsp)
684             xsltSaveResultToString(&buf_out, &len_out, resDoc,
685                                    schema->stylesheet_xsp);
686         else
687             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
688
689         p->output_format = yaz_oid_recsyn_sutrs;
690         p->rec_len = len_out;
691         p->rec_buf = odr_malloc(p->odr, p->rec_len);
692         memcpy(p->rec_buf, buf_out, p->rec_len);
693
694         xmlFree(buf_out);
695     }
696     else
697     {
698         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
699     }
700     xmlFreeDoc(resDoc);
701     return 0;
702 }
703
704 static struct recType filter_type = {
705     0,
706     "alvis",
707     filter_init,
708     filter_config,
709     filter_destroy,
710     filter_extract,
711     filter_retrieve
712 };
713
714 RecType
715 #if IDZEBRA_STATIC_ALVIS
716 idzebra_filter_alvis
717 #else
718 idzebra_filter
719 #endif
720
721 [] = {
722     &filter_type,
723     0,
724 };
725 /*
726  * Local variables:
727  * c-basic-offset: 4
728  * c-file-style: "Stroustrup"
729  * indent-tabs-mode: nil
730  * End:
731  * vim: shiftwidth=4 tabstop=8 expandtab
732  */
733