]> git.decadent.org.uk Git - ion3.git/blob - ioncore/strings.c
37487fd0216f17872c034ff889eca140110d924f
[ion3.git] / ioncore / strings.c
1 /*
2  * ion/ioncore/strings.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <libtu/output.h>
13 #include <libtu/misc.h>
14 #include <string.h>
15 #include <regex.h>
16 #include "common.h"
17 #include "global.h"
18 #include "strings.h"
19
20
21 /*{{{ String scanning */
22
23
24 wchar_t str_wchar_at(char *p, int max)
25 {
26     wchar_t wc;
27     if(mbtowc(&wc, p, max)>0)
28         return wc;
29     return 0;
30 }
31     
32
33 char *str_stripws(char *p)
34 {
35     mbstate_t ps;
36     wchar_t wc;
37     int first=-1, pos=0;
38     int n=strlen(p);
39     int ret;
40     
41     memset(&ps, 0, sizeof(ps));
42     
43     while(1){
44         ret=mbrtowc(&wc, p+pos, n-pos, &ps);
45         if(ret<=0)
46             break;
47         if(!iswspace(wc))
48             break;
49         pos+=ret;
50     }
51     
52     if(pos!=0)
53         memmove(p, p+pos, n-pos+1);
54     
55     if(ret<=0)
56         return p;
57     
58     pos=ret;
59     
60     while(1){
61         ret=mbrtowc(&wc, p+pos, n-pos, &ps);
62         if(ret<=0)
63             break;
64         if(iswspace(wc)){
65             if(first==-1)
66                 first=pos;
67         }else{
68             first=-1;
69         }
70         pos+=ret;
71     }
72         
73     if(first!=-1)
74         p[first]='\0';
75     
76     return p;
77 }
78
79
80 int str_prevoff(const char *p, int pos)
81 {
82     if(ioncore_g.enc_sb)
83         return (pos>0 ? 1 : 0);
84
85     if(ioncore_g.enc_utf8){
86         int opos=pos;
87         
88         while(pos>0){
89             pos--;
90             if((p[pos]&0xC0)!=0x80)
91                 break;
92         }
93         return opos-pos;
94     }
95     
96     assert(ioncore_g.use_mb);
97     {
98         /* *sigh* */
99         int l, prev=0;
100         mbstate_t ps;
101         
102         memset(&ps, 0, sizeof(ps));
103         
104         while(1){
105             l=mbrlen(p+prev, pos-prev, &ps);
106             if(l<0){
107                 warn(TR("Invalid multibyte string."));
108                 return 0;
109             }
110             if(prev+l>=pos)
111                 return pos-prev;
112             prev+=l;
113         }
114         
115     }
116 }
117
118
119 int str_nextoff(const char *p, int opos)
120 {
121     if(ioncore_g.enc_sb)
122         return (*(p+opos)=='\0' ? 0 : 1);
123
124     if(ioncore_g.enc_utf8){
125         int pos=opos;
126         
127         while(p[pos]){
128             pos++;
129             if((p[pos]&0xC0)!=0x80)
130                 break;
131         }
132         return pos-opos;
133     }
134
135     assert(ioncore_g.use_mb);
136     {
137         mbstate_t ps;
138         int l;
139         memset(&ps, 0, sizeof(ps));
140         
141         l=mbrlen(p+opos, strlen(p+opos), &ps);
142         if(l<0){
143             warn(TR("Invalid multibyte string."));
144             return 0;
145         }
146         return l;
147     }
148 }
149
150
151 int str_len(const char *p)
152 {
153     if(ioncore_g.enc_sb)
154         return strlen(p);
155
156     if(ioncore_g.enc_utf8){
157         int len=0;
158         
159         while(*p){
160             if(((*p)&0xC0)!=0x80)
161                 len++;
162             p++;
163         }
164         return len;
165     }
166
167     assert(ioncore_g.use_mb);
168     {
169         mbstate_t ps;
170         int len=0, bytes=strlen(p), l;
171         memset(&ps, 0, sizeof(ps));
172         
173         while(bytes>0){
174             l=mbrlen(p, bytes, &ps);
175             if(l<=0){
176                 warn(TR("Invalid multibyte string."));
177                 break;
178             }
179             len++;
180             bytes-=l;
181         }
182         return len;
183     }
184     
185 }
186
187 /*}}}*/
188
189
190 /*{{{ Title shortening */
191
192
193 static char *scatn3(const char *p1, int l1,
194                     const char *p2, int l2,
195                     const char *p3, int l3)
196 {
197     char *p=ALLOC_N(char, l1+l2+l3+1);
198     
199     if(p!=NULL){
200         strncat(p, p1, l1);
201         strncat(p, p2, l2);
202         strncat(p, p3, l3);
203     }
204     return p;
205 }
206
207 INTRSTRUCT(SR);
208     
209 DECLSTRUCT(SR){
210     regex_t re;
211     char *rule;
212     SR *next, *prev;
213     bool always;
214 };
215
216
217 static SR *shortenrules=NULL;
218
219
220 /*EXTL_DOC
221  * Add a rule describing how too long titles should be shortened to fit in tabs.
222  * The regular expression \var{rx} (POSIX, not Lua!) is used to match titles
223  * and when \var{rx} matches, \var{rule} is attempted to use as a replacement
224  * for title. If \var{always} is set, the rule is used even if no shortening 
225  * is necessary.
226  *
227  * Similarly to sed's 's' command, \var{rule} may contain characters that are
228  * inserted in the resulting string and specials as follows:
229  * 
230  * \begin{tabularx}{\linewidth}{lX}
231  *  \tabhead{Special & Description}
232  *  \$0 &          Place the original string here. \\
233  *  \$1 to \$9 & Insert n:th capture here (as usual,captures are surrounded
234  *                 by parentheses in the regex). \\
235  *  \$| &          Alternative shortening separator. The shortening described
236  *                 before the first this kind of separator is tried first and
237  *                 if it fails to make the string short enough, the next is 
238  *                  tried, and so on. \\
239  *  \$< &         Remove characters on the left of this marker to shorten the
240  *                 string. \\
241  *  \$> &         Remove characters on the right of this marker to shorten the
242  *                 string. Only the first \$< or \$> within an alternative 
243  *                 shortening is used. \\
244  * \end{tabularx}
245  */
246 EXTL_EXPORT
247 bool ioncore_defshortening(const char *rx, const char *rule, bool always)
248 {
249     SR *si;
250     int ret;
251     #define ERRBUF_SIZE 256
252     static char errbuf[ERRBUF_SIZE];
253         
254     if(rx==NULL || rule==NULL)
255         return FALSE;
256     
257     si=ALLOC(SR);
258
259     if(si==NULL)
260         return FALSE;
261     
262     ret=regcomp(&(si->re), rx, REG_EXTENDED);
263     
264     if(ret!=0){
265         errbuf[0]='\0';
266         regerror(ret, &(si->re), errbuf, ERRBUF_SIZE);
267         warn(TR("Error compiling regular expression: %s"), errbuf);
268         goto fail2;
269     }
270     
271     si->rule=scopy(rule);
272     si->always=always;
273     
274     if(si->rule==NULL)
275         goto fail;
276     
277     LINK_ITEM(shortenrules, si, next, prev);
278     
279     return TRUE;
280     
281 fail:
282     regfree(&(si->re));
283 fail2:
284     free(si);
285     return FALSE;
286 }
287
288
289 static char *shorten(GrBrush *brush, const char *str, uint maxw,
290                      const char *rule, int nmatch, regmatch_t *pmatch)
291 {
292     char *s;
293     int rulelen, slen, i, j, k, ll;
294     int strippt=0;
295     int stripdir=-1;
296     bool more=FALSE;
297     
298     /* Ensure matches are at character boundaries */
299     if(!ioncore_g.enc_sb){
300         int pos=0, len, strl;
301         mbstate_t ps;
302         memset(&ps, 0, sizeof(ps));
303         
304         strl=strlen(str);
305
306         while(pos<strl){
307             len=mbrtowc(NULL, str+pos, strl-pos, &ps);
308             if(len<0){
309                 /* Invalid multibyte string */
310                 return scopy("???");
311             }
312             if(len==0)
313                 break;
314             for(i=0; i<nmatch; i++){
315                 if(pmatch[i].rm_so>pos && pmatch[i].rm_so<pos+len)
316                     pmatch[i].rm_so=pos+len;
317                 if(pmatch[i].rm_eo>pos && pmatch[i].rm_eo<pos+len)
318                     pmatch[i].rm_eo=pos;
319             }
320             pos+=len;
321         }
322     }
323
324     /* Stupid alloc rule that wastes space */
325     rulelen=strlen(rule);
326     slen=rulelen;
327     
328     for(i=0; i<nmatch; i++){
329         if(pmatch[i].rm_so==-1)
330             continue;
331         slen+=(pmatch[i].rm_eo-pmatch[i].rm_so);
332     }
333     
334     s=ALLOC_N(char, slen);
335     
336     if(s==NULL)
337         return NULL;
338     
339     do{
340         more=FALSE;
341         j=0;
342         strippt=0;
343         stripdir=-1;
344         
345         for(i=0; i<rulelen; i++){
346             if(rule[i]!='$'){
347                 s[j++]=rule[i];
348                 continue;
349             }
350             
351             i++;
352             
353             if(rule[i]=='|'){
354                 rule=rule+i+1;
355                 rulelen=rulelen-i-1;
356                 more=TRUE;
357                 break;
358             }
359             
360             if(rule[i]=='$'){
361                 s[j++]='$';
362                 continue;
363             }
364             
365             if(rule[i]=='<'){
366                 strippt=j;
367                 stripdir=-1;
368                 continue;
369             }
370             
371             if(rule[i]=='>'){
372                 strippt=j;
373                 stripdir=1;
374                 continue;
375             }
376             
377             if(rule[i]>='0' && rule[i]<='9'){
378                 k=(int)(rule[i]-'0');
379                 if(k>=nmatch)
380                     continue;
381                 if(pmatch[k].rm_so==-1)
382                     continue;
383                 ll=(pmatch[k].rm_eo-pmatch[k].rm_so);
384                 strncpy(s+j, str+pmatch[k].rm_so, ll);
385                 j+=ll;
386             }
387         }
388         
389         slen=j;
390         s[slen]='\0';
391         
392         i=strippt;
393         j=strippt;
394         
395         /* shorten */
396         {
397             uint bl=grbrush_get_text_width(brush, s, i);
398             uint el=grbrush_get_text_width(brush, s+j, slen-j);
399
400             while(1){
401                 /* el+bl may not be the actual length, but close enough. */
402                 if(el+bl<=maxw){
403                     memmove(s+i, s+j, slen-j+1);
404                     return s;
405                 }
406                 
407                 if(stripdir==-1){
408                     ll=str_prevoff(s, i);
409                     if(ll==0)
410                         break;
411                     i-=ll;
412                     bl=grbrush_get_text_width(brush, s, i);
413                 }else{
414                     ll=str_nextoff(s, j);
415                     if(ll==0)
416                         break;
417                     j+=ll;
418                     el=grbrush_get_text_width(brush, s+j, slen-j);
419                 }
420             }
421         }
422     }while(more);
423         
424     free(s);
425
426     return NULL;
427 }
428
429
430 char *grbrush_make_label(GrBrush *brush, const char *str, uint maxw)
431 {
432     size_t nmatch=10;
433     regmatch_t pmatch[10];
434     SR *rule;
435     int ret;
436     char *retstr;
437     bool fits=FALSE;
438     
439     if(grbrush_get_text_width(brush, str, strlen(str))<=maxw)
440         fits=TRUE;
441         
442         /*return scopy(str);*/
443     
444     for(rule=shortenrules; rule!=NULL; rule=rule->next){
445         if(fits && !rule->always)
446             continue;
447         ret=regexec(&(rule->re), str, nmatch, pmatch, 0);
448         if(ret!=0)
449             continue;
450         retstr=shorten(brush, str, maxw, rule->rule, nmatch, pmatch);
451         goto rettest;
452     }
453
454     if(fits){
455         retstr=scopy(str);
456     }else{
457         pmatch[0].rm_so=0;
458         pmatch[0].rm_eo=strlen(str)-1;
459         retstr=shorten(brush, str, maxw, "$1$<...", 1, pmatch);
460     }
461     
462 rettest:
463     if(retstr!=NULL)
464         return retstr;
465     return scopy("");
466 }
467
468
469 /*}}}*/
470