Loading of the nfa now possible from an xml file.
[yaz-moved-to-github.git] / src / nfaxml.c
1 /*  Copyright (C) 2006, Index Data ApS
2  *  See the file LICENSE for details.
3  * 
4  *  $Id: nfaxml.c,v 1.9 2006-07-14 13:06:38 heikki Exp $ 
5  */
6
7 /**
8  * \file nfaxml.c
9  * \brief Routines for reading a NFA spec from an XML file
10  *
11  */
12
13 #if YAZ_HAVE_XML2
14
15 #include <string.h>
16
17 /* #include <libxml/parser.h> */
18 #include <libxml/tree.h>
19 #include <libxml/xinclude.h>
20
21 #include <yaz/nfa.h>
22 #include <yaz/nmem.h> 
23 #include <yaz/yconfig.h>
24 #include <yaz/nfa.h>
25 #include <yaz/nfaxml.h>
26 #include <yaz/libxml2_error.h>
27
28 /** \brief How long strings we are willing to handle here */
29 #define MAXDATALEN 200 
30
31 /** \brief Get content of a node, in utf16, for yaz_nfa */
32 static int utf16_content(xmlNodePtr node, yaz_nfa_char *buf, int maxlen,
33         const char *filename, int rulenumber)
34 {
35     int bufidx=0;
36     xmlChar *content = xmlNodeGetContent(node);
37     xmlChar *cp=content;
38     int conlen=strlen((char *)content);
39     int len;
40     int res;
41     while (*cp && (bufidx<maxlen) ) {
42         len=conlen;
43         res=xmlGetUTF8Char(cp,&len);
44         if (res==-1) {
45             /* should be caught earlier */
46             yaz_log(YLOG_FATAL,"Illegal utf-8 sequence "
47                     "%d bytes into '%s' in %s, rule %d ",
48                     cp-content, content, filename, rulenumber);
49             xmlFree(content);
50             return -1;
51         }
52         buf[bufidx++]=res;
53         cp +=len;
54         conlen -=len;
55     }
56     buf[bufidx]=0;
57     xmlFree(content);
58     return bufidx;
59 }
60
61 static int parse_range(xmlNodePtr node, 
62         yaz_nfa_char *range_start,
63         yaz_nfa_char *range_end,
64         const char *filename, int rulenumber )
65 {
66     xmlChar *content = xmlNodeGetContent(node);
67     xmlChar *cp=content;
68     int conlen=strlen((char *)content);
69     int len;
70     int res;
71     len=conlen;
72     res=xmlGetUTF8Char(cp,&len);
73     if ( res != -1 ) {
74         *range_start=res;
75         cp +=len;
76         conlen -=len;
77         len=conlen;
78         res=xmlGetUTF8Char(cp,&len);
79         if (res != '-' )
80             res = -1;
81     }
82     if ( res != -1 ) {
83         cp +=len;
84         conlen -=len;
85         len=conlen;
86         res=xmlGetUTF8Char(cp,&len);
87     }
88     if ( res != -1 ) {
89         *range_end=res;
90     }
91     xmlFree(content);
92     if (res==-1) {
93         yaz_log(YLOG_FATAL,"Illegal range. '%s'. Must be like 'a-z' "
94                 "'in %s, rule %d ",
95                 content, filename, rulenumber);
96         return 0;
97     }
98     return 1;
99 } /* parserange */
100
101
102 /** \brief Parse a fromstring clause */
103 static yaz_nfa_state *parse_fromstring(yaz_nfa *nfa, 
104         xmlNodePtr node, const char *filename, int rulenumber )
105 {
106     yaz_nfa_char buf[MAXDATALEN];
107     yaz_nfa_state *state;
108     int bufidx=utf16_content(node, buf, MAXDATALEN, filename, rulenumber);
109     if (bufidx<0) 
110         return 0;
111     state=yaz_nfa_add_sequence(nfa, 0, buf, bufidx);
112     return state;
113 } /* parse_fromstring */
114
115 /** \brief Parse a tostring clause */
116 static yaz_nfa_converter *parse_tostring(yaz_nfa *nfa,
117                 xmlNodePtr node, const char *filename, int rulenumber )
118 {
119     yaz_nfa_char buf[MAXDATALEN];
120     yaz_nfa_converter *conv;
121     int bufidx=utf16_content(node, buf, MAXDATALEN, filename, rulenumber);
122     if (bufidx<0) 
123         return 0;
124     conv=yaz_nfa_create_string_converter(nfa, buf, bufidx);
125     return conv;
126 } /* parse_tostring */
127
128 static yaz_nfa_state * parse_fromrange(yaz_nfa *nfa,
129                 xmlNodePtr node, 
130                 yaz_nfa_char *from_begin,
131                 yaz_nfa_char *from_end,
132                 const char *filename, int rulenumber )
133 {
134     yaz_nfa_char begin;
135     yaz_nfa_char end;
136     yaz_nfa_state *state;
137     int rc;
138     rc=parse_range(node, &begin, &end, filename, rulenumber);
139     if (!rc)
140         return 0;
141     *from_begin=begin;
142     *from_end=end; /* save for calculating the to-range */
143     state=yaz_nfa_add_range(nfa, 0, begin, end);
144     return state;
145 } /* parse_fromrange */
146
147 static yaz_nfa_converter *parse_torange(yaz_nfa *nfa,
148              xmlNodePtr node, yaz_nfa_char from_begin, yaz_nfa_char from_end,
149              const char *filename, int rulenumber )
150 {
151     yaz_nfa_char begin;
152     yaz_nfa_char end;
153     yaz_nfa_converter *conv;
154     int rc;
155     rc=parse_range(node, &begin, &end, filename, rulenumber);
156     if (!rc)
157         return 0;
158     if ( from_end - from_begin != end - begin ) {
159         yaz_log(YLOG_FATAL,"From-range not as long as to-range: "
160                 "from=%x-%x to=%x-%x in rule %d in %s",
161                 from_begin, from_end,  begin, end, rulenumber, filename);
162         return 0;
163     }
164     conv=yaz_nfa_create_range_converter(nfa, 0, from_begin, begin);
165     return conv;
166 } /* parse_torange */
167
168 /** \brief Parse one rule from an XML node */
169 static int parse_rule(yaz_nfa *nfa, xmlNodePtr rulenode, 
170         const char *filename, int rulenumber ) 
171 {
172     yaz_nfa_state *state=0;
173     yaz_nfa_converter *conv=0;
174     yaz_nfa_char range_begin=0, range_end=0;
175     xmlNodePtr node;
176     int clauses=0;
177     for (node = rulenode->children; node; node = node->next)
178     {
179         if (node->type != XML_ELEMENT_NODE)
180             continue;
181         clauses++;
182         if (!strcmp((const char *) node->name, "fromstring")) 
183         {
184             state = parse_fromstring(nfa, node, filename, rulenumber );
185             if (!state)
186                 return 0;
187         } else if (!strcmp((const char *) node->name, "tostring")) 
188         {
189             conv = parse_tostring(nfa, node, filename, rulenumber );
190             if (!conv)
191                 return 0;
192         } else if (!strcmp((const char *) node->name, "fromrange")) 
193         {
194             state = parse_fromrange(nfa, node, 
195                     &range_begin, &range_end, filename, rulenumber );
196             if (!state)
197                 return 0;
198         } else if (!strcmp((const char *) node->name, "torange")) 
199         {
200             conv = parse_torange(nfa, node, 
201                     range_begin, range_end, filename, rulenumber );
202             if (!conv)
203                 return 0;
204         } else {
205             yaz_log(YLOG_FATAL,"Unknown clause '%s' in %s rule %d",
206                     node->name, filename,rulenumber);
207             return 0;
208         }
209     } /* for child */
210     if (!state) {
211         yaz_log(YLOG_FATAL,"No 'from' clause in a rule %d in %s", 
212                 rulenumber,filename);
213         return 0;
214     }
215     if (!conv) {
216         yaz_log(YLOG_FATAL,"No 'to' clause in a rule %d in %s",
217                 rulenumber,filename);
218         return 0;
219     }
220     if (clauses != 2) {
221         yaz_log(YLOG_FATAL,"Must have exactly one 'from' and one 'to' clause "
222                 "in rule %d in %s", rulenumber,filename);
223         return 0;
224     }
225     if ( YAZ_NFA_SUCCESS == yaz_nfa_set_result(nfa,state,conv))
226         return 1; 
227     yaz_log(YLOG_FATAL,"Conflicting rules in %s rule %d",
228             filename, rulenumber);
229     return 0;
230 } /* parse_rule */
231
232
233 /** \brief Parse the NFA from a XML document 
234  */
235 yaz_nfa *yaz_nfa_parse_xml_doc(xmlDocPtr doc, const char *filename)
236 {
237     xmlNodePtr node;
238     yaz_nfa *nfa;
239     int rulenumber=0;
240
241     if (!doc)
242         return 0;
243     libxml2_error_to_yazlog(YLOG_FATAL, "yaz_nfa_parse_doc");
244     node = xmlDocGetRootElement(doc);
245     if (!node || node->type != XML_ELEMENT_NODE ||
246         strcmp((const char *) node->name, "ruleset")) 
247     {
248         yaz_log(YLOG_FATAL,"nfa_parse_xml: Could not find root element 'ruleset' "
249                 "in %s", filename);
250         return 0;
251     }
252     nfa= yaz_nfa_init();
253     if (!nfa) 
254     {
255         yaz_log(YLOG_FATAL,"nfa_parse_xml: Creating nfa failed, can't parse %s",
256                 filename);
257         return 0;
258     }
259         
260     for (node = node->children; node; node = node->next)
261     {
262         if (node->type != XML_ELEMENT_NODE)
263             continue;
264          if (!strcmp((const char *) node->name, "rule")) {
265              if (!parse_rule(nfa,node,filename,rulenumber++))
266                  return 0;
267          } else {
268             yaz_log(YLOG_FATAL,"nfa_parse_xml: "
269                     "expected 'rule', found '%s' in %s", 
270                     (const char *) node->name,filename);
271             return 0;
272          }
273     } /* for */
274     return nfa;
275 } /* yaz_nfa_parse_xml_doc */
276
277
278 /** \brief Parse the NFA from a file 
279  */
280 yaz_nfa *yaz_nfa_parse_xml_file(const char *filepath) 
281 {
282     int nSubst;
283     xmlDocPtr doc;
284     if (!filepath) 
285     {
286         yaz_log(YLOG_FATAL,"yaz_nfa_parse_xml_file called with NULL");
287         return 0;
288     }
289     libxml2_error_to_yazlog(YLOG_FATAL, "yaz_nfa_parse_xml_file");
290
291     doc = xmlParseFile(filepath);
292     if (!doc) {
293         return 0;
294     }
295     nSubst=xmlXIncludeProcess(doc);
296     if (nSubst==-1) {
297         return 0;
298     }
299     return yaz_nfa_parse_xml_doc(doc, filepath);
300 }
301
302 /** \brief Parse the NFA from a memory buffer
303  */
304 yaz_nfa *yaz_nfa_parse_xml_memory(const char *xmlbuff, const char *filename) {
305     xmlDocPtr doc;
306     if (!xmlbuff) 
307     {
308         yaz_log(YLOG_FATAL,"yaz_nfa_parse_memroy called with NULL");
309         return 0;
310     }
311     libxml2_error_to_yazlog(YLOG_FATAL, "yaz_nfa_parse_xml_memory");
312     doc = xmlParseMemory(xmlbuff, strlen(xmlbuff));
313     return yaz_nfa_parse_xml_doc(doc,filename);
314 }
315
316
317
318 #endif /* YAZ_HAVE_XML2 */
319
320
321 /*
322  * Local variables:
323  * c-basic-offset: 4
324  * indent-tabs-mode: nil
325  * End:
326  * vim: shiftwidth=4 tabstop=8 expandtab
327  */