Initial commit
[profile/ivi/openjade.git] / style / LangObj.cxx
1 // Copyright (c) 1998, 1999 Matthias Clasen
2 // See the file copying.txt for copying permission.
3
4 #include "stylelib.h"
5 #include "LangObj.h"
6 #include "HashTable.h"
7 #include "CharMap.h"
8 #include "constant.h"
9 #include <math.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <ctype.h>
14 #include <string.h>
15
16 #ifdef SP_HAVE_LOCALE
17 #ifdef SP_HAVE_WCHAR
18 #include <wchar.h>
19 #include <wctype.h>
20 #include <locale.h>
21 #endif
22 #endif
23
24 #ifdef DSSSL_NAMESPACE
25 namespace DSSSL_NAMESPACE {
26 #endif
27
28 #ifdef SP_HAVE_LOCALE
29 #ifdef SP_HAVE_WCHAR
30 # ifndef __GLIBC__
31 static char *strdup(const char *s)
32 {
33   size_t l = strlen(s) + 1;
34   return (char *) memcpy ((char *) malloc (sizeof (char) * l), s, l);
35 }
36 # endif /* __GLIBC__ */
37
38 static char *stringify(const StringC &s)
39 {
40   char *r = (char *) malloc (sizeof(char)*(s.size() + 1));
41   for (size_t i = 0; i < s.size(); i++) 
42     r[i] = char(s[i]);
43   r[s.size()] = 0;
44   return r;
45 }
46
47 // FIXME this is unneeded if SP_WCHAR_IS_USHORT
48 static wchar_t *wchartify(const StringC &s)
49 {
50   wchar_t *r = (wchar_t *) malloc (sizeof(wchar_t)*(s.size() + 1));
51   for (size_t i = 0; i < s.size(); i++) 
52     r[i] = wchar_t(s[i]);
53   r[s.size()] = 0;
54   return r;
55 }
56
57 char *RefLangObj::localeName(const StringC &lang, const StringC &country)
58 {
59   char *p;
60
61   p = (char *) malloc (sizeof(char)*(lang.size() + country.size() + 2));
62   size_t i;
63   for (i = 0; i < lang.size(); i++)
64     p[i] = tolower(char(lang[i]));
65   p[i++] = '_';
66   for (size_t j = 0; j < country.size(); j++, i++)
67     p[i] = toupper(char(country[j]));
68   p[i] = 0;
69   return p;
70 }
71
72 bool RefLangObj::supportedLanguage(const StringC &lang, const StringC &country)
73 {
74   char *p = RefLangObj::localeName(lang, country);
75   char *old = strdup(setlocale(LC_ALL, 0));
76   bool res = (setlocale(LC_ALL, p) != 0);
77   setlocale(LC_ALL, old);
78   free (p); free (old);
79   return res;
80 }
81
82 RefLangObj::RefLangObj(const StringC &lang, const StringC &country) 
83 {
84   char *p = localeName(lang, country);
85   oldLocale_ = strdup(setlocale(LC_ALL, 0));  
86   newLocale_ = strdup(setlocale(LC_ALL, p));
87   free (p);
88 }
89
90 RefLangObj::~RefLangObj() 
91 {
92   free (oldLocale_);
93   free (newLocale_);
94 }
95
96 LanguageObj *RefLangObj::asLanguage()
97 {
98   return this;
99 }
100
101 Char RefLangObj::toUpper(const Char c) const
102 {
103   setlocale(LC_ALL, newLocale_);  
104   Char uc = towupper(c);
105   setlocale(LC_ALL, oldLocale_);  
106   return uc;
107 }
108
109 Char RefLangObj::toLower(const Char c) const
110 {
111   setlocale(LC_ALL, newLocale_);  
112   Char lc = towlower(c);
113   setlocale(LC_ALL, oldLocale_);  
114   return lc;
115 }
116
117 bool RefLangObj::areEquivalent(const StringC &r, const StringC &s, 
118                                const Char l) const
119 {
120   setlocale(LC_ALL, newLocale_);  
121   wchar_t *rr = wchartify(r);
122   unsigned rn = wcsxfrm (0, rr, 0);
123   wchar_t *rx = (wchar_t *) malloc (sizeof(wchar_t)*rn); 
124   wcsxfrm(rx, rr, rn);
125   wchar_t *ss = wchartify(s);
126   unsigned sn = wcsxfrm (0, ss, 0);
127   wchar_t *sx = (wchar_t *) malloc (sizeof(wchar_t)*sn);
128   wcsxfrm(sx, ss, sn);
129   bool res;
130   unsigned k = 0;
131   for (unsigned i = 0; ; i++) {
132     if (rx[i] != sx[i]) { res = 0; break; } 
133     if (rx[i] == 1) k++;
134     if (k == l || rx[i] == 0) { res = 1; break; } 
135   }
136   free (rr); free (ss); free (rx); free (sx); 
137   setlocale(LC_ALL, oldLocale_);  
138   return res;
139 }
140
141 bool RefLangObj::isLess(const StringC &r, const StringC &s) const
142 {
143   setlocale(LC_ALL, newLocale_);  
144   wchar_t *rr = wchartify(r);
145   wchar_t *ss = wchartify(s);
146   int res = wcscoll(rr, ss);
147   free (rr); free (ss);
148   setlocale(LC_ALL, oldLocale_);  
149   return (res < 0);
150 }
151
152 bool RefLangObj::isLessOrEqual(const StringC &r, const StringC &s) const
153 {
154   setlocale(LC_ALL, newLocale_);  
155   wchar_t *rr = wchartify(r);
156   wchar_t *ss = wchartify(s);
157   int res = wcscoll(rr, ss);
158   free (rr); free (ss);
159   setlocale(LC_ALL, oldLocale_);  
160   return (res <= 0);
161 }
162 #endif /* SP_HAVE_WCHAR */
163 #endif /* SP_HAVE_LOCALE */
164
165 class LangBuildData {
166 public:
167   LangBuildData() : currentpos(0) {};
168   HashTable<StringC, StringC> order;
169   Char currentpos;
170   HashTable<StringC, StringC> ce; 
171   HashTable<StringC, Char> syms;  
172 };
173   
174   class LangData {
175   public:
176     LangData();
177     LangObj::LevelSort level[20]; // FIXME  
178     Char levels;
179     HashTable<StringC, StringC> weights;   
180     HashTable<StringC, Char> collate; 
181     CharMap<Char> toupper;
182     CharMap<Char> tolower;
183   };
184
185 LangData::LangData() 
186 {
187   toupper.setAll(charMax);
188   tolower.setAll(charMax);
189   levels = 0;
190 }
191
192 void LangObj::addMultiCollatingElement(const StringC &sym, 
193                                            const StringC &str)
194 {
195   buildData_->ce.insert(sym, str); 
196 }
197
198 void LangObj::addCollatingSymbol(const StringC &sym)
199 {
200   buildData_->syms.insert(sym, charMax); 
201 }
202
203 void LangObj::addLevel(const LevelSort &sort) 
204 {
205   data_->level[data_->levels++] = sort;
206 }
207
208 void LangObj::addDefaultPos()
209 {
210   StringC empty;
211   addCollatingPos(empty);
212 }
213
214 bool LangObj::addCollatingPos(const StringC &sym)
215 {
216   if (!buildData_->ce.lookup(sym) && !buildData_->syms.lookup(sym)) 
217     if (sym.size() <= 1) 
218       buildData_->ce.insert(sym, sym);
219     else 
220       return 0;
221   buildData_->order.insert(StringC(&buildData_->currentpos, 1), sym);
222   buildData_->currentpos++;
223   return 1;
224 }
225
226 bool LangObj::addLevelWeight(const Char l, const StringC &w)
227 {
228   if (!buildData_->ce.lookup(w) && !buildData_->syms.lookup(w)) 
229     if (w.size() <= 1) 
230       buildData_->ce.insert(w, w); 
231     else 
232       return 0;
233   StringC key;
234   key.resize(3);
235   key[0] = buildData_->currentpos - 1;
236   key[1] = l;
237   for (key[2] = 0; buildData_->order.lookup(key); key[2]++) ; 
238   buildData_->order.insert(key, w);
239   return 1;
240 }
241
242 void LangObj::addToupper(const Char lc, const Char uc)
243 {
244   data_->toupper.setChar(lc, uc);
245 }
246
247 void LangObj::addTolower(const Char uc, const Char lc)
248 {
249   data_->tolower.setChar(uc, lc);
250 }
251
252 LangObj::LangObj() 
253 {
254   buildData_ = new LangBuildData;
255   data_ = new LangData;
256 }
257
258 LangObj::~LangObj()
259 {
260   if (buildData_) delete buildData_;
261   if (data_) delete data_;
262 }
263
264 bool LangObj::compile()
265 {
266   const Char *col;
267   StringC key, val, data;
268   StringC empty;
269   const StringC *match, *match2;
270
271   data_->collate.insert(empty, buildData_->currentpos); 
272   key.resize(1);
273   for (key[0] = 0; key[0] < buildData_->currentpos; key[0]++) {
274     match = buildData_->order.lookup(key);
275     if (match == 0) 
276       return 0;
277     match2 = buildData_->ce.lookup(*match);
278     if (match2 == 0) 
279       buildData_->syms.insert(*match, key[0]);
280     else  
281       data_->collate.insert(*match2, key[0]);
282   }
283   key.resize(2);
284   data.resize(3);
285   for (data[0] = 0; data[0] < buildData_->currentpos; data[0]++) {
286     key[0] = data[0];
287     for (data[1] = 0; data[1] < levels(); data[1]++) {
288       key[1] = data[1];
289       val.resize(0);
290       for (data[2] = 0; buildData_->order.lookup(data); data[2]++) {
291         match = buildData_->order.lookup(data);
292         if (match == 0) 
293           return 0;
294         match2 = buildData_->ce.lookup(*match);
295         if (match2 == 0) 
296           col = buildData_->syms.lookup(*match);
297         else 
298           col = data_->collate.lookup(*match2);
299         if (col == 0)  
300           return 0;
301         val += *col;
302       }
303       data_->weights.insert(key, val);
304      }
305   }
306   delete buildData_;
307   buildData_ = 0;
308   return 1;
309 }
310
311 LanguageObj *LangObj::asLanguage()
312 {
313   return this;
314 }
315
316 bool LangObj::areEquivalent(const StringC &r, const StringC &s,
317                                 const Char k) const
318 {
319   return (compare(r, s, k) == 0);
320 }
321
322 int LangObj::compare(const StringC &rr, const StringC &ss, 
323                          const Char k) const
324 {
325   StringC rc = asCollatingElts(rr);
326   StringC sc = asCollatingElts(ss);
327  
328   for (Char l = 0; (l < k) && (l < levels()); l++) {
329     StringC r = atLevel(rc, l);
330     StringC s = atLevel(sc, l);
331     for (size_t i = 0; (i < r.size()) || (i < s.size()); i++) {
332       if (i == r.size()) return -1;
333       if (i == s.size()) return 1;
334       if (r[i] < s[i]) return -1;
335       if (r[i] > s[i]) return 1;
336     }
337   }
338   return 0;
339 }
340
341 StringC LangObj::asCollatingElts(const StringC &s) const
342 {
343   StringC res, key, empty;
344   unsigned i, j;
345   int col;
346   const Char *c, *def;
347   Char def_val;
348
349   def = data_->collate.lookup(empty); 
350   def_val = (def == 0) ? charMax : *def;  
351   for (i = 0; i < s.size(); i = j) {
352     col = def_val;
353     key = empty;
354     for (j = i; j < s.size(); j++) {
355       key += s[j]; 
356       c = data_->collate.lookup(key);
357       if (c == 0) 
358         break;
359       col = *c;
360     }
361     if (i == j) 
362       // if we get here, s[j] is a single `unknown' char and 
363       // we better not reconsider it to avoid an infinite loop. 
364       j++;
365     res += col;
366   }
367   return res;
368 }
369
370 StringC LangObj::atLevel(const StringC &s, const Char l) const
371 {
372   StringC cols, res, key;
373   const StringC *w;
374
375   if (data_->level[l].backward) 
376     for (int i = s.size() - 1; i >= 0; i--) 
377       cols += s[i];
378   else 
379     cols = s;
380   key.resize(2);
381   key[1] = l;
382   for (size_t i = 0; i < cols.size(); i++) {
383     key[0] = cols[i];
384     w = data_->weights.lookup(key);
385     if (w == 0) 
386       return res;
387     if (data_->level[l].backward) 
388       for (int j = w->size() - 1; j >= 0; j--) {
389         if (data_->level[l].position) 
390           res += i; 
391         res += (*w)[j]; 
392       }
393     else  // forward
394       for (int j = 0; j < w->size(); j++) {
395         if (data_->level[l].position) 
396           res += i; 
397         res += (*w)[j]; 
398       }
399   }
400   return res;
401 }
402
403 Char LangObj::toUpper(const Char c) const
404 {
405   Char uc = data_->toupper[c];
406   return (uc == charMax) ? c : uc;
407 }
408
409 Char LangObj::toLower(const Char c) const
410 {
411   Char lc = data_->tolower[c];
412   return (lc == charMax) ? c : lc;
413 }
414
415 unsigned LangObj::levels() const
416 {
417   return data_->levels;
418 }
419
420 bool LangObj::isLess(const StringC &r, const StringC &s) const
421 {
422   return (compare(r, s, levels()) < 0);
423 }
424
425 bool LangObj::isLessOrEqual(const StringC &r, const StringC &s) const
426 {
427   return (compare(r, s, levels()) <= 0);
428 }
429
430 #ifdef DSSSL_NAMESPACE
431 }
432 #endif
433
434 /*
435
436 Notes on the LangObj class:
437
438 All data is in the classes LanguageData and 
439 LanguageBuildData. The LanguageBuildData is only
440 needed until we call LanguageObj::compile() and
441 is freed afterwards.
442
443 Once a LangObj is built, we use the functions isLess(), 
444 isLessOrEqual(), toUpper(), toLower() and areEquivalent()
445 to implement the language-dependent functions of the
446 expression language. 
447
448 The LangBuildData stores a map symbols --> strings
449 for multi-collating-elements (ce) and a second map
450 positions --> symbols (order). These are incrementally 
451 filled up while parsing a define-language expression.
452 order also stores a map 
453
454   positions x levels x weight-numbers --> weights 
455
456 (there may be more than one weight for a given position 
457 and level). The empty string is used as key for the 
458 default entry in syms.
459
460 Compilation is done in two phases: In the first phase,
461 we build the syms and collate maps (syms maps
462 symbols --> positions and collate maps 
463 multi-collating-elements --> positions). This is achived
464 by inverting the corresponding part of order.
465
466 In the next phase, the weights map is built. It maps
467 positions x levels x weight-numbers --> positions.
468
469 Comparing two string works in three phases: First, 
470 the strings are converted to positions (done by 
471 asCollatingElts()), then we are comparing one level
472 at a time. For this, the strings of positions are
473 converted into strings of weights. How this is done
474 depends on the level.
475 Finally the strings of weights are lexicographically
476 compared.
477
478 Notes on the RefLangObj class:
479
480 It uses the underlying POSIX locale system and wchar_t. At least
481 with GNU libc, wchar_t is always encoded as ISO-10646 UCS4, thus 
482 the Char and wchar_t codes of a character have the same value. But
483 since sizeof(wchar_t) = 4 != 2 = sizeof(Char), we have to do ugly 
484 conversions (at least on i386-linux-glibc2) to be able to use the 
485 libc wide character routines wcscoll() and wcsxfrm(). 
486
487 To create a language by reference to a locale, use the external
488 procedure with pubid UNREGISTERED::OpenJade//Procedure::language 
489
490 (language lang country)
491
492 where lang and country are symbols or strings like the ones used 
493 for the language: and country: characteristics of the paragraph FO. 
494 If the locale is not supported, language returns #f. If Jade is 
495 compiled without locale support (!SP_HAVE_LOCALE) language *always* 
496 returns #f.
497
498 The implementation of string-equiv? depends on the fact that
499 wcsxfrm() returns a 0-terminated string of integers where the 
500 substrings for each level are separated by 1. This is true for
501 glibc, but I don't know if it is universally true.
502
503 */