Override id in icu_chain for relevance, sort, ..
[pazpar2-moved-to-github.git] / src / charsets.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2011 Index Data
3
4 Pazpar2 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 Pazpar2 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 /** \file charsets.c
21     \brief Pazpar2 Character set facilities
22 */
23
24 #if HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <yaz/xmalloc.h>
29 #include <yaz/wrbuf.h>
30 #include <yaz/log.h>
31 #include <yaz/yaz-version.h>
32 #include <ctype.h>
33 #include <assert.h>
34 #include <string.h>
35
36 #include "charsets.h"
37 #include "normalize7bit.h"
38
39 #if YAZ_HAVE_ICU
40 #include <yaz/icu.h>
41 #endif
42
43 typedef struct pp2_charset_s *pp2_charset_t;
44 static pp2_charset_t pp2_charset_create_xml(xmlNode *xml_node);
45 static pp2_charset_t pp2_charset_create(struct icu_chain * icu_chn);
46 static pp2_charset_t pp2_charset_create_a_to_z(void);
47 static void pp2_charset_destroy(pp2_charset_t pct);
48 static pp2_charset_token_t pp2_charset_tokenize(pp2_charset_t pct);
49
50 /* charset handle */
51 struct pp2_charset_s {
52     const char *(*token_next_handler)(pp2_charset_token_t prt);
53     const char *(*get_sort_handler)(pp2_charset_token_t prt);
54     const char *(*get_display_handler)(pp2_charset_token_t prt);
55 #if YAZ_HAVE_ICU
56     struct icu_chain * icu_chn;
57     UErrorCode icu_sts;
58 #endif
59 };
60
61 static const char *pp2_charset_token_null(pp2_charset_token_t prt);
62 static const char *pp2_charset_token_a_to_z(pp2_charset_token_t prt);
63 static const char *pp2_get_sort_ascii(pp2_charset_token_t prt);
64 static const char *pp2_get_display_ascii(pp2_charset_token_t prt);
65
66 #if YAZ_HAVE_ICU
67 static const char *pp2_charset_token_icu(pp2_charset_token_t prt);
68 static const char *pp2_get_sort_icu(pp2_charset_token_t prt);
69 static const char *pp2_get_display_icu(pp2_charset_token_t prt);
70 #endif
71
72 /* tokenzier handle */
73 struct pp2_charset_token_s {
74     const char *cp;     /* unnormalized buffer we're tokenizing */
75     const char *last_cp;  /* pointer to last token we're dealing with */
76     pp2_charset_t pct;  /* our main charset handle (type+config) */
77     WRBUF norm_str;     /* normized string we return (temporarily) */
78     WRBUF sort_str;     /* sort string we return (temporarily) */
79 #if YAZ_HAVE_ICU
80     yaz_icu_iter_t iter;
81 #endif
82 };
83
84 struct pp2_charset_fact_s {
85     struct pp2_charset_entry *list;
86     int ref_count;
87 };
88
89 struct pp2_charset_entry {
90     struct pp2_charset_entry *next;
91     pp2_charset_t pct;
92     char *name;
93 };
94
95
96 static int pp2_charset_fact_add(pp2_charset_fact_t pft,
97                                 pp2_charset_t pct, const char *default_id);
98
99 pp2_charset_fact_t pp2_charset_fact_create(void)
100 {
101     pp2_charset_fact_t pft = xmalloc(sizeof(*pft));
102     pft->list = 0;
103     pft->ref_count = 1;
104
105     pp2_charset_fact_add(pft, pp2_charset_create_a_to_z(), "relevance");
106     pp2_charset_fact_add(pft, pp2_charset_create_a_to_z(), "sort");
107     pp2_charset_fact_add(pft, pp2_charset_create_a_to_z(), "mergekey");
108     pp2_charset_fact_add(pft, pp2_charset_create(0), "facet");
109     return pft;
110 }
111
112 void pp2_charset_fact_destroy(pp2_charset_fact_t pft)
113 {
114     if (pft)
115     {
116         assert(pft->ref_count >= 1);
117         --(pft->ref_count);
118         if (pft->ref_count == 0)
119         {
120             struct pp2_charset_entry *pce = pft->list;
121             while (pce)
122             {
123                 struct pp2_charset_entry *next = pce->next;
124                 pp2_charset_destroy(pce->pct);
125                 xfree(pce->name);
126                 xfree(pce);
127                 pce = next;
128             }
129             xfree(pft);
130         }
131     }
132 }
133
134 int pp2_charset_fact_add(pp2_charset_fact_t pft,
135                          pp2_charset_t pct, const char *default_id)
136 {
137     struct pp2_charset_entry *pce;
138
139     for (pce = pft->list; pce; pce = pce->next)
140         if (!strcmp(default_id, pce->name))
141             break;
142
143     if (!pce)
144     {
145         pce = xmalloc(sizeof(*pce));
146         pce->name = xstrdup(default_id);
147         pce->next = pft->list;
148         pft->list = pce;
149     }
150     else
151     {
152         pp2_charset_destroy(pce->pct);
153     }
154     pce->pct = pct;
155     return 0;
156 }
157
158 int pp2_charset_fact_define(pp2_charset_fact_t pft,
159                             xmlNode *xml_node, const char *default_id)
160 {
161     int r;
162     pp2_charset_t pct;
163     xmlChar *id = 0;
164
165     assert(xml_node);
166     pct = pp2_charset_create_xml(xml_node);
167     if (!pct)
168         return -1;
169     if (!default_id)
170     {
171         id = xmlGetProp(xml_node, (xmlChar*) "id");
172         if (!id)
173         {
174             yaz_log(YLOG_WARN, "Missing id for icu_chain");
175             pp2_charset_destroy(pct);
176             return -1;
177         }
178         default_id = (const char *) id;
179     }
180     r = pp2_charset_fact_add(pft, pct, default_id);
181     if (id)
182         xmlFree(id);
183     return r;
184 }
185
186 void pp2_charset_fact_incref(pp2_charset_fact_t pft)
187 {
188     (pft->ref_count)++;
189 }
190
191 pp2_charset_t pp2_charset_create_xml(xmlNode *xml_node)
192 {
193 #if YAZ_HAVE_ICU
194     UErrorCode status = U_ZERO_ERROR;
195     struct icu_chain *chain = 0;
196     while (xml_node && xml_node->type != XML_ELEMENT_NODE)
197         xml_node = xml_node->next;
198     chain = icu_chain_xml_config(xml_node, 1, &status);
199     if (!chain || U_FAILURE(status)){
200         //xmlDocPtr icu_doc = 0;
201         //xmlChar *xmlstr = 0;
202                 //int size = 0;
203                 //xmlDocDumpMemory(icu_doc, size);
204         
205         yaz_log(YLOG_FATAL, "Could not parse ICU chain config:\n"
206                 "<%s>\n ... \n</%s>",
207                 xml_node->name, xml_node->name);
208         return 0;
209     }
210     return pp2_charset_create(chain);
211 #else // YAZ_HAVE_ICU
212     yaz_log(YLOG_FATAL, "Error: ICU support requested with element:\n"
213             "<%s>\n ... \n</%s>",
214             xml_node->name, xml_node->name);
215     yaz_log(YLOG_FATAL, 
216             "But no ICU support is compiled into the YAZ library.");
217     return 0;
218 #endif // YAZ_HAVE_ICU
219 }
220
221 pp2_charset_t pp2_charset_create_a_to_z(void)
222 {
223     pp2_charset_t pct = pp2_charset_create(0);
224     pct->token_next_handler = pp2_charset_token_a_to_z;
225     return pct;
226 }
227
228 pp2_charset_t pp2_charset_create(struct icu_chain *icu_chn)
229 {
230     pp2_charset_t pct = xmalloc(sizeof(*pct));
231
232     pct->token_next_handler = pp2_charset_token_null;
233     pct->get_sort_handler  = pp2_get_sort_ascii;
234     pct->get_display_handler  = pp2_get_display_ascii;
235 #if YAZ_HAVE_ICU
236     pct->icu_chn = 0;
237     if (icu_chn)
238     {
239         pct->icu_chn = icu_chn;
240         pct->icu_sts = U_ZERO_ERROR;
241         pct->token_next_handler = pp2_charset_token_icu;
242         pct->get_sort_handler = pp2_get_sort_icu;
243         pct->get_display_handler = pp2_get_display_icu;
244     }
245 #endif // YAZ_HAVE_ICU
246     return pct;
247 }
248
249 void pp2_charset_destroy(pp2_charset_t pct)
250 {
251 #if YAZ_HAVE_ICU
252     icu_chain_destroy(pct->icu_chn);
253 #endif
254     xfree(pct);
255 }
256
257 pp2_charset_token_t pp2_charset_token_create(pp2_charset_fact_t pft,
258                                                const char *id)
259 {
260     struct pp2_charset_entry *pce;
261     for (pce = pft->list; pce; pce = pce->next)
262         if (!strcmp(id, pce->name))
263             return pp2_charset_tokenize(pce->pct);
264     return 0;
265 }
266
267 pp2_charset_token_t pp2_charset_tokenize(pp2_charset_t pct)
268 {
269     pp2_charset_token_t prt = xmalloc(sizeof(*prt));
270
271     assert(pct);
272
273     prt->norm_str = wrbuf_alloc();
274     prt->sort_str = wrbuf_alloc();
275     prt->cp = 0;
276     prt->last_cp = 0;
277     prt->pct = pct;
278
279 #if YAZ_HAVE_ICU
280     prt->iter = 0;
281     if (pct->icu_chn)
282         prt->iter = icu_iter_create(pct->icu_chn);
283 #endif
284     return prt;
285 }
286
287 void pp2_charset_token_first(pp2_charset_token_t prt,
288                              const char *buf, int skip_article)
289
290     if (skip_article)
291     {
292         const char *p = buf;
293         char firstword[64];
294         char *pout = firstword;
295         char articles[] = "the den der die des an a "; // must end in space
296         
297         for (; *p && *p != ' ' && pout - firstword < (sizeof(firstword)-2); p++)
298             *pout++ = tolower(*(unsigned char *)p);
299         *pout++ = ' ';
300         *pout++ = '\0';
301         if (strstr(articles, firstword))
302             buf = p;
303     }
304
305     wrbuf_rewind(prt->norm_str);
306     wrbuf_rewind(prt->sort_str);
307     prt->cp = buf;
308     prt->last_cp = 0;
309
310 #if YAZ_HAVE_ICU
311     if (prt->iter)
312     {
313         icu_iter_first(prt->iter, buf);
314     }
315 #endif // YAZ_HAVE_ICU
316 }
317
318 void pp2_charset_token_destroy(pp2_charset_token_t prt)
319 {
320     assert(prt);
321 #if YAZ_HAVE_ICU
322     if (prt->iter)
323         icu_iter_destroy(prt->iter);
324 #endif
325     if(prt->norm_str) 
326         wrbuf_destroy(prt->norm_str);
327     if(prt->sort_str) 
328         wrbuf_destroy(prt->sort_str);
329     xfree(prt);
330 }
331
332 const char *pp2_charset_token_next(pp2_charset_token_t prt)
333 {
334     assert(prt);
335     return (prt->pct->token_next_handler)(prt);
336 }
337
338 const char *pp2_get_sort(pp2_charset_token_t prt)
339 {
340     return prt->pct->get_sort_handler(prt);
341 }
342
343 const char *pp2_get_display(pp2_charset_token_t prt)
344 {
345     return prt->pct->get_display_handler(prt);
346 }
347
348 #define raw_char(c) (((c) >= 'a' && (c) <= 'z') ? (c) : -1)
349 /* original tokenizer with our tokenize interface, but we
350    add +1 to ensure no '\0' are in our string (except for EOF)
351 */
352 static const char *pp2_charset_token_a_to_z(pp2_charset_token_t prt)
353 {
354     const char *cp = prt->cp;
355     int c;
356
357     /* skip white space */
358     while (*cp && (c = raw_char(tolower(*(const unsigned char *)cp))) < 0)
359         cp++;
360     if (*cp == '\0')
361     {
362         prt->cp = cp;
363         prt->last_cp = 0;
364         return 0;
365     }
366     /* now read the term itself */
367
368     prt->last_cp = cp;
369     wrbuf_rewind(prt->norm_str);
370     while (*cp && (c = raw_char(tolower(*cp))) >= 0)
371     {
372         wrbuf_putc(prt->norm_str, c);
373         cp++;
374     }
375     prt->cp = cp;
376     return wrbuf_cstr(prt->norm_str);
377 }
378
379 static const char *pp2_get_sort_ascii(pp2_charset_token_t prt)
380 {
381     if (prt->last_cp == 0)
382         return 0;
383     else
384     {
385         char *tmp = xstrdup(prt->last_cp);
386         char *result = 0;
387         result = normalize7bit_mergekey(tmp);
388         
389         wrbuf_rewind(prt->sort_str);
390         wrbuf_puts(prt->sort_str, result);
391         xfree(tmp);
392         return wrbuf_cstr(prt->sort_str);
393     }
394 }
395
396 static const char *pp2_get_display_ascii(pp2_charset_token_t prt)
397 {
398     if (prt->last_cp == 0)
399         return 0;
400     else
401     {
402         return wrbuf_cstr(prt->norm_str);
403     }
404 }
405
406 static const char *pp2_charset_token_null(pp2_charset_token_t prt)
407 {
408     const char *cp = prt->cp;
409
410     prt->last_cp = *cp ? cp : 0;
411     while (*cp)
412         cp++;
413     prt->cp = cp;
414     return prt->last_cp;
415 }
416
417 #if YAZ_HAVE_ICU
418 static const char *pp2_charset_token_icu(pp2_charset_token_t prt)
419 {
420     if (icu_iter_next(prt->iter))
421     {
422         return icu_iter_get_norm(prt->iter);
423     }
424     return 0;
425 }
426
427 static const char *pp2_get_sort_icu(pp2_charset_token_t prt)
428 {
429     return icu_iter_get_sortkey(prt->iter);
430 }
431
432 static const char *pp2_get_display_icu(pp2_charset_token_t prt)
433 {
434     return icu_iter_get_display(prt->iter);
435 }
436
437 #endif // YAZ_HAVE_ICU
438
439
440 /*
441  * Local variables:
442  * c-basic-offset: 4
443  * c-file-style: "Stroustrup"
444  * indent-tabs-mode: nil
445  * End:
446  * vim: shiftwidth=4 tabstop=8 expandtab
447  */
448