rules: don't use custom logging functions
[platform/upstream/libxkbcommon.git] / src / xkbcomp / rules.c
1 /************************************************************
2  Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
3
4  Permission to use, copy, modify, and distribute this
5  software and its documentation for any purpose and without
6  fee is hereby granted, provided that the above copyright
7  notice appear in all copies and that both that copyright
8  notice and this permission notice appear in supporting
9  documentation, and that the name of Silicon Graphics not be
10  used in advertising or publicity pertaining to distribution
11  of the software without specific prior written permission.
12  Silicon Graphics makes no representation about the suitability
13  of this software for any purpose. It is provided "as is"
14  without any express or implied warranty.
15
16  SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17  SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18  AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
19  GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
20  DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21  DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
22  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
23  THE USE OR PERFORMANCE OF THIS SOFTWARE.
24
25  ********************************************************/
26
27 #include <stdio.h>
28 #include <ctype.h>
29
30 #include "rules.h"
31 #include "path.h"
32
33 #define DFLT_LINE_SIZE  128
34
35 typedef struct {
36         size_t  sz_line;
37         int     num_line;
38         char    buf[DFLT_LINE_SIZE];
39         char *  line;
40 } InputLine;
41
42 static void
43 InitInputLine(InputLine *line)
44 {
45     line->num_line= 0;
46     line->sz_line= DFLT_LINE_SIZE;
47     line->line= line->buf;
48 }
49
50 static void
51 FreeInputLine(InputLine *line)
52 {
53     if (line->line!=line->buf)
54         free(line->line);
55     line->num_line= 0;
56     line->sz_line= DFLT_LINE_SIZE;
57     line->line= line->buf;
58 }
59
60 static int
61 InputLineAddChar(InputLine *line,int ch)
62 {
63     if (line->num_line>=line->sz_line) {
64         if (line->line==line->buf) {
65             line->line = malloc(line->sz_line * 2);
66             memcpy(line->line,line->buf,line->sz_line);
67         }
68         else {
69             line->line = realloc(line->line, line->sz_line * 2);
70         }
71         line->sz_line*= 2;
72     }
73     line->line[line->num_line++]= ch;
74     return ch;
75 }
76
77 #define ADD_CHAR(l,c)   ((l)->num_line<(l)->sz_line?\
78                                 (int)((l)->line[(l)->num_line++]= (c)):\
79                                 InputLineAddChar(l,c))
80
81 static bool
82 GetInputLine(FILE *file,InputLine *line,bool checkbang)
83 {
84      int ch;
85      bool endOfFile,spacePending,slashPending,inComment;
86
87      endOfFile= false;
88      while ((!endOfFile)&&(line->num_line==0)) {
89         spacePending= slashPending= inComment= false;
90         while (((ch=getc(file))!='\n')&&(ch!=EOF)) {
91             if (ch=='\\') {
92                 if ((ch=getc(file))==EOF)
93                     break;
94                 if (ch=='\n') {
95                     inComment= false;
96                     ch= ' ';
97                 }
98             }
99             if (inComment)
100                 continue;
101             if (ch=='/') {
102                 if (slashPending) {
103                     inComment= true;
104                     slashPending= false;
105                 }
106                 else {
107                     slashPending= true;
108                 }
109                 continue;
110             }
111             else if (slashPending) {
112                 if (spacePending) {
113                     ADD_CHAR(line,' ');
114                     spacePending= false;
115                 }
116                 ADD_CHAR(line,'/');
117                 slashPending= false;
118             }
119             if (isspace(ch)) {
120                 while (isspace(ch)&&(ch!='\n')&&(ch!=EOF)) {
121                     ch= getc(file);
122                 }
123                 if (ch==EOF)
124                     break;
125                 if ((ch!='\n')&&(line->num_line>0))
126                     spacePending= true;
127                 ungetc(ch,file);
128             }
129             else {
130                 if (spacePending) {
131                     ADD_CHAR(line,' ');
132                     spacePending= false;
133                 }
134                 if (checkbang && ch=='!') {
135                     if (line->num_line!=0) {
136                         WARN("The '!' legal only at start of line\n");
137                         ACTION("Line containing '!' ignored\n");
138                         line->num_line= 0;
139                         break;
140                     }
141
142                 }
143                 ADD_CHAR(line,ch);
144             }
145         }
146         if (ch==EOF)
147              endOfFile= true;
148 /*      else line->num_line++;*/
149      }
150      if ((line->num_line==0)&&(endOfFile))
151         return false;
152       ADD_CHAR(line,'\0');
153       return true;
154 }
155
156 /***====================================================================***/
157
158 #define MODEL           0
159 #define LAYOUT          1
160 #define VARIANT         2
161 #define OPTION          3
162 #define KEYCODES        4
163 #define SYMBOLS         5
164 #define TYPES           6
165 #define COMPAT          7
166 #define GEOMETRY        8
167 #define KEYMAP          9
168 #define MAX_WORDS       10
169
170 #define PART_MASK       0x000F
171 #define COMPONENT_MASK  0x03F0
172
173 static  const char * cname[MAX_WORDS] = {
174         "model", "layout", "variant", "option",
175         "keycodes", "symbols", "types", "compat", "geometry", "keymap"
176 };
177
178 typedef struct {
179     const char *model;
180     const char *layout;
181     const char *variant;
182     const char *options;
183 } XkbRF_VarDefsRec, *XkbRF_VarDefsPtr;
184
185 typedef struct _RemapSpec {
186         int                     number;
187         size_t                  num_remap;
188         struct  {
189                 int     word;
190                 int     index;
191                 }               remap[MAX_WORDS];
192 } RemapSpec;
193
194 typedef struct _FileSpec {
195         char *                  name[MAX_WORDS];
196         struct _FileSpec *      pending;
197 } FileSpec;
198
199 typedef struct {
200         const char *            model;
201         const char *            layout[XkbNumKbdGroups+1];
202         const char *            variant[XkbNumKbdGroups+1];
203         char *                  options;
204 } XkbRF_MultiDefsRec, *XkbRF_MultiDefsPtr;
205
206 typedef struct _XkbRF_VarDesc {
207     char *  name;
208     char *  desc;
209 } XkbRF_VarDescRec, *XkbRF_VarDescPtr;
210
211 typedef struct _XkbRF_DescribeVars {
212     size_t              sz_desc;
213     size_t              num_desc;
214     XkbRF_VarDescPtr    desc;
215 } XkbRF_DescribeVarsRec,*XkbRF_DescribeVarsPtr;
216
217 typedef struct _XkbRF_Group {
218     int     number;
219     char *  name;
220     char *  words;
221 } XkbRF_GroupRec, *XkbRF_GroupPtr;
222
223 #define XkbRF_PendingMatch  (1L<<1)
224 #define XkbRF_Option        (1L<<2)
225 #define XkbRF_Append        (1L<<3)
226 #define XkbRF_Normal        (1L<<4)
227 #define XkbRF_Invalid       (1L<<5)
228
229 typedef struct _XkbRF_Rule {
230     int         number;
231     int         layout_num;
232     int         variant_num;
233     char *      model;
234     char *      layout;
235     char *      variant;
236     char *      option;
237     /* yields */
238     char *      keycodes;
239     char *      symbols;
240     char *      types;
241     char *      compat;
242     char *      keymap;
243     unsigned    flags;
244 } XkbRF_RuleRec,*XkbRF_RulePtr;
245
246 typedef struct XkbRF_Rules {
247     XkbRF_DescribeVarsRec   models;
248     XkbRF_DescribeVarsRec   layouts;
249     XkbRF_DescribeVarsRec   variants;
250     XkbRF_DescribeVarsRec   options;
251
252     size_t                  sz_rules;
253     size_t                  num_rules;
254     XkbRF_RulePtr           rules;
255     size_t                  sz_groups;
256     size_t                  num_groups;
257     XkbRF_GroupPtr          groups;
258 } XkbRF_RulesRec, *XkbRF_RulesPtr;
259
260 #define NDX_BUFF_SIZE   4
261
262 /***====================================================================***/
263
264 static char*
265 get_index(char *str, int *ndx)
266 {
267    char ndx_buf[NDX_BUFF_SIZE];
268    char *end;
269
270    if (*str != '[') {
271        *ndx = 0;
272        return str;
273    }
274    str++;
275    end = strchr(str, ']');
276    if (end == NULL) {
277        *ndx = -1;
278        return str - 1;
279    }
280    if ( (end - str) >= NDX_BUFF_SIZE) {
281        *ndx = -1;
282        return end + 1;
283    }
284    strncpy(ndx_buf, str, end - str);
285    ndx_buf[end - str] = '\0';
286    *ndx = atoi(ndx_buf);
287    return end + 1;
288 }
289
290 static void
291 SetUpRemap(InputLine *line,RemapSpec *remap)
292 {
293    char *tok, *str;
294    unsigned present, l_ndx_present, v_ndx_present;
295    int i;
296    size_t len;
297    int ndx;
298    char *strtok_buf;
299    bool found;
300
301    l_ndx_present = v_ndx_present = present= 0;
302    str= &line->line[1];
303    len = remap->number;
304    memset(remap, 0, sizeof(RemapSpec));
305    remap->number = len;
306    while ((tok = strtok_r(str, " ", &strtok_buf)) != NULL) {
307         found= false;
308         str= NULL;
309         if (strcmp(tok,"=")==0)
310             continue;
311         for (i=0;i<MAX_WORDS;i++) {
312             len = strlen(cname[i]);
313             if (strncmp(cname[i],tok,len)==0) {
314                 if(strlen(tok) > len) {
315                     char *end = get_index(tok+len, &ndx);
316                     if ((i != LAYOUT && i != VARIANT) ||
317                         *end != '\0' || ndx == -1)
318                         break;
319                      if (ndx < 1 || ndx > XkbNumKbdGroups) {
320                         WARN("Illegal %s index: %d\n", cname[i], ndx);
321                         WARN("Index must be in range 1..%d\n", XkbNumKbdGroups);
322                         break;
323                      }
324                 } else {
325                     ndx = 0;
326                 }
327
328                 found= true;
329
330                 if (present&(1<<i)) {
331                     if ((i == LAYOUT && l_ndx_present&(1<<ndx)) ||
332                         (i == VARIANT && v_ndx_present&(1<<ndx)) ) {
333                         WARN("Component \"%s\" listed twice\n", tok);
334                         ACTION("Second definition ignored\n");
335                         break;
336                     }
337                 }
338                 present |= (1<<i);
339                 if (i == LAYOUT)
340                     l_ndx_present |= 1 << ndx;
341                 if (i == VARIANT)
342                     v_ndx_present |= 1 << ndx;
343                 remap->remap[remap->num_remap].word= i;
344                 remap->remap[remap->num_remap++].index= ndx;
345                 break;
346             }
347         }
348
349         if (!found) {
350             WARN("Unknown component \"%s\"\n", tok);
351             ACTION("ignored\n");
352         }
353    }
354    if ((present&PART_MASK)==0) {
355         unsigned mask= PART_MASK;
356
357         /* FIXME: Use log function instead of fprintf. */
358         WARN("Mapping needs at least one of ");
359         for (i=0; (i<MAX_WORDS); i++) {
360             if ((1L<<i)&mask) {
361                 mask&= ~(1L<<i);
362                 if (mask)       fprintf(stderr,"\"%s,\" ",cname[i]);
363                 else            fprintf(stderr,"or \"%s\"\n",cname[i]);
364             }
365         }
366         ACTION("Illegal mapping ignored\n");
367
368         remap->num_remap= 0;
369         return;
370    }
371    if ((present&COMPONENT_MASK)==0) {
372         WARN("Mapping needs at least one component\n");
373         ACTION("Illegal mapping ignored\n");
374         remap->num_remap= 0;
375         return;
376    }
377    if (((present&COMPONENT_MASK)&(1<<KEYMAP))&&
378                                 ((present&COMPONENT_MASK)!=(1<<KEYMAP))) {
379         WARN("Keymap cannot appear with other components\n");
380         ACTION("Illegal mapping ignored\n");
381         remap->num_remap= 0;
382         return;
383    }
384    remap->number++;
385 }
386
387 static bool
388 MatchOneOf(char *wanted,char *vals_defined)
389 {
390     char *str, *next;
391     int want_len = strlen(wanted);
392
393     for (str=vals_defined,next=NULL;str!=NULL;str=next) {
394         int len;
395         next= strchr(str,',');
396         if (next) {
397             len= next-str;
398             next++;
399         }
400         else {
401             len= strlen(str);
402         }
403         if ((len==want_len)&&(strncmp(wanted,str,len)==0))
404             return true;
405     }
406     return false;
407 }
408
409 /***====================================================================***/
410
411 static bool
412 CheckLine(      InputLine *             line,
413                 RemapSpec *             remap,
414                 XkbRF_RulePtr           rule,
415                 XkbRF_GroupPtr          group)
416 {
417     char *str, *tok;
418     int nread, i;
419     FileSpec tmp;
420     char *strtok_buf;
421     bool append = false;
422
423     if (line->line[0]=='!') {
424         if (line->line[1] == '$' ||
425             (line->line[1] == ' ' && line->line[2] == '$')) {
426             char *gname = strchr(line->line, '$');
427             char *words = strchr(gname, ' ');
428             if(!words)
429                 return false;
430             *words++ = '\0';
431             for (; *words; words++) {
432                 if (*words != '=' && *words != ' ')
433                     break;
434             }
435             if (*words == '\0')
436                 return false;
437             group->name = uDupString(gname);
438             group->words = uDupString(words);
439             for (i = 1, words = group->words; *words; words++) {
440                  if ( *words == ' ') {
441                      *words++ = '\0';
442                      i++;
443                  }
444             }
445             group->number = i;
446             return true;
447         } else {
448             SetUpRemap(line,remap);
449             return false;
450         }
451     }
452
453     if (remap->num_remap==0) {
454         WARN("Must have a mapping before first line of data\n");
455         ACTION("Illegal line of data ignored\n");
456         return false;
457     }
458     memset(&tmp, 0, sizeof(FileSpec));
459     str= line->line;
460     for (nread = 0; (tok = strtok_r(str, " ", &strtok_buf)) != NULL; nread++) {
461         str= NULL;
462         if (strcmp(tok,"=")==0) {
463             nread--;
464             continue;
465         }
466         if (nread>remap->num_remap) {
467             WARN("Too many words on a line\n");
468             ACTION("Extra word \"%s\" ignored\n",tok);
469             continue;
470         }
471         tmp.name[remap->remap[nread].word]= tok;
472         if (*tok == '+' || *tok == '|')
473             append = true;
474     }
475     if (nread<remap->num_remap) {
476         WARN("Too few words on a line: %s\n", line->line);
477         ACTION("line ignored\n");
478         return false;
479     }
480
481     rule->flags= 0;
482     rule->number = remap->number;
483     if (tmp.name[OPTION])
484          rule->flags|= XkbRF_Option;
485     else if (append)
486          rule->flags|= XkbRF_Append;
487     else
488          rule->flags|= XkbRF_Normal;
489     rule->model= uDupString(tmp.name[MODEL]);
490     rule->layout= uDupString(tmp.name[LAYOUT]);
491     rule->variant= uDupString(tmp.name[VARIANT]);
492     rule->option= uDupString(tmp.name[OPTION]);
493
494     rule->keycodes= uDupString(tmp.name[KEYCODES]);
495     rule->symbols= uDupString(tmp.name[SYMBOLS]);
496     rule->types= uDupString(tmp.name[TYPES]);
497     rule->compat= uDupString(tmp.name[COMPAT]);
498     rule->keymap= uDupString(tmp.name[KEYMAP]);
499
500     rule->layout_num = rule->variant_num = 0;
501     for (i = 0; i < nread; i++) {
502         if (remap->remap[i].index) {
503             if (remap->remap[i].word == LAYOUT)
504                 rule->layout_num = remap->remap[i].index;
505             if (remap->remap[i].word == VARIANT)
506                 rule->variant_num = remap->remap[i].index;
507         }
508     }
509     return true;
510 }
511
512 static char *
513 _Concat(char *str1,char *str2)
514 {
515     int len;
516
517     if ((!str1)||(!str2))
518         return str1;
519     len= strlen(str1)+strlen(str2)+1;
520     str1 = uTypedRealloc(str1, len, char);
521     if (str1)
522         strcat(str1,str2);
523     return str1;
524 }
525
526 static void
527 squeeze_spaces(char *p1)
528 {
529    char *p2;
530    for (p2 = p1; *p2; p2++) {
531        *p1 = *p2;
532        if (*p1 != ' ') p1++;
533    }
534    *p1 = '\0';
535 }
536
537 static bool
538 MakeMultiDefs(XkbRF_MultiDefsPtr mdefs, const XkbRF_VarDefsPtr defs)
539 {
540    memset(mdefs, 0, sizeof(XkbRF_MultiDefsRec));
541    mdefs->model = defs->model;
542    mdefs->options = uDupString(defs->options);
543    if (mdefs->options) squeeze_spaces(mdefs->options);
544
545    if (defs->layout) {
546        if (!strchr(defs->layout, ',')) {
547            mdefs->layout[0] = defs->layout;
548        } else {
549            char *p;
550            int i;
551            p = uDupString(defs->layout);
552            if (p == NULL)
553               return false;
554            squeeze_spaces(p);
555            mdefs->layout[1] = p;
556            for (i = 2; i <= XkbNumKbdGroups; i++) {
557               if ((p = strchr(p, ','))) {
558                  *p++ = '\0';
559                  mdefs->layout[i] = p;
560               } else {
561                  break;
562               }
563            }
564            if (p && (p = strchr(p, ',')))
565               *p = '\0';
566        }
567    }
568
569    if (defs->variant) {
570        if (!strchr(defs->variant, ',')) {
571            mdefs->variant[0] = defs->variant;
572        } else {
573            char *p;
574            int i;
575            p = uDupString(defs->variant);
576            if (p == NULL)
577               return false;
578            squeeze_spaces(p);
579            mdefs->variant[1] = p;
580            for (i = 2; i <= XkbNumKbdGroups; i++) {
581               if ((p = strchr(p, ','))) {
582                  *p++ = '\0';
583                  mdefs->variant[i] = p;
584               } else {
585                  break;
586               }
587            }
588            if (p && (p = strchr(p, ',')))
589               *p = '\0';
590        }
591    }
592    return true;
593 }
594
595 static void
596 FreeMultiDefs(XkbRF_MultiDefsPtr defs)
597 {
598     free(defs->options);
599     free(UNCONSTIFY(defs->layout[1]));
600     free(UNCONSTIFY(defs->variant[1]));
601 }
602
603 static void
604 Apply(char *src, char **dst)
605 {
606     if (src) {
607         if (*src == '+' || *src == '!') {
608             *dst= _Concat(*dst, src);
609         } else {
610             if (*dst == NULL)
611                 *dst= uDupString(src);
612         }
613     }
614 }
615
616 static void
617 XkbRF_ApplyRule(        XkbRF_RulePtr           rule,
618                         struct xkb_component_names *    names)
619 {
620     rule->flags&= ~XkbRF_PendingMatch; /* clear the flag because it's applied */
621
622     Apply(rule->keycodes, &names->keycodes);
623     Apply(rule->symbols,  &names->symbols);
624     Apply(rule->types,    &names->types);
625     Apply(rule->compat,   &names->compat);
626     Apply(rule->keymap,   &names->keymap);
627 }
628
629 static bool
630 CheckGroup(     XkbRF_RulesPtr          rules,
631                 const char *            group_name,
632                 const char *            name)
633 {
634    int i;
635    const char *p;
636    XkbRF_GroupPtr group;
637
638    for (i = 0, group = rules->groups; i < rules->num_groups; i++, group++) {
639        if (! strcmp(group->name, group_name)) {
640            break;
641        }
642    }
643    if (i == rules->num_groups)
644        return false;
645    for (i = 0, p = group->words; i < group->number; i++, p += strlen(p)+1) {
646        if (! strcmp(p, name)) {
647            return true;
648        }
649    }
650    return false;
651 }
652
653 static int
654 XkbRF_CheckApplyRule(   XkbRF_RulePtr           rule,
655                         XkbRF_MultiDefsPtr      mdefs,
656                         struct xkb_component_names *    names,
657                         XkbRF_RulesPtr          rules)
658 {
659     bool pending = false;
660
661     if (rule->model != NULL) {
662         if(mdefs->model == NULL)
663             return 0;
664         if (strcmp(rule->model, "*") == 0) {
665             pending = true;
666         } else {
667             if (rule->model[0] == '$') {
668                if (!CheckGroup(rules, rule->model, mdefs->model))
669                   return 0;
670             } else {
671                if (strcmp(rule->model, mdefs->model) != 0)
672                   return 0;
673             }
674         }
675     }
676     if (rule->option != NULL) {
677         if (mdefs->options == NULL)
678             return 0;
679         if ((!MatchOneOf(rule->option,mdefs->options)))
680             return 0;
681     }
682
683     if (rule->layout != NULL) {
684         if(mdefs->layout[rule->layout_num] == NULL ||
685            *mdefs->layout[rule->layout_num] == '\0')
686             return 0;
687         if (strcmp(rule->layout, "*") == 0) {
688             pending = true;
689         } else {
690             if (rule->layout[0] == '$') {
691                if (!CheckGroup(rules, rule->layout,
692                                mdefs->layout[rule->layout_num]))
693                   return 0;
694             } else {
695                if (strcmp(rule->layout, mdefs->layout[rule->layout_num]) != 0)
696                    return 0;
697             }
698         }
699     }
700     if (rule->variant != NULL) {
701         if (mdefs->variant[rule->variant_num] == NULL ||
702             *mdefs->variant[rule->variant_num] == '\0')
703             return 0;
704         if (strcmp(rule->variant, "*") == 0) {
705             pending = true;
706         } else {
707             if (rule->variant[0] == '$') {
708                if (!CheckGroup(rules, rule->variant,
709                                mdefs->variant[rule->variant_num]))
710                   return 0;
711             } else {
712                if (strcmp(rule->variant,
713                           mdefs->variant[rule->variant_num]) != 0)
714                    return 0;
715             }
716         }
717     }
718     if (pending) {
719         rule->flags|= XkbRF_PendingMatch;
720         return rule->number;
721     }
722     /* exact match, apply it now */
723     XkbRF_ApplyRule(rule,names);
724     return rule->number;
725 }
726
727 static void
728 XkbRF_ClearPartialMatches(XkbRF_RulesPtr rules)
729 {
730     int i;
731     XkbRF_RulePtr rule;
732
733     for (i=0,rule=rules->rules;i<rules->num_rules;i++,rule++) {
734         rule->flags&= ~XkbRF_PendingMatch;
735     }
736 }
737
738 static void
739 XkbRF_ApplyPartialMatches(XkbRF_RulesPtr rules,struct xkb_component_names * names)
740 {
741     int i;
742     XkbRF_RulePtr rule;
743
744     for (rule = rules->rules, i = 0; i < rules->num_rules; i++, rule++) {
745         if ((rule->flags&XkbRF_PendingMatch)==0)
746             continue;
747         XkbRF_ApplyRule(rule,names);
748     }
749 }
750
751 static void
752 XkbRF_CheckApplyRules(  XkbRF_RulesPtr          rules,
753                         XkbRF_MultiDefsPtr      mdefs,
754                         struct xkb_component_names *    names,
755                         unsigned int                    flags)
756 {
757     int i;
758     XkbRF_RulePtr rule;
759     int skip;
760
761     for (rule = rules->rules, i=0; i < rules->num_rules; rule++, i++) {
762         if ((rule->flags & flags) != flags)
763             continue;
764         skip = XkbRF_CheckApplyRule(rule, mdefs, names, rules);
765         if (skip && !(flags & XkbRF_Option)) {
766             for ( ;(i < rules->num_rules) && (rule->number == skip);
767                   rule++, i++);
768             rule--; i--;
769         }
770     }
771 }
772
773 /***====================================================================***/
774
775 static char *
776 XkbRF_SubstituteVars(char *name, XkbRF_MultiDefsPtr mdefs)
777 {
778     char *str, *outstr, *orig, *var;
779     size_t len;
780     int ndx;
781
782     if (!name)
783         return NULL;
784
785     orig= name;
786     str= strchr(name,'%');
787     if (str==NULL)
788         return name;
789     len= strlen(name);
790     while (str!=NULL) {
791         char pfx= str[1];
792         int   extra_len= 0;
793         if ((pfx=='+')||(pfx=='|')||(pfx=='_')||(pfx=='-')) {
794             extra_len= 1;
795             str++;
796         }
797         else if (pfx=='(') {
798             extra_len= 2;
799             str++;
800         }
801         var = str + 1;
802         str = get_index(var + 1, &ndx);
803         if (ndx == -1) {
804             str = strchr(str,'%');
805             continue;
806         }
807         if ((*var=='l') && mdefs->layout[ndx] && *mdefs->layout[ndx])
808             len+= strlen(mdefs->layout[ndx])+extra_len;
809         else if ((*var=='m')&&mdefs->model)
810             len+= strlen(mdefs->model)+extra_len;
811         else if ((*var=='v') && mdefs->variant[ndx] && *mdefs->variant[ndx])
812             len+= strlen(mdefs->variant[ndx])+extra_len;
813         if ((pfx=='(')&&(*str==')')) {
814             str++;
815         }
816         str= strchr(&str[0],'%');
817     }
818     name = malloc(len + 1);
819     str= orig;
820     outstr= name;
821     while (*str!='\0') {
822         if (str[0]=='%') {
823             char pfx,sfx;
824             str++;
825             pfx= str[0];
826             sfx= '\0';
827             if ((pfx=='+')||(pfx=='|')||(pfx=='_')||(pfx=='-')) {
828                 str++;
829             }
830             else if (pfx=='(') {
831                 sfx= ')';
832                 str++;
833             }
834             else pfx= '\0';
835
836             var = str;
837             str = get_index(var + 1, &ndx);
838             if (ndx == -1) {
839                 continue;
840             }
841             if ((*var=='l') && mdefs->layout[ndx] && *mdefs->layout[ndx]) {
842                 if (pfx) *outstr++= pfx;
843                 strcpy(outstr,mdefs->layout[ndx]);
844                 outstr+= strlen(mdefs->layout[ndx]);
845                 if (sfx) *outstr++= sfx;
846             }
847             else if ((*var=='m')&&(mdefs->model)) {
848                 if (pfx) *outstr++= pfx;
849                 strcpy(outstr,mdefs->model);
850                 outstr+= strlen(mdefs->model);
851                 if (sfx) *outstr++= sfx;
852             }
853             else if ((*var=='v') && mdefs->variant[ndx] && *mdefs->variant[ndx]) {
854                 if (pfx) *outstr++= pfx;
855                 strcpy(outstr,mdefs->variant[ndx]);
856                 outstr+= strlen(mdefs->variant[ndx]);
857                 if (sfx) *outstr++= sfx;
858             }
859             if ((pfx=='(')&&(*str==')'))
860                 str++;
861         }
862         else {
863             *outstr++= *str++;
864         }
865     }
866     *outstr++= '\0';
867     if (orig!=name)
868         free(orig);
869     return name;
870 }
871
872 /***====================================================================***/
873
874 static bool
875 XkbcRF_GetComponents(struct XkbRF_Rules *rules, const XkbRF_VarDefsPtr defs,
876                      struct xkb_component_names *names)
877 {
878     XkbRF_MultiDefsRec mdefs;
879
880     MakeMultiDefs(&mdefs, defs);
881
882     memset(names, 0, sizeof(struct xkb_component_names));
883     XkbRF_ClearPartialMatches(rules);
884     XkbRF_CheckApplyRules(rules, &mdefs, names, XkbRF_Normal);
885     XkbRF_ApplyPartialMatches(rules, names);
886     XkbRF_CheckApplyRules(rules, &mdefs, names, XkbRF_Append);
887     XkbRF_ApplyPartialMatches(rules, names);
888     XkbRF_CheckApplyRules(rules, &mdefs, names, XkbRF_Option);
889     XkbRF_ApplyPartialMatches(rules, names);
890
891     names->keycodes = XkbRF_SubstituteVars(names->keycodes, &mdefs);
892     names->symbols = XkbRF_SubstituteVars(names->symbols, &mdefs);
893     names->types = XkbRF_SubstituteVars(names->types, &mdefs);
894     names->compat = XkbRF_SubstituteVars(names->compat, &mdefs);
895     names->keymap = XkbRF_SubstituteVars(names->keymap, &mdefs);
896
897     FreeMultiDefs(&mdefs);
898     return (names->keycodes && names->symbols && names->types &&
899                 names->compat) || names->keymap;
900 }
901
902 static XkbRF_RulePtr
903 XkbcRF_AddRule(XkbRF_RulesPtr   rules)
904 {
905     if (rules->sz_rules<1) {
906         rules->sz_rules= 16;
907         rules->num_rules= 0;
908         rules->rules= uTypedCalloc(rules->sz_rules,XkbRF_RuleRec);
909     }
910     else if (rules->num_rules>=rules->sz_rules) {
911         rules->sz_rules*= 2;
912         rules->rules= uTypedRealloc(rules->rules,rules->sz_rules,
913                                                         XkbRF_RuleRec);
914     }
915     if (!rules->rules) {
916         rules->sz_rules= rules->num_rules= 0;
917         return NULL;
918     }
919     memset(&rules->rules[rules->num_rules], 0, sizeof(XkbRF_RuleRec));
920     return &rules->rules[rules->num_rules++];
921 }
922
923 static XkbRF_GroupPtr
924 XkbcRF_AddGroup(XkbRF_RulesPtr  rules)
925 {
926     if (rules->sz_groups<1) {
927         rules->sz_groups= 16;
928         rules->num_groups= 0;
929         rules->groups= uTypedCalloc(rules->sz_groups,XkbRF_GroupRec);
930     }
931     else if (rules->num_groups >= rules->sz_groups) {
932         rules->sz_groups *= 2;
933         rules->groups= uTypedRealloc(rules->groups,rules->sz_groups,
934                                      XkbRF_GroupRec);
935     }
936     if (!rules->groups) {
937         rules->sz_groups= rules->num_groups= 0;
938         return NULL;
939     }
940
941     memset(&rules->groups[rules->num_groups], 0, sizeof(XkbRF_GroupRec));
942     return &rules->groups[rules->num_groups++];
943 }
944
945 static XkbRF_RulesPtr
946 XkbcRF_LoadRules(FILE *file)
947 {
948 InputLine       line;
949 RemapSpec       remap;
950 XkbRF_RuleRec   trule,*rule;
951 XkbRF_GroupRec  tgroup,*group;
952     XkbRF_RulesPtr rules;
953
954     rules = calloc(1, sizeof(*rules));
955     if (!rules)
956         return NULL;
957
958     memset(&remap, 0, sizeof(RemapSpec));
959     memset(&tgroup, 0, sizeof(XkbRF_GroupRec));
960     InitInputLine(&line);
961     while (GetInputLine(file, &line, true)) {
962         if (CheckLine(&line,&remap,&trule,&tgroup)) {
963             if (tgroup.number) {
964                 if ((group= XkbcRF_AddGroup(rules))!=NULL) {
965                     *group= tgroup;
966                     memset(&tgroup, 0, sizeof(XkbRF_GroupRec));
967                 }
968             } else {
969                 if ((rule= XkbcRF_AddRule(rules))!=NULL) {
970                     *rule= trule;
971                     memset(&trule, 0, sizeof(XkbRF_RuleRec));
972                 }
973             }
974         }
975         line.num_line= 0;
976     }
977     FreeInputLine(&line);
978     return rules;
979 }
980
981 static void
982 XkbRF_ClearVarDescriptions(XkbRF_DescribeVarsPtr var)
983 {
984     int i;
985
986     for (i=0;i<var->num_desc;i++) {
987         free(var->desc[i].name);
988         free(var->desc[i].desc);
989         var->desc[i].name= var->desc[i].desc= NULL;
990     }
991     free(var->desc);
992     var->desc= NULL;
993 }
994
995 static void
996 XkbcRF_Free(XkbRF_RulesPtr rules)
997 {
998     int i;
999     XkbRF_RulePtr rule;
1000     XkbRF_GroupPtr group;
1001
1002     if (!rules)
1003         return;
1004     XkbRF_ClearVarDescriptions(&rules->models);
1005     XkbRF_ClearVarDescriptions(&rules->layouts);
1006     XkbRF_ClearVarDescriptions(&rules->variants);
1007     XkbRF_ClearVarDescriptions(&rules->options);
1008
1009     for (i=0, rule = rules->rules; i < rules->num_rules && rules; i++, rule++) {
1010         free(rule->model);
1011         free(rule->layout);
1012         free(rule->variant);
1013         free(rule->option);
1014         free(rule->keycodes);
1015         free(rule->symbols);
1016         free(rule->types);
1017         free(rule->compat);
1018         free(rule->keymap);
1019     }
1020     free(rules->rules);
1021
1022     for (i=0, group = rules->groups; i < rules->num_groups && group; i++, group++) {
1023         free(group->name);
1024         free(group->words);
1025     }
1026     free(rules->groups);
1027
1028     free(rules);
1029 }
1030
1031 struct xkb_component_names *
1032 xkb_components_from_rules(struct xkb_context *ctx,
1033                           const struct xkb_rule_names *rmlvo)
1034 {
1035     int i;
1036     FILE *rulesFile;
1037     char *rulesPath;
1038     XkbRF_RulesPtr loaded;
1039     struct xkb_component_names *names = NULL;
1040     XkbRF_VarDefsRec defs = {
1041         .model = rmlvo->model,
1042         .layout = rmlvo->layout,
1043         .variant = rmlvo->variant,
1044         .options = rmlvo->options,
1045     };
1046
1047     rulesFile = XkbFindFileInPath(ctx, rmlvo->rules, XkmRulesFile,
1048                                   &rulesPath);
1049     if (!rulesFile) {
1050         ERROR("could not find \"%s\" rules in XKB path\n", rmlvo->rules);
1051         ERROR("%d include paths searched:\n",
1052               xkb_context_num_include_paths(ctx));
1053         for (i = 0; i < xkb_context_num_include_paths(ctx); i++)
1054             ERROR("\t%s\n", xkb_context_include_path_get(ctx, i));
1055         return NULL;
1056     }
1057
1058     loaded = XkbcRF_LoadRules(rulesFile);
1059     if (!loaded) {
1060         ERROR("failed to load XKB rules \"%s\"\n", rulesPath);
1061         goto unwind_file;
1062     }
1063
1064     names = calloc(1, sizeof(*names));
1065     if (!names) {
1066         ERROR("failed to allocate XKB components\n");
1067         goto unwind_file;
1068     }
1069
1070     if (!XkbcRF_GetComponents(loaded, &defs, names)) {
1071         free(names->keymap);
1072         free(names->keycodes);
1073         free(names->types);
1074         free(names->compat);
1075         free(names->symbols);
1076         free(names);
1077         names = NULL;
1078         ERROR("no components returned from XKB rules \"%s\"\n", rulesPath);
1079     }
1080
1081 unwind_file:
1082     XkbcRF_Free(loaded);
1083     if (rulesFile)
1084         fclose(rulesFile);
1085     free(rulesPath);
1086     return names;
1087 }