Refactor PP2 charsets handling, use pazpar2_mutex.
[pazpar2-moved-to-github.git] / src / charsets.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2010 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 /* charset handle */
44 struct pp2_charset_s {
45     const char *(*token_next_handler)(pp2_relevance_token_t prt);
46     const char *(*get_sort_handler)(pp2_relevance_token_t prt);
47     int ref_count;
48 #if YAZ_HAVE_ICU
49     struct icu_chain * icu_chn;
50     UErrorCode icu_sts;
51 #endif
52 };
53
54 static const char *pp2_relevance_token_a_to_z(pp2_relevance_token_t prt);
55 static const char *pp2_get_sort_ascii(pp2_relevance_token_t prt);
56
57 #if YAZ_HAVE_ICU
58 static const char *pp2_relevance_token_icu(pp2_relevance_token_t prt);
59 static const char *pp2_get_sort_icu(pp2_relevance_token_t prt);
60 #endif
61
62 /* tokenzier handle */
63 struct pp2_relevance_token_s {
64     const char *cp;     /* unnormalized buffer we're tokenizing */
65     const char *last_cp;  /* pointer to last token we're dealing with */
66     pp2_charset_t pct;  /* our main charset handle (type+config) */
67     WRBUF norm_str;     /* normized string we return (temporarily) */
68     WRBUF sort_str;     /* sort string we return (temporarily) */
69     yaz_icu_iter_t iter;
70 };
71
72
73 pp2_charset_t pp2_charset_create_xml(xmlNode *xml_node)
74 {
75 #if YAZ_HAVE_ICU
76     UErrorCode status = U_ZERO_ERROR;
77     struct icu_chain *chain = 0;
78     if (xml_node)
79         xml_node = xml_node->children;
80     while (xml_node && xml_node->type != XML_ELEMENT_NODE)
81         xml_node = xml_node->next;
82     chain = icu_chain_xml_config(xml_node, 1, &status);
83     if (!chain || U_FAILURE(status)){
84         //xmlDocPtr icu_doc = 0;
85         //xmlChar *xmlstr = 0;
86                 //int size = 0;
87                 //xmlDocDumpMemory(icu_doc, size);
88         
89         yaz_log(YLOG_FATAL, "Could not parse ICU chain config:\n"
90                 "<%s>\n ... \n</%s>",
91                 xml_node->name, xml_node->name);
92         return 0;
93     }
94     return pp2_charset_create(chain);
95 #else // YAZ_HAVE_ICU
96     yaz_log(YLOG_FATAL, "Error: ICU support requested with element:\n"
97             "<%s>\n ... \n</%s>",
98             xml_node->name, xml_node->name);
99     yaz_log(YLOG_FATAL, 
100             "But no ICU support is compiled into the YAZ library.");
101     return 0;
102 #endif // YAZ_HAVE_ICU
103 }
104
105 void pp2_charset_incref(pp2_charset_t pct)
106 {
107     (pct->ref_count)++;
108 }
109
110 pp2_charset_t pp2_charset_create(struct icu_chain * icu_chn)
111 {
112     pp2_charset_t pct = xmalloc(sizeof(*pct));
113
114     pct->token_next_handler = pp2_relevance_token_a_to_z;
115     pct->get_sort_handler  = pp2_get_sort_ascii;
116     pct->ref_count = 1;
117 #if YAZ_HAVE_ICU
118     pct->icu_chn = 0;
119     if (icu_chn)
120     {
121         pct->icu_chn = icu_chn;
122         pct->icu_sts = U_ZERO_ERROR;
123         pct->token_next_handler = pp2_relevance_token_icu;
124         pct->get_sort_handler = pp2_get_sort_icu;
125     }
126 #endif // YAZ_HAVE_ICU
127     return pct;
128 }
129
130 void pp2_charset_destroy(pp2_charset_t pct)
131 {
132     if (pct)
133     {
134         assert(pct->ref_count >= 1);
135         --(pct->ref_count);
136         if (pct->ref_count == 0)
137         {
138 #if YAZ_HAVE_ICU
139             icu_chain_destroy(pct->icu_chn);
140 #endif
141             xfree(pct);
142         }
143     }
144 }
145
146 pp2_relevance_token_t pp2_relevance_tokenize(pp2_charset_t pct)
147 {
148     pp2_relevance_token_t prt = xmalloc(sizeof(*prt));
149
150     assert(pct);
151
152     prt->norm_str = wrbuf_alloc();
153     prt->sort_str = wrbuf_alloc();
154     prt->cp = 0;
155     prt->last_cp = 0;
156     prt->pct = pct;
157
158 #if YAZ_HAVE_ICU
159     prt->iter = 0;
160     if (pct->icu_chn)
161         prt->iter = icu_iter_create(pct->icu_chn);
162 #endif
163     return prt;
164 }
165
166 void pp2_relevance_first(pp2_relevance_token_t prt,
167                          const char *buf,
168                          int skip_article)
169
170     if (skip_article)
171     {
172         const char *p = buf;
173         char firstword[64];
174         char *pout = firstword;
175         char articles[] = "the den der die des an a "; // must end in space
176         
177         while (*p && !isalnum(*(unsigned char *)p))
178             p++;
179         for (; *p && *p != ' ' && pout - firstword < (sizeof(firstword)-2); p++)
180             *pout++ = tolower(*(unsigned char *)p);
181         *pout++ = ' ';
182         *pout++ = '\0';
183         if (strstr(articles, firstword))
184             buf = p;
185     }
186
187     wrbuf_rewind(prt->norm_str);
188     wrbuf_rewind(prt->sort_str);
189     prt->cp = buf;
190     prt->last_cp = 0;
191
192 #if YAZ_HAVE_ICU
193     if (prt->iter)
194     {
195         icu_iter_first(prt->iter, buf);
196     }
197 #endif // YAZ_HAVE_ICU
198 }
199
200 void pp2_relevance_token_destroy(pp2_relevance_token_t prt)
201 {
202     assert(prt);
203 #if YAZ_HAVE_ICU
204     if (prt->iter)
205         icu_iter_destroy(prt->iter);
206 #endif
207     if(prt->norm_str) 
208         wrbuf_destroy(prt->norm_str);
209     if(prt->sort_str) 
210         wrbuf_destroy(prt->sort_str);
211     xfree(prt);
212 }
213
214 const char *pp2_relevance_token_next(pp2_relevance_token_t prt)
215 {
216     assert(prt);
217     return (prt->pct->token_next_handler)(prt);
218 }
219
220 const char *pp2_get_sort(pp2_relevance_token_t prt)
221 {
222     return prt->pct->get_sort_handler(prt);
223 }
224
225 #define raw_char(c) (((c) >= 'a' && (c) <= 'z') ? (c) : -1)
226 /* original tokenizer with our tokenize interface, but we
227    add +1 to ensure no '\0' are in our string (except for EOF)
228 */
229 static const char *pp2_relevance_token_a_to_z(pp2_relevance_token_t prt)
230 {
231     const char *cp = prt->cp;
232     int c;
233
234     /* skip white space */
235     while (*cp && (c = raw_char(tolower(*(const unsigned char *)cp))) < 0)
236         cp++;
237     if (*cp == '\0')
238     {
239         prt->cp = cp;
240         prt->last_cp = 0;
241         return 0;
242     }
243     /* now read the term itself */
244
245     prt->last_cp = cp;
246     wrbuf_rewind(prt->norm_str);
247     while (*cp && (c = raw_char(tolower(*cp))) >= 0)
248     {
249         wrbuf_putc(prt->norm_str, c);
250         cp++;
251     }
252     prt->cp = cp;
253     return wrbuf_cstr(prt->norm_str);
254 }
255
256 static const char *pp2_get_sort_ascii(pp2_relevance_token_t prt)
257 {
258     if (prt->last_cp == 0)
259         return 0;
260     else
261     {
262         char *tmp = xstrdup(prt->last_cp);
263         char *result = 0;
264         result = normalize7bit_mergekey(tmp);
265         
266         wrbuf_rewind(prt->sort_str);
267         wrbuf_puts(prt->sort_str, result);
268         xfree(tmp);
269         return wrbuf_cstr(prt->sort_str);
270     }
271 }
272
273
274 #if YAZ_HAVE_ICU
275 static const char *pp2_relevance_token_icu(pp2_relevance_token_t prt)
276 {
277     if (icu_iter_next(prt->iter))
278     {
279         return icu_iter_get_norm(prt->iter);
280     }
281     return 0;
282 }
283
284 static const char *pp2_get_sort_icu(pp2_relevance_token_t prt)
285 {
286     return icu_iter_get_sortkey(prt->iter);
287 }
288
289 #endif // YAZ_HAVE_ICU
290
291
292 /*
293  * Local variables:
294  * c-basic-offset: 4
295  * c-file-style: "Stroustrup"
296  * indent-tabs-mode: nil
297  * End:
298  * vim: shiftwidth=4 tabstop=8 expandtab
299  */
300