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