Fixes for perform_convert: use xmlParseMemory instead of xmlParseMemory
[idzebra-moved-to-github.git] / index / mod_dom.c
1 /* $Id: mod_dom.c,v 1.26 2007-03-03 21:39:10 adam Exp $
2    Copyright (C) 1995-2007
3    Index Data ApS
4
5    This file is part of the Zebra server.
6
7    Zebra is free software; you can redistribute it and/or modify it under
8    the terms of the GNU General Public License as published by the Free
9    Software Foundation; either version 2, or (at your option) any later
10    version.
11
12    Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13    WARRANTY; without even the implied warranty of MERCHANTABILITY or
14    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15    for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 */
22
23 #include <stdio.h>
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdarg.h>
27
28 #include <yaz/diagbib1.h>
29 #include <yaz/tpath.h>
30 #include <yaz/snprintf.h>
31
32 #include <libxml/xmlversion.h>
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <libxml/xmlIO.h>
36 #include <libxml/xmlreader.h>
37 #include <libxslt/transform.h>
38 #include <libxslt/xsltutils.h>
39
40 #if YAZ_HAVE_EXSLT
41 #include <libexslt/exslt.h>
42 #endif
43
44 #include <idzebra/util.h>
45 #include <idzebra/recctrl.h>
46
47 /* DOM filter style indexing */
48 #define ZEBRA_DOM_NS "http://indexdata.com/zebra-2.0"
49 static const char *zebra_dom_ns = ZEBRA_DOM_NS;
50
51 /* DOM filter style indexing */
52 #define ZEBRA_PI_NAME "zebra-2.0"
53 static const char *zebra_pi_name = ZEBRA_PI_NAME;
54
55
56
57 struct convert_s {
58     const char *stylesheet;
59     xsltStylesheetPtr stylesheet_xsp;
60     struct convert_s *next;
61 };
62
63 struct filter_extract {
64     const char *name;
65     struct convert_s *convert;
66 };
67
68 struct filter_store {
69     struct convert_s *convert;
70 };
71
72 struct filter_retrieve {
73     const char *name;
74     const char *identifier;
75     struct convert_s *convert;
76     struct filter_retrieve *next;
77 };
78
79 #define DOM_INPUT_XMLREADER 1
80 #define DOM_INPUT_MARC 2
81 struct filter_input {
82     const char *syntax;
83     const char *name;
84     struct convert_s *convert;
85     int type;
86     union {
87         struct {
88             const char *input_charset;
89             yaz_marc_t handle;
90             yaz_iconv_t iconv;
91         } marc;
92         struct {
93             xmlTextReaderPtr reader;
94             int split_level;
95         } xmlreader;
96     } u;
97     struct filter_input *next;
98 };
99   
100 struct filter_info {
101     char *fname;
102     char *full_name;
103     const char *profile_path;
104     ODR odr_record;
105     ODR odr_config;
106     xmlDocPtr doc_config;
107     struct filter_extract *extract;
108     struct filter_retrieve *retrieve_list;
109     struct filter_input *input_list;
110     struct filter_store *store;
111     int record_info_invoked;
112 };
113
114
115
116 #define XML_STRCMP(a,b)   strcmp((char*)a, b)
117 #define XML_STRLEN(a) strlen((char*)a)
118
119
120 #define FOR_EACH_ELEMENT(ptr) for (; ptr; ptr = ptr->next) if (ptr->type == XML_ELEMENT_NODE)
121
122 static void dom_log(int level, struct filter_info *tinfo, xmlNodePtr ptr,
123                     const char *fmt, ...)
124 #ifdef __GNUC__
125     __attribute__ ((format (printf, 4, 5)))
126 #endif
127     ;
128
129 static void dom_log(int level, struct filter_info *tinfo, xmlNodePtr ptr,
130                     const char *fmt, ...)
131 {
132     va_list ap;
133     char buf[4096];
134
135     va_start(ap, fmt);
136     yaz_vsnprintf(buf, sizeof(buf)-1, fmt, ap);
137     if (ptr)
138     {
139         yaz_log(level, "%s:%ld: %s", tinfo->fname ? tinfo->fname : "none", 
140                 xmlGetLineNo(ptr), buf);
141     }
142     else
143     {
144         yaz_log(level, "%s: %s", tinfo->fname ? tinfo->fname : "none", buf);
145     }
146     va_end(ap);
147 }
148
149
150 static void set_param_str(const char **params, const char *name,
151                           const char *value, ODR odr)
152 {
153     char *quoted = odr_malloc(odr, 3 + strlen(value));
154     sprintf(quoted, "'%s'", value);
155     while (*params)
156         params++;
157     params[0] = name;
158     params[1] = quoted;
159     params[2] = 0;
160 }
161
162 static void set_param_int(const char **params, const char *name,
163                           zint value, ODR odr)
164 {
165     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
166     while (*params)
167         params++;
168     sprintf(quoted, "'" ZINT_FORMAT "'", value);
169     params[0] = name;
170     params[1] = quoted;
171     params[2] = 0;
172 }
173
174 static void *filter_init(Res res, RecType recType)
175 {
176     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
177     tinfo->fname = 0;
178     tinfo->full_name = 0;
179     tinfo->profile_path = 0;
180     tinfo->odr_record = odr_createmem(ODR_ENCODE);
181     tinfo->odr_config = odr_createmem(ODR_ENCODE);
182     tinfo->extract = 0;
183     tinfo->retrieve_list = 0;
184     tinfo->input_list = 0;
185     tinfo->store = 0;
186     tinfo->doc_config = 0;
187     tinfo->record_info_invoked = 0;
188
189 #if YAZ_HAVE_EXSLT
190     exsltRegisterAll(); 
191 #endif
192
193     return tinfo;
194 }
195
196 static int attr_content(struct _xmlAttr *attr, const char *name,
197                         const char **dst_content)
198 {
199     if (!XML_STRCMP(attr->name, name) && attr->children 
200         && attr->children->type == XML_TEXT_NODE)
201     {
202         *dst_content = (const char *)(attr->children->content);
203         return 1;
204     }
205     return 0;
206 }
207
208 static void destroy_xsp(struct convert_s *c)
209 {
210     while(c)
211     {
212         if (c->stylesheet_xsp)
213             xsltFreeStylesheet(c->stylesheet_xsp);
214         c = c->next;
215     }
216 }
217
218 static void destroy_dom(struct filter_info *tinfo)
219 {
220     if (tinfo->extract)
221     {
222         destroy_xsp(tinfo->extract->convert);
223         tinfo->extract = 0;
224     }
225     if (tinfo->store)
226     {
227         destroy_xsp(tinfo->store->convert);
228         tinfo->store = 0;
229     }
230     if (tinfo->input_list)
231     {
232         struct filter_input *i_ptr;
233         for (i_ptr = tinfo->input_list; i_ptr; i_ptr = i_ptr->next)
234         {
235             switch(i_ptr->type)
236             {
237             case DOM_INPUT_XMLREADER:
238                 if (i_ptr->u.xmlreader.reader)
239                     xmlFreeTextReader(i_ptr->u.xmlreader.reader);
240                 break;
241             case DOM_INPUT_MARC:
242                 yaz_iconv_close(i_ptr->u.marc.iconv);
243                 yaz_marc_destroy(i_ptr->u.marc.handle);
244                 break;
245             }
246             destroy_xsp(i_ptr->convert);
247         }
248         tinfo->input_list = 0;
249     }
250     if (tinfo->retrieve_list)
251     {
252         struct filter_retrieve *r_ptr;
253         for (r_ptr = tinfo->retrieve_list; r_ptr; r_ptr = r_ptr->next)
254             destroy_xsp(r_ptr->convert);
255         tinfo->retrieve_list = 0;
256     }
257
258     if (tinfo->doc_config)
259     {
260         xmlFreeDoc(tinfo->doc_config);
261         tinfo->doc_config = 0;
262     }
263     odr_reset(tinfo->odr_config);
264 }
265
266 static ZEBRA_RES parse_convert(struct filter_info *tinfo, xmlNodePtr ptr,
267                                struct convert_s **l)
268 {
269     *l = 0;
270     FOR_EACH_ELEMENT(ptr) {
271         if (!XML_STRCMP(ptr->name, "xslt"))
272         {
273             struct _xmlAttr *attr;
274             struct convert_s *p 
275                 = odr_malloc(tinfo->odr_config, sizeof(*p));
276             
277             p->next = 0;
278             p->stylesheet = 0;
279             p->stylesheet_xsp = 0;
280             
281             for (attr = ptr->properties; attr; attr = attr->next)
282                 if (attr_content(attr, "stylesheet", &p->stylesheet))
283                     ;
284                 else
285                 {
286                     dom_log(YLOG_WARN, tinfo, ptr,
287                             "bad attribute @%s", attr->name);
288                 }
289             if (p->stylesheet)
290             {
291                 char tmp_xslt_full_name[1024];
292                 if (!yaz_filepath_resolve(p->stylesheet, 
293                                           tinfo->profile_path,
294                                           NULL, 
295                                           tmp_xslt_full_name))
296                 {
297                     dom_log(YLOG_WARN, tinfo, 0,
298                             "stylesheet %s not found in "
299                             "path %s",
300                             p->stylesheet, 
301                             tinfo->profile_path);
302                     return ZEBRA_FAIL;
303                 }
304                 
305                 p->stylesheet_xsp
306                     = xsltParseStylesheetFile((const xmlChar*) 
307                                               tmp_xslt_full_name);
308                 if (!p->stylesheet_xsp)
309                 {
310                     dom_log(YLOG_WARN, tinfo, 0,
311                             "could not parse xslt stylesheet %s",
312                             tmp_xslt_full_name);
313                     return ZEBRA_FAIL;
314                 }
315                 }
316                 else
317                 {
318                     dom_log(YLOG_WARN, tinfo, ptr,
319                             "missing attribute 'stylesheet' ");
320                     return ZEBRA_FAIL;
321                 }
322                 *l = p;
323                 l = &p->next;
324         }
325         else
326         {
327             dom_log(YLOG_WARN, tinfo, ptr,
328                     "bad element '%s', expected <xslt>", ptr->name);
329             return ZEBRA_FAIL;
330         }
331     }
332     return ZEBRA_OK;
333 }
334
335 static ZEBRA_RES perform_convert(struct filter_info *tinfo, 
336                                  struct recExtractCtrl *extctr,
337                                  struct convert_s *convert,
338                                  const char **params,
339                                  xmlDocPtr *doc,
340                                  xsltStylesheetPtr *last_xsp)
341 {
342     for (; convert; convert = convert->next)
343     {
344         xmlChar *buf_out = 0;
345         int len_out = 0;
346         xmlDocPtr res_doc = xsltApplyStylesheet(convert->stylesheet_xsp,
347                                                 *doc, params);
348         if (last_xsp)
349             *last_xsp = convert->stylesheet_xsp;
350         
351         if (!res_doc)
352             break;
353
354         /* now saving into buffer and re-reading into DOM to avoid annoing
355            XSLT problem with thrown-out indentation text nodes */
356         xsltSaveResultToString(&buf_out, &len_out, res_doc,
357                                convert->stylesheet_xsp); 
358         xmlFreeDoc(res_doc);
359
360         xmlFreeDoc(*doc);
361
362         *doc = xmlParseMemory((const char *) buf_out, len_out);
363
364         /* writing debug info out */
365         if (extctr->flagShowRecords)
366             yaz_log(YLOG_LOG, "%s: XSLT %s\n %.*s", 
367                     tinfo->fname ? tinfo->fname : "(none)", 
368                     convert->stylesheet,
369                     len_out, buf_out);
370         
371         xmlFree(buf_out);
372     }
373     return ZEBRA_OK;
374 }
375
376 static struct filter_input *new_input(struct filter_info *tinfo, int type)
377 {
378     struct filter_input *p;
379     struct filter_input **np = &tinfo->input_list;
380     for (;*np; np = &(*np)->next)
381         ;
382     p = *np = odr_malloc(tinfo->odr_config, sizeof(*p));
383     p->next = 0;
384     p->syntax = 0;
385     p->name = 0;
386     p->convert = 0;
387     p->type = type;
388     return p;
389 }
390
391 static ZEBRA_RES parse_input(struct filter_info *tinfo, xmlNodePtr ptr,
392                              const char *syntax, const char *name)
393 {
394     FOR_EACH_ELEMENT(ptr) {
395         if (!XML_STRCMP(ptr->name, "marc"))
396         {
397             yaz_iconv_t iconv = 0;
398             const char *input_charset = "marc-8";
399             struct _xmlAttr *attr;
400             
401             for (attr = ptr->properties; attr; attr = attr->next)
402             {
403                 if (attr_content(attr, "inputcharset", &input_charset))
404                     ;
405                 else
406                 {
407                     dom_log(YLOG_WARN, tinfo, ptr,
408                             "bad attribute @%s, expected @inputcharset",
409                             attr->name);
410                 }
411             }
412             iconv = yaz_iconv_open("utf-8", input_charset);
413             if (!iconv)
414             {
415                 dom_log(YLOG_WARN, tinfo, ptr, 
416                         "unsupported @charset '%s'", input_charset);
417                 return ZEBRA_FAIL;
418             }
419             else
420             {
421                 struct filter_input *p 
422                     = new_input(tinfo, DOM_INPUT_MARC);
423                 p->u.marc.handle = yaz_marc_create();
424                 p->u.marc.iconv = iconv;
425                 
426                 yaz_marc_iconv(p->u.marc.handle, p->u.marc.iconv);
427                 
428                 ptr = ptr->next;
429                 
430                 parse_convert(tinfo, ptr, &p->convert);
431             }
432             break;
433
434         }
435         else if (!XML_STRCMP(ptr->name, "xmlreader"))
436         {
437             struct filter_input *p 
438                 = new_input(tinfo, DOM_INPUT_XMLREADER);
439             struct _xmlAttr *attr;
440             const char *level_str = 0;
441
442             p->u.xmlreader.split_level = 0;
443             p->u.xmlreader.reader = 0;
444
445             for (attr = ptr->properties; attr; attr = attr->next)
446             {
447                 if (attr_content(attr, "level", &level_str))
448                     ;
449                 else
450                 {
451                     dom_log(YLOG_WARN, tinfo, ptr,
452                             "bad attribute @%s, expected @level",
453                             attr->name);
454                 }
455             }
456             if (level_str)
457                 p->u.xmlreader.split_level = atoi(level_str);
458                 
459             ptr = ptr->next;
460
461             parse_convert(tinfo, ptr, &p->convert);
462             break;
463         }
464         else
465         {
466             dom_log(YLOG_WARN, tinfo, ptr,
467                     "bad element <%s>, expected <marc>|<xmlreader>",
468                     ptr->name);
469             return ZEBRA_FAIL;
470         }
471     }
472     return ZEBRA_OK;
473 }
474
475 static ZEBRA_RES parse_dom(struct filter_info *tinfo, const char *fname)
476 {
477     char tmp_full_name[1024];
478     xmlNodePtr ptr;
479     xmlDocPtr doc;
480
481     tinfo->fname = odr_strdup(tinfo->odr_config, fname);
482     
483     if (yaz_filepath_resolve(tinfo->fname, tinfo->profile_path, 
484                              NULL, tmp_full_name))
485         tinfo->full_name = odr_strdup(tinfo->odr_config, tmp_full_name);
486     else
487         tinfo->full_name = odr_strdup(tinfo->odr_config, tinfo->fname);
488     
489     yaz_log(YLOG_LOG, "%s dom filter: "
490             "loading config file %s", tinfo->fname, tinfo->full_name);
491
492     doc = xmlParseFile(tinfo->full_name);
493     if (!doc)
494     {
495         yaz_log(YLOG_WARN, "%s: dom filter: "
496                 "failed to parse config file %s",
497                 tinfo->fname, tinfo->full_name);
498         return ZEBRA_FAIL;
499     }
500     /* save because we store ptrs to the content */ 
501     tinfo->doc_config = doc;
502     
503     ptr = xmlDocGetRootElement(doc);
504     if (!ptr || ptr->type != XML_ELEMENT_NODE 
505         || XML_STRCMP(ptr->name, "dom"))
506     {
507         dom_log(YLOG_WARN, tinfo, ptr,
508                 "bad root element <%s>, expected root element <dom>", 
509                 ptr->name);  
510         return ZEBRA_FAIL;
511     }
512
513     ptr = ptr->children;
514     FOR_EACH_ELEMENT(ptr) {
515         if (!XML_STRCMP(ptr->name, "extract"))
516         {
517             /*
518               <extract name="index">
519               <xslt stylesheet="first.xsl"/>
520               <xslt stylesheet="second.xsl"/>
521               </extract>
522             */
523             struct _xmlAttr *attr;
524             struct filter_extract *f =
525                 odr_malloc(tinfo->odr_config, sizeof(*f));
526             
527             tinfo->extract = f;
528             f->name = 0;
529             f->convert = 0;
530             for (attr = ptr->properties; attr; attr = attr->next)
531             {
532                 if (attr_content(attr, "name", &f->name))
533                     ;
534                 else
535                 {
536                     dom_log(YLOG_WARN, tinfo, ptr,
537                             "bad attribute @%s, expected @name",
538                             attr->name);
539                 }
540             }
541             parse_convert(tinfo, ptr->children, &f->convert);
542         }
543         else if (!XML_STRCMP(ptr->name, "retrieve"))
544         {  
545             /* 
546                <retrieve name="F">
547                <xslt stylesheet="some.xsl"/>
548                <xslt stylesheet="some.xsl"/>
549                </retrieve>
550             */
551             struct _xmlAttr *attr;
552             struct filter_retrieve **fp = &tinfo->retrieve_list;
553             struct filter_retrieve *f =
554                 odr_malloc(tinfo->odr_config, sizeof(*f));
555             
556             while (*fp)
557                 fp = &(*fp)->next;
558
559             *fp = f;
560             f->name = 0;
561             f->identifier = 0;
562             f->convert = 0;
563             f->next = 0;
564
565             for (attr = ptr->properties; attr; attr = attr->next)
566             {
567                 if (attr_content(attr, "identifier", 
568                                  &f->identifier))
569                     ;
570                 else if (attr_content(attr, "name", &f->name))
571                     ;
572                 else
573                 {
574                     dom_log(YLOG_WARN, tinfo, ptr,
575                             "bad attribute @%s,  expected @identifier|@name",
576                             attr->name);
577                 }
578             }
579             parse_convert(tinfo, ptr->children, &f->convert);
580         }
581         else if (!XML_STRCMP(ptr->name, "store"))
582         {
583             /*
584               <store name="F">
585               <xslt stylesheet="some.xsl"/>
586               <xslt stylesheet="some.xsl"/>
587               </retrieve>
588             */
589             struct filter_store *f =
590                 odr_malloc(tinfo->odr_config, sizeof(*f));
591             
592             tinfo->store = f;
593             f->convert = 0;
594             parse_convert(tinfo, ptr->children, &f->convert);
595         }
596         else if (!XML_STRCMP(ptr->name, "input"))
597         {
598             /*
599               <input syntax="xml">
600               <xmlreader level="1"/>
601               </input>
602               <input syntax="usmarc">
603               <marc inputcharset="marc-8"/>
604               </input>
605             */
606             struct _xmlAttr *attr;
607             const char  *syntax = 0;
608             const char *name = 0;
609             for (attr = ptr->properties; attr; attr = attr->next)
610             {
611                 if (attr_content(attr, "syntax", &syntax))
612                     ;
613                 else if (attr_content(attr, "name", &name))
614                     ;
615                 else
616                 {
617                     dom_log(YLOG_WARN, tinfo, ptr,
618                             "bad attribute @%s,  expected @syntax|@name",
619                             attr->name);
620                 }
621             }
622             parse_input(tinfo, ptr->children, syntax, name);
623         }
624         else
625         {
626             dom_log(YLOG_WARN, tinfo, ptr,
627                     "bad element <%s>, "
628                     "expected <extract>|<input>|<retrieve>|<store>",
629                     ptr->name);
630             return ZEBRA_FAIL;
631         }
632     }
633     return ZEBRA_OK;
634 }
635
636 static struct filter_retrieve *lookup_retrieve(struct filter_info *tinfo,
637                                                const char *est)
638 {
639     struct filter_retrieve *f = tinfo->retrieve_list;
640
641     /* return first schema if no est is provided */
642     if (!est)
643         return f;
644     for (; f; f = f->next)
645     { 
646         /* find requested schema */
647         if (est) 
648         {    
649             if (f->identifier && !strcmp(f->identifier, est))
650                 return f;
651             if (f->name && !strcmp(f->name, est))
652                 return f;
653         } 
654     }
655     return 0;
656 }
657
658 static ZEBRA_RES filter_config(void *clientData, Res res, const char *args)
659 {
660     struct filter_info *tinfo = clientData;
661     if (!args || !*args)
662     {
663         yaz_log(YLOG_WARN, "dom filter: need config file");
664         return ZEBRA_FAIL;
665     }
666
667     if (tinfo->fname && !strcmp(args, tinfo->fname))
668         return ZEBRA_OK;
669     
670     tinfo->profile_path = res_get(res, "profilePath");
671
672     destroy_dom(tinfo);
673     return parse_dom(tinfo, args);
674 }
675
676 static void filter_destroy(void *clientData)
677 {
678     struct filter_info *tinfo = clientData;
679     destroy_dom(tinfo);
680     odr_destroy(tinfo->odr_config);
681     odr_destroy(tinfo->odr_record);
682     xfree(tinfo);
683 }
684
685 static int ioread_ex(void *context, char *buffer, int len)
686 {
687     struct recExtractCtrl *p = context;
688     return p->stream->readf(p->stream, buffer, len);
689 }
690
691 static int ioclose_ex(void *context)
692 {
693     return 0;
694 }
695
696
697 /* DOM filter style indexing */
698 static int attr_content_xml(struct _xmlAttr *attr, const char *name,
699                             xmlChar **dst_content)
700 {
701     if (0 == XML_STRCMP(attr->name, name) && attr->children 
702         && attr->children->type == XML_TEXT_NODE)
703     {
704         *dst_content = (attr->children->content);
705         return 1;
706     }
707     return 0;
708 }
709
710
711 /* DOM filter style indexing */
712 static void index_value_of(struct filter_info *tinfo, 
713                            struct recExtractCtrl *extctr,
714                            RecWord* recword, 
715                            xmlNodePtr node, 
716                            xmlChar * index_p)
717 {
718     if (tinfo->record_info_invoked == 1)
719     {
720         xmlChar *text = xmlNodeGetContent(node);
721         size_t text_len = strlen((const char *)text);
722         
723         /* if there is no text, we do not need to proceed */
724         if (text_len)
725         {            
726             xmlChar *look = index_p;
727             xmlChar *bval;
728             xmlChar *eval;
729
730             xmlChar index[256];
731             xmlChar type[256];
732
733             /* assingning text to be indexed */
734             recword->term_buf = (const char *)text;
735             recword->term_len = text_len;
736
737             /* parsing all index name/type pairs */
738             /* may not start with ' ' or ':' */
739             while (*look && ' ' != *look && ':' != *look)
740             {
741                 /* setting name and type to zero */
742                 *index = '\0';
743                 *type = '\0';
744     
745                 /* parsing one index name */
746                 bval = look;
747                 while (*look && ':' != *look && ' ' != *look)
748                 {
749                     look++;
750                 }
751                 eval = look;
752                 strncpy((char *)index, (const char *)bval, eval - bval);
753                 index[eval - bval] = '\0';
754     
755     
756                 /* parsing one index type, if existing */
757                 if (':' == *look)
758                 {
759                     look++;
760       
761                     bval = look;
762                     while (*look && ' ' != *look)
763                     {
764                         look++;
765                     }
766                     eval = look;
767                     strncpy((char *)type, (const char *)bval, eval - bval);
768                     type[eval - bval] = '\0';
769                 }
770
771                 /* actually indexing the text given */
772                 dom_log(YLOG_DEBUG, tinfo, 0, 
773                         "INDEX '%s:%s' '%s'", 
774                         index ? (const char *) index : "null",
775                         type ? (const char *) type : "null", 
776                         text ? (const char *) text : "null");
777
778                 recword->index_name = (const char *)index;
779                 if (type && *type)
780                     recword->index_type = *type;
781
782                 /* writing debug out */
783                 if (extctr->flagShowRecords)
784                     dom_log(YLOG_LOG, tinfo, 0, 
785                             "INDEX '%s:%s' '%s'", 
786                             index ? (const char *) index : "null",
787                             type ? (const char *) type : "null", 
788                             text ? (const char *) text : "null");
789                 
790                 /* actually indexing the text given */
791                 recword->index_name = (const char *)index;
792                 if (type && *type)
793                     recword->index_type = *type;
794                 (extctr->tokenAdd)(recword);
795
796                 /* eat whitespaces */
797                 if (*look && ' ' == *look && *(look+1))
798                 {
799                     look++;
800                 } 
801             }
802         }
803         xmlFree(text); 
804     }
805 }
806
807
808 /* DOM filter style indexing */
809 static void set_record_info(struct filter_info *tinfo, 
810                             struct recExtractCtrl *extctr, 
811                             xmlNodePtr node, 
812                             xmlChar * id_p, 
813                             xmlChar * rank_p, 
814                             xmlChar * type_p)
815 {
816
817     /* writing debug info out */
818     if (extctr->flagShowRecords)
819         dom_log(YLOG_LOG, tinfo, 0,
820                 "RECORD id=%s rank=%s type=%s", 
821                 id_p ? (const char *) id_p : "(null)",
822                 rank_p ? (const char *) rank_p : "(null)",
823                 type_p ? (const char *) type_p : "(null)");
824     
825
826     if (id_p)
827         sscanf((const char *)id_p, "%255s", extctr->match_criteria);
828
829     if (rank_p)
830         extctr->staticrank = atozint((const char *)rank_p);
831
832     /*     if (!strcmp("update", type_str)) */
833     /*         index_node(tinfo, ctrl, ptr, recword); */
834     /*     else if (!strcmp("delete", type_str)) */
835     /*         dom_log(YLOG_WARN, tinfo, ptr, "dom filter delete: to be implemented"); */
836     /*     else */
837     /*         dom_log(YLOG_WARN, tinfo, ptr, "dom filter: unknown record type '%s'",  */
838     /*                 type_str); */
839     if (tinfo->record_info_invoked == 1)
840     {
841         /* warn about multiple only once */
842         dom_log(YLOG_WARN, tinfo, node, "multiple record elements");
843     }
844     tinfo->record_info_invoked++;
845
846 }
847
848
849 /* DOM filter style indexing */
850 static void process_xml_element_zebra_node(struct filter_info *tinfo, 
851                                            struct recExtractCtrl *extctr, 
852                                            RecWord* recword, 
853                                            xmlNodePtr node)
854 {
855     if (node->type == XML_ELEMENT_NODE && node->ns && node->ns->href
856         && 0 == XML_STRCMP(node->ns->href, zebra_dom_ns))
857     {
858          if (0 == XML_STRCMP(node->name, "index"))
859          {
860             xmlChar *index_p = 0;
861
862             struct _xmlAttr *attr;      
863             for (attr = node->properties; attr; attr = attr->next)
864             {
865                 if (attr_content_xml(attr, "name", &index_p))
866                 {
867                     index_value_of(tinfo, extctr, recword,node, index_p);
868                 }  
869                 else
870                 {
871                     dom_log(YLOG_WARN, tinfo, node,
872                             "bad attribute @%s, expected @name",
873                             attr->name);
874                 }
875             }
876         }
877         else if (0 == XML_STRCMP(node->name, "record"))
878         {
879             xmlChar *id_p = 0;
880             xmlChar *rank_p = 0;
881             xmlChar *type_p = 0;
882
883             struct _xmlAttr *attr;
884             for (attr = node->properties; attr; attr = attr->next)
885             {
886                 if (attr_content_xml(attr, "id", &id_p))
887                     ;
888                 else if (attr_content_xml(attr, "rank", &rank_p))
889                     ;
890                 else if (attr_content_xml(attr, "type", &type_p))
891                     ;
892                 else
893                 {
894                     dom_log(YLOG_WARN, tinfo, node,
895                             "bad attribute @%s, expected @id|@rank|@type",
896                             attr->name);
897                 }
898
899                 if (type_p && 0 != strcmp("update", (const char *)type_p))
900                 {
901                     dom_log(YLOG_WARN, tinfo, node,
902                             "attribute @%s, only implemented '@type='update'",
903                             attr->name);
904                 }
905             }
906             set_record_info(tinfo, extctr, node, id_p, rank_p, type_p);
907         } 
908         else
909         {
910             dom_log(YLOG_WARN, tinfo, node,
911                     "bad element <%s>,"
912                     " expected <record>|<index> in namespace '%s'",
913                     node->name, zebra_dom_ns);
914         }
915     }
916 }
917
918
919 /* DOM filter style indexing */
920 static void process_xml_pi_node(struct filter_info *tinfo, 
921                                 struct recExtractCtrl *extctr, 
922                                 xmlNodePtr node,
923                                 xmlChar **index_pp)
924 {
925     /* if right PI name, continue parsing PI */
926     if (0 == strcmp(zebra_pi_name, (const char *)node->name))
927     {
928         xmlChar *pi_p =  node->content;
929         xmlChar *look = pi_p;
930     
931         xmlChar *bval;
932         xmlChar *eval;
933
934         /* parsing PI record instructions */
935         if (0 == strncmp((const char *)look, "record", 6))
936         {
937             xmlChar id[256];
938             xmlChar rank[256];
939             xmlChar type[256];
940
941             *id = '\0';
942             *rank = '\0';
943             *type = '\0';
944       
945             look += 6;
946       
947             /* eat whitespace */
948             while (*look && ' ' == *look && *(look+1))
949                 look++;
950
951             /* parse possible id */
952             if (*look && 0 == strncmp((const char *)look, "id=", 3))
953             {
954                 look += 3;
955                 bval = look;
956                 while (*look && ' ' != *look)
957                     look++;
958                 eval = look;
959                 strncpy((char *)id, (const char *)bval, eval - bval);
960                 id[eval - bval] = '\0';
961             }
962       
963             /* eat whitespace */
964             while (*look && ' ' == *look && *(look+1))
965                 look++;
966       
967             /* parse possible rank */
968             if (*look && 0 == strncmp((const char *)look, "rank=", 5))
969             {
970                 look += 6;
971                 bval = look;
972                 while (*look && ' ' != *look)
973                     look++;
974                 eval = look;
975                 strncpy((char *)rank, (const char *)bval, eval - bval);
976                 rank[eval - bval] = '\0';
977             }
978
979             /* eat whitespace */
980             while (*look && ' ' == *look && *(look+1))
981                 look++;
982
983             if (look && '\0' != *look)
984             {
985                 dom_log(YLOG_WARN, tinfo, node,
986                         "content '%s', can not parse '%s'",
987                         pi_p, look);
988             }
989             else 
990                 set_record_info(tinfo, extctr, node, id, rank, 0);
991
992         } 
993         /* parsing index instruction */
994         else if (0 == strncmp((const char *)look, "index", 5))
995         {
996             look += 5;
997       
998             /* eat whitespace */
999             while (*look && ' ' == *look && *(look+1))
1000                 look++;
1001
1002             /* export index instructions to outside */
1003             *index_pp = look;
1004         } 
1005         else 
1006         {
1007             dom_log(YLOG_WARN, tinfo, node,
1008                     "content '%s', can not parse '%s'",
1009                     pi_p, look);
1010         }
1011     }
1012 }
1013
1014 /* DOM filter style indexing */
1015 static void process_xml_element_node(struct filter_info *tinfo, 
1016                                      struct recExtractCtrl *extctr, 
1017                                      RecWord* recword, 
1018                                      xmlNodePtr node)
1019 {
1020     /* remember indexing instruction from PI to next element node */
1021     xmlChar *index_p = 0;
1022
1023     /* check if we are an element node in the special zebra namespace 
1024        and either set record data or index value-of node content*/
1025     process_xml_element_zebra_node(tinfo, extctr, recword, node);
1026   
1027     /* loop through kid nodes */
1028     for (node = node->children; node; node = node->next)
1029     {
1030         /* check and set PI record and index index instructions */
1031         if (node->type == XML_PI_NODE)
1032         {
1033             process_xml_pi_node(tinfo, extctr, node, &index_p);
1034         }
1035         else if (node->type == XML_ELEMENT_NODE)
1036         {
1037             /* if there was a PI index instruction before this element */
1038             if (index_p)
1039             {
1040                 index_value_of(tinfo, extctr, recword, node, index_p);
1041                 index_p = 0;
1042             }
1043             process_xml_element_node(tinfo, extctr, recword,node);
1044         }
1045         else
1046             continue;
1047     }
1048 }
1049
1050
1051 /* DOM filter style indexing */
1052 static void extract_dom_doc_node(struct filter_info *tinfo, 
1053                                  struct recExtractCtrl *extctr, 
1054                                  xmlDocPtr doc)
1055 {
1056     /* only need to do the initialization once, reuse recword for all terms */
1057     RecWord recword;
1058     (*extctr->init)(extctr, &recword);
1059
1060     tinfo->record_info_invoked = 0;
1061     process_xml_element_node(tinfo, extctr, &recword, (xmlNodePtr)doc);
1062 }
1063
1064
1065
1066
1067 static int convert_extract_doc(struct filter_info *tinfo, 
1068                                struct filter_input *input,
1069                                struct recExtractCtrl *p, 
1070                                xmlDocPtr doc)
1071
1072 {
1073     xmlChar *buf_out;
1074     int len_out;
1075     const char *params[10];
1076     xsltStylesheetPtr last_xsp = 0;
1077     xmlDocPtr store_doc = 0;
1078
1079     params[0] = 0;
1080     set_param_str(params, "schema", zebra_dom_ns, tinfo->odr_record);
1081
1082     /* input conversion */
1083     perform_convert(tinfo, p, input->convert, params, &doc, 0);
1084
1085     if (tinfo->store)
1086     {
1087         /* store conversion */
1088         store_doc = xmlCopyDoc(doc, 1);
1089         perform_convert(tinfo, p, tinfo->store->convert,
1090                         params, &store_doc, &last_xsp);
1091     }
1092     
1093     if (last_xsp)
1094         xsltSaveResultToString(&buf_out, &len_out, 
1095                                store_doc ? store_doc : doc, last_xsp);
1096     else
1097         xmlDocDumpMemory(store_doc ? store_doc : doc, &buf_out, &len_out);
1098   
1099     /* if (p->flagShowRecords)
1100        fwrite(buf_out, len_out, 1, stdout); */
1101
1102     (*p->setStoreData)(p, buf_out, len_out);
1103     xmlFree(buf_out);
1104
1105     if (store_doc)
1106         xmlFreeDoc(store_doc);
1107
1108     /* extract conversion */
1109     perform_convert(tinfo, p, tinfo->extract->convert, params, &doc, 0);
1110
1111     /* finally, do the indexing */
1112     if (doc)
1113         extract_dom_doc_node(tinfo, p, doc);
1114
1115     if (doc)
1116         xmlFreeDoc(doc);
1117
1118     if (tinfo->record_info_invoked == 0)
1119         return RECCTRL_EXTRACT_SKIP;
1120     return RECCTRL_EXTRACT_OK;
1121 }
1122
1123 static int extract_xml_split(struct filter_info *tinfo,
1124                              struct filter_input *input,
1125                              struct recExtractCtrl *p)
1126 {
1127     int ret;
1128
1129     if (p->first_record)
1130     {
1131         if (input->u.xmlreader.reader)
1132             xmlFreeTextReader(input->u.xmlreader.reader);
1133         input->u.xmlreader.reader = xmlReaderForIO(ioread_ex, ioclose_ex,
1134                                                    p /* I/O handler */,
1135                                                    0 /* URL */, 
1136                                                    0 /* encoding */,
1137                                                    XML_PARSE_XINCLUDE|
1138                                                    XML_PARSE_NOENT);
1139     }
1140     if (!input->u.xmlreader.reader)
1141         return RECCTRL_EXTRACT_ERROR_GENERIC;
1142
1143     ret = xmlTextReaderRead(input->u.xmlreader.reader);
1144     while (ret == 1)
1145     {
1146         int type = xmlTextReaderNodeType(input->u.xmlreader.reader);
1147         int depth = xmlTextReaderDepth(input->u.xmlreader.reader);
1148         if (type == XML_READER_TYPE_ELEMENT && 
1149             input->u.xmlreader.split_level == depth)
1150         {
1151             xmlNodePtr ptr
1152                 = xmlTextReaderExpand(input->u.xmlreader.reader);
1153             if (ptr)
1154             {
1155                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
1156                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
1157                 
1158                 xmlDocSetRootElement(doc, ptr2);
1159                 
1160                 /* writing debug info out */
1161                 if (p->flagShowRecords)
1162                 {
1163                     xmlChar *buf_out = 0;
1164                     int len_out = 0;
1165                     xmlDocDumpMemory(doc, &buf_out, &len_out);
1166                     yaz_log(YLOG_LOG, "%s: XMLREADER depth: %i\n%.*s", 
1167                             tinfo->fname ? tinfo->fname : "(none)",
1168                             depth, len_out, buf_out); 
1169                     xmlFree(buf_out);
1170                 }
1171                 
1172                 return convert_extract_doc(tinfo, input, p, doc);
1173             }
1174             else
1175             {
1176                 xmlFreeTextReader(input->u.xmlreader.reader);
1177                 input->u.xmlreader.reader = 0;
1178                 return RECCTRL_EXTRACT_ERROR_GENERIC;
1179             }
1180         }
1181         ret = xmlTextReaderRead(input->u.xmlreader.reader);
1182     }
1183     xmlFreeTextReader(input->u.xmlreader.reader);
1184     input->u.xmlreader.reader = 0;
1185     return RECCTRL_EXTRACT_EOF;
1186 }
1187
1188 static int extract_xml_full(struct filter_info *tinfo, 
1189                             struct filter_input *input,
1190                             struct recExtractCtrl *p)
1191 {
1192     if (p->first_record) /* only one record per stream */
1193     {
1194         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, 
1195                                   p /* I/O handler */,
1196                                   0 /* URL */,
1197                                   0 /* encoding */,
1198                                   XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1199         if (!doc)
1200         {
1201             return RECCTRL_EXTRACT_ERROR_GENERIC;
1202         }
1203         return convert_extract_doc(tinfo, input, p, doc);
1204     }
1205     else
1206         return RECCTRL_EXTRACT_EOF;
1207 }
1208
1209 static int extract_iso2709(struct filter_info *tinfo,
1210                            struct filter_input *input,
1211                            struct recExtractCtrl *p)
1212 {
1213     char buf[100000];
1214     int record_length;
1215     int read_bytes, r;
1216
1217     if (p->stream->readf(p->stream, buf, 5) != 5)
1218         return RECCTRL_EXTRACT_EOF;
1219     while (*buf < '0' || *buf > '9')
1220     {
1221         int i;
1222
1223         dom_log(YLOG_WARN, tinfo, 0,
1224                 "MARC: Skipping bad byte %d (0x%02X)",
1225                 *buf & 0xff, *buf & 0xff);
1226         for (i = 0; i<4; i++)
1227             buf[i] = buf[i+1];
1228
1229         if (p->stream->readf(p->stream, buf+4, 1) != 1)
1230             return RECCTRL_EXTRACT_EOF;
1231     }
1232     record_length = atoi_n (buf, 5);
1233     if (record_length < 25)
1234     {
1235         dom_log(YLOG_WARN, tinfo, 0,
1236                 "MARC record length < 25, is %d",  record_length);
1237         return RECCTRL_EXTRACT_ERROR_GENERIC;
1238     }
1239     read_bytes = p->stream->readf(p->stream, buf+5, record_length-5);
1240     if (read_bytes < record_length-5)
1241     {
1242         dom_log(YLOG_WARN, tinfo, 0,
1243                 "couldn't read whole MARC record");
1244         return RECCTRL_EXTRACT_ERROR_GENERIC;
1245     }
1246     r = yaz_marc_read_iso2709(input->u.marc.handle,  buf, record_length);
1247     if (r < record_length)
1248     {
1249         dom_log (YLOG_WARN, tinfo, 0,
1250                  "parsing of MARC record failed r=%d length=%d",
1251                  r, record_length);
1252         return RECCTRL_EXTRACT_ERROR_GENERIC;
1253     }
1254     else
1255     {
1256         xmlDocPtr rdoc;
1257         xmlNode *root_ptr;
1258         yaz_marc_write_xml(input->u.marc.handle, &root_ptr, 0, 0, 0);
1259         rdoc = xmlNewDoc((const xmlChar*) "1.0");
1260         xmlDocSetRootElement(rdoc, root_ptr);
1261         return convert_extract_doc(tinfo, input, p, rdoc);        
1262     }
1263     return RECCTRL_EXTRACT_OK;
1264 }
1265
1266 static int filter_extract(void *clientData, struct recExtractCtrl *p)
1267 {
1268     struct filter_info *tinfo = clientData;
1269     struct filter_input *input = tinfo->input_list;
1270
1271     if (!input)
1272         return RECCTRL_EXTRACT_ERROR_GENERIC;
1273
1274     odr_reset(tinfo->odr_record);
1275     switch(input->type)
1276     {
1277     case DOM_INPUT_XMLREADER:
1278         if (input->u.xmlreader.split_level == 0)
1279             return extract_xml_full(tinfo, input, p);
1280         else
1281             return extract_xml_split(tinfo, input, p);
1282         break;
1283     case DOM_INPUT_MARC:
1284         return extract_iso2709(tinfo, input, p);
1285     }
1286     return RECCTRL_EXTRACT_ERROR_GENERIC;
1287 }
1288
1289 static int ioread_ret(void *context, char *buffer, int len)
1290 {
1291     struct recRetrieveCtrl *p = context;
1292     return p->stream->readf(p->stream, buffer, len);
1293 }
1294
1295 static int ioclose_ret(void *context)
1296 {
1297     return 0;
1298 }
1299
1300 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
1301 {
1302     /* const char *esn = zebra_dom_ns; */
1303     const char *esn = 0;
1304     const char *params[32];
1305     struct filter_info *tinfo = clientData;
1306     xmlDocPtr doc;
1307     struct filter_retrieve *retrieve;
1308     xsltStylesheetPtr last_xsp = 0;
1309
1310     if (p->comp)
1311     {
1312         if (p->comp->which == Z_RecordComp_simple
1313             && p->comp->u.simple->which == Z_ElementSetNames_generic)
1314         {
1315             esn = p->comp->u.simple->u.generic;
1316         }
1317         else if (p->comp->which == Z_RecordComp_complex 
1318                  && p->comp->u.complex->generic->elementSpec
1319                  && p->comp->u.complex->generic->elementSpec->which ==
1320                  Z_ElementSpec_elementSetName)
1321         {
1322             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
1323         }
1324     }
1325     retrieve = lookup_retrieve(tinfo, esn);
1326     if (!retrieve)
1327     {
1328         p->diagnostic =
1329             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
1330         return 0;
1331     }
1332
1333     params[0] = 0;
1334     set_param_int(params, "id", p->localno, p->odr);
1335     if (p->fname)
1336         set_param_str(params, "filename", p->fname, p->odr);
1337     if (p->staticrank >= 0)
1338         set_param_int(params, "rank", p->staticrank, p->odr);
1339
1340     if (esn)
1341         set_param_str(params, "schema", esn, p->odr);
1342     else
1343         if (retrieve->name)
1344             set_param_str(params, "schema", retrieve->name, p->odr);
1345         else if (retrieve->identifier)
1346             set_param_str(params, "schema", retrieve->identifier, p->odr);
1347         else
1348             set_param_str(params, "schema", "", p->odr);
1349
1350     if (p->score >= 0)
1351         set_param_int(params, "score", p->score, p->odr);
1352     set_param_int(params, "size", p->recordSize, p->odr);
1353
1354     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
1355                     0 /* URL */,
1356                     0 /* encoding */,
1357                     XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1358     if (!doc)
1359     {
1360         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1361         return 0;
1362     }
1363
1364     /* retrieve conversion */
1365     perform_convert(tinfo, 0, retrieve->convert, params, &doc, &last_xsp);
1366     if (!doc)
1367     {
1368         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1369     }
1370     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
1371     {
1372         xmlChar *buf_out;
1373         int len_out;
1374
1375         if (last_xsp)
1376             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1377         else
1378             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1379
1380         p->output_format = VAL_TEXT_XML;
1381         p->rec_len = len_out;
1382         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1383         memcpy(p->rec_buf, buf_out, p->rec_len);
1384         xmlFree(buf_out);
1385     }
1386     else if (p->output_format == VAL_SUTRS)
1387     {
1388         xmlChar *buf_out;
1389         int len_out;
1390
1391         if (last_xsp)
1392             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1393         else
1394             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1395         
1396         p->output_format = VAL_SUTRS;
1397         p->rec_len = len_out;
1398         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1399         memcpy(p->rec_buf, buf_out, p->rec_len);
1400         
1401         xmlFree(buf_out);
1402     }
1403     else
1404     {
1405         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
1406     }
1407     xmlFreeDoc(doc);
1408     return 0;
1409 }
1410
1411 static struct recType filter_type = {
1412     0,
1413     "dom",
1414     filter_init,
1415     filter_config,
1416     filter_destroy,
1417     filter_extract,
1418     filter_retrieve
1419 };
1420
1421 RecType
1422 #ifdef IDZEBRA_STATIC_DOM
1423 idzebra_filter_dom
1424 #else
1425 idzebra_filter
1426 #endif
1427
1428 [] = {
1429     &filter_type,
1430     0,
1431 };
1432 /*
1433  * Local variables:
1434  * c-basic-offset: 4
1435  * indent-tabs-mode: nil
1436  * End:
1437  * vim: shiftwidth=4 tabstop=8 expandtab
1438  */
1439