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