244701dee58393985f4b196ee3e90bbf83703d40
[profile/ivi/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 static bool
34 input_line_get(FILE *file, darray_char *line)
35 {
36     int ch;
37     bool end_of_file = false;
38     bool space_pending;
39     bool slash_pending;
40     bool in_comment;
41
42     while (!end_of_file && darray_empty(*line)) {
43         space_pending = slash_pending = in_comment = false;
44
45         while ((ch = getc(file)) != '\n' && ch != EOF) {
46             if (ch == '\\') {
47                 ch = getc(file);
48
49                 if (ch == EOF)
50                     break;
51
52                 if (ch == '\n') {
53                     in_comment = false;
54                     ch = ' ';
55                 }
56             }
57
58             if (in_comment)
59                 continue;
60
61             if (ch == '/') {
62                 if (slash_pending) {
63                     in_comment = true;
64                     slash_pending = false;
65                 }
66                 else {
67                     slash_pending = true;
68                 }
69
70                 continue;
71             }
72
73             if (slash_pending) {
74                 if (space_pending) {
75                     darray_append(*line, ' ');
76                     space_pending = false;
77                 }
78
79                 darray_append(*line, '/');
80                 slash_pending = false;
81             }
82
83             if (isspace(ch)) {
84                 while (isspace(ch) && ch != '\n' && ch != EOF)
85                     ch = getc(file);
86
87                 if (ch == EOF)
88                     break;
89
90                 if (ch != '\n' && !darray_empty(*line))
91                     space_pending = true;
92
93                 ungetc(ch, file);
94             }
95             else {
96                 if (space_pending) {
97                     darray_append(*line, ' ');
98                     space_pending = false;
99                 }
100
101                 if (ch == '!') {
102                     if (!darray_empty(*line)) {
103                         WARN("The '!' is legal only at start of line\n");
104                         ACTION("Line containing '!' ignored\n");
105                         darray_resize(*line, 0);
106                         break;
107                     }
108                 }
109
110                 darray_append(*line, ch);
111             }
112         }
113
114         if (ch == EOF)
115             end_of_file = true;
116     }
117
118     if (darray_empty(*line) && end_of_file)
119         return false;
120
121     darray_append(*line, '\0');
122     return true;
123 }
124
125 /***====================================================================***/
126
127 enum {
128     /* "Parts" - the MLVO which rules file maps to components. */
129     MODEL = 0,
130     LAYOUT,
131     VARIANT,
132     OPTION,
133
134 #define PART_MASK \
135     ((1 << MODEL) | (1 << LAYOUT) | (1 << VARIANT) | (1 << OPTION))
136
137     /* Components */
138     KEYCODES,
139     SYMBOLS,
140     TYPES,
141     COMPAT,
142     GEOMETRY,
143     KEYMAP,
144
145 #define COMPONENT_MASK \
146     ((1 << KEYCODES) | (1 << SYMBOLS) | (1 << TYPES) | (1 << COMPAT) | \
147      (1 << GEOMETRY) | (1 << KEYMAP))
148
149     MAX_WORDS
150 };
151
152 static const char *cname[] = {
153     [MODEL] = "model",
154     [LAYOUT] = "layout",
155     [VARIANT] = "variant",
156     [OPTION] = "option",
157
158     [KEYCODES] = "keycodes",
159     [SYMBOLS] = "symbols",
160     [TYPES] = "types",
161     [COMPAT] = "compat",
162     [GEOMETRY] = "geometry",
163     [KEYMAP] = "keymap",
164 };
165
166 struct multi_defs {
167     const char *model;
168     const char *layout[XkbNumKbdGroups + 1];
169     const char *variant[XkbNumKbdGroups + 1];
170     char *options;
171 };
172
173 struct mapping {
174     /* Sequential id for the mappings. */
175     int number;
176     size_t num_maps;
177
178     struct {
179         int word;
180         int index;
181     } map[MAX_WORDS];
182 };
183
184 struct var_desc {
185     char *name;
186     char *desc;
187 };
188
189 struct group {
190     int number;
191     char *name;
192     char *words;
193 };
194
195 enum rule_flag {
196     RULE_FLAG_PENDING_MATCH = (1L << 1),
197     RULE_FLAG_OPTION        = (1L << 2),
198     RULE_FLAG_APPEND        = (1L << 3),
199     RULE_FLAG_NORMAL        = (1L << 4),
200 };
201
202 struct rule {
203     int number;
204
205     char *model;
206     char *layout;
207     int layout_num;
208     char *variant;
209     int variant_num;
210     char *option;
211
212     /* yields */
213
214     char *keycodes;
215     char *symbols;
216     char *types;
217     char *compat;
218     char *keymap;
219     unsigned flags;
220 };
221
222 struct rules {
223     darray(struct rule) rules;
224     darray(struct group) groups;
225 };
226
227 /***====================================================================***/
228
229 /*
230  * Resolve numeric index, such as "[4]" in layout[4]. Missing index
231  * means zero.
232  */
233 static char *
234 get_index(char *str, int *ndx)
235 {
236     int empty = 0, consumed = 0, num;
237
238     sscanf(str, "[%n%d]%n", &empty, &num, &consumed);
239     if (consumed > 0) {
240         *ndx = num;
241         str += consumed;
242     } else if (empty > 0) {
243         *ndx = -1;
244     } else {
245         *ndx = 0;
246     }
247
248     return str;
249 }
250
251 /*
252  * Match a mapping line which opens a rule, e.g:
253  * ! model      layout[4]       variant[4]      =       symbols       geometry
254  * Which will be followed by lines such as:
255  *   *          ben             basic           =       +in(ben):4    nec(pc98)
256  * So if the MLVO matches the LHS of some line, we'll get the components
257  * on the RHS.
258  * In this example, we will get for the second and fourth columns:
259  * mapping->map[1] = {.word = LAYOUT, .index = 4}
260  * mapping->map[3] = {.word = SYMBOLS, .index = 0}
261  */
262 static void
263 match_mapping_line(darray_char *line, struct mapping *mapping)
264 {
265     char *tok;
266     char *str = &darray_item(*line, 1);
267     unsigned present = 0, layout_ndx_present = 0, variant_ndx_present = 0;
268     int i, tmp;
269     size_t len;
270     int ndx;
271     char *strtok_buf;
272     bool found;
273
274     /*
275      * Remember the last sequential mapping id (incremented if the match
276      * is successful).
277      */
278     tmp = mapping->number;
279     memset(mapping, 0, sizeof(*mapping));
280     mapping->number = tmp;
281
282     while ((tok = strtok_r(str, " ", &strtok_buf)) != NULL) {
283         found = false;
284         str = NULL;
285
286         if (strcmp(tok, "=") == 0)
287             continue;
288
289         for (i = 0; i < MAX_WORDS; i++) {
290             len = strlen(cname[i]);
291
292             if (strncmp(cname[i], tok, len) == 0) {
293                 if (strlen(tok) > len) {
294                     char *end = get_index(tok + len, &ndx);
295
296                     if ((i != LAYOUT && i != VARIANT) ||
297                         *end != '\0' || ndx == -1) {
298                         WARN("Illegal %s index: %d\n", cname[i], ndx);
299                         WARN("Can only index layout and variant\n");
300                         break;
301                     }
302
303                     if (ndx < 1 || ndx > XkbNumKbdGroups) {
304                         WARN("Illegal %s index: %d\n", cname[i], ndx);
305                         WARN("Index must be in range 1..%d\n", XkbNumKbdGroups);
306                         break;
307                     }
308                 } else {
309                     ndx = 0;
310                 }
311
312                 found = true;
313
314                 if (present & (1 << i)) {
315                     if ((i == LAYOUT && layout_ndx_present & (1 << ndx)) ||
316                         (i == VARIANT && variant_ndx_present & (1 << ndx))) {
317                         WARN("Component \"%s\" listed twice\n", tok);
318                         ACTION("Second definition ignored\n");
319                         break;
320                     }
321                 }
322
323                 present |= (1 << i);
324                 if (i == LAYOUT)
325                     layout_ndx_present |= 1 << ndx;
326                 if (i == VARIANT)
327                     variant_ndx_present |= 1 << ndx;
328
329                 mapping->map[mapping->num_maps].word = i;
330                 mapping->map[mapping->num_maps].index = ndx;
331                 mapping->num_maps++;
332                 break;
333             }
334         }
335
336         if (!found) {
337             WARN("Unknown component \"%s\"\n", tok);
338             ACTION("ignored\n");
339         }
340     }
341
342     if ((present & PART_MASK) == 0) {
343         WARN("Mapping needs at least one MLVO part\n");
344         ACTION("Illegal mapping ignored\n");
345         mapping->num_maps = 0;
346         return;
347     }
348
349     if ((present & COMPONENT_MASK) == 0) {
350         WARN("Mapping needs at least one component\n");
351         ACTION("Illegal mapping ignored\n");
352         mapping->num_maps = 0;
353         return;
354     }
355
356     if (((present & COMPONENT_MASK) & (1 << KEYMAP)) &&
357         ((present & COMPONENT_MASK) != (1 << KEYMAP))) {
358         WARN("Keymap cannot appear with other components\n");
359         ACTION("Illegal mapping ignored\n");
360         mapping->num_maps = 0;
361         return;
362     }
363
364     mapping->number++;
365 }
366
367 /*
368  * Match a line such as:
369  * ! $pcmodels = pc101 pc102 pc104 pc105
370  */
371 static bool
372 match_group_line(darray_char *line, struct group *group)
373 {
374     int i;
375     char *name = strchr(&darray_item(*line, 0), '$');
376     char *words = strchr(name, ' ');
377
378     if (!words)
379         return false;
380
381     *words++ = '\0';
382
383     for (; *words; words++) {
384         if (*words != '=' && *words != ' ')
385             break;
386     }
387
388     if (*words == '\0')
389         return false;
390
391     group->name = strdup(name);
392     group->words = strdup(words);
393
394     words = group->words;
395     for (i = 1; *words; words++) {
396         if (*words == ' ') {
397             *words++ = '\0';
398             i++;
399         }
400     }
401     group->number = i;
402
403     return true;
404
405 }
406
407 /* Match lines following a mapping (see match_mapping_line comment). */
408 static bool
409 match_rule_line(darray_char *line, struct mapping *mapping,
410                 struct rule *rule)
411 {
412     char *str, *tok;
413     int nread, i;
414     char *strtok_buf;
415     bool append = false;
416     const char *names[MAX_WORDS] = { NULL };
417
418     if (mapping->num_maps == 0) {
419         WARN("Must have a mapping before first line of data\n");
420         ACTION("Illegal line of data ignored\n");
421         return false;
422     }
423
424     str = &darray_item(*line, 0);
425
426     for (nread = 0; (tok = strtok_r(str, " ", &strtok_buf)) != NULL; nread++) {
427         str = NULL;
428
429         if (strcmp(tok, "=") == 0) {
430             nread--;
431             continue;
432         }
433
434         if (nread > mapping->num_maps) {
435             WARN("Too many words on a line\n");
436             ACTION("Extra word \"%s\" ignored\n", tok);
437             continue;
438         }
439
440         names[mapping->map[nread].word] = tok;
441         if (*tok == '+' || *tok == '|')
442             append = true;
443     }
444
445     if (nread < mapping->num_maps) {
446         WARN("Too few words on a line: %s\n", &darray_item(*line, 0));
447         ACTION("line ignored\n");
448         return false;
449     }
450
451     rule->flags = 0;
452     rule->number = mapping->number;
453
454     if (names[OPTION])
455         rule->flags |= RULE_FLAG_OPTION;
456     else if (append)
457         rule->flags |= RULE_FLAG_APPEND;
458     else
459         rule->flags |= RULE_FLAG_NORMAL;
460
461     rule->model = uDupString(names[MODEL]);
462     rule->layout = uDupString(names[LAYOUT]);
463     rule->variant = uDupString(names[VARIANT]);
464     rule->option = uDupString(names[OPTION]);
465
466     rule->keycodes = uDupString(names[KEYCODES]);
467     rule->symbols = uDupString(names[SYMBOLS]);
468     rule->types = uDupString(names[TYPES]);
469     rule->compat = uDupString(names[COMPAT]);
470     rule->keymap = uDupString(names[KEYMAP]);
471
472     rule->layout_num = rule->variant_num = 0;
473     for (i = 0; i < nread; i++) {
474         if (mapping->map[i].index) {
475             if (mapping->map[i].word == LAYOUT)
476                 rule->layout_num = mapping->map[i].index;
477             if (mapping->map[i].word == VARIANT)
478                 rule->variant_num = mapping->map[i].index;
479         }
480     }
481
482     return true;
483 }
484
485 static bool
486 match_line(darray_char *line, struct mapping *mapping,
487            struct rule *rule, struct group *group)
488 {
489     if (darray_item(*line, 0) != '!')
490         return match_rule_line(line, mapping, rule);
491
492     if (darray_item(*line, 1) == '$' ||
493         (darray_item(*line, 1) == ' ' && darray_item(*line, 2) == '$'))
494         return match_group_line(line, group);
495
496     match_mapping_line(line, mapping);
497     return false;
498 }
499
500 static void
501 squeeze_spaces(char *p1)
502 {
503    char *p2;
504
505    for (p2 = p1; *p2; p2++) {
506        *p1 = *p2;
507        if (*p1 != ' ')
508            p1++;
509    }
510
511    *p1 = '\0';
512 }
513
514 /*
515  * Expand the layout and variant of the rule_names and remove extraneous
516  * spaces. If there's one layout/variant, it is kept in
517  * .layout[0]/.variant[0], else is kept in [1], [2] and so on, and [0]
518  * remains empty. For example, this rule_names:
519  *      .model  = "pc105",
520  *      .layout = "us,il,ru,ca"
521  *      .variant = ",,,multix"
522  *      .options = "grp:alts_toggle,   ctrl:nocaps,  compose:rwin"
523  * Is expanded into this multi_defs:
524  *      .model = "pc105"
525  *      .layout = {NULL, "us", "il", "ru", "ca"},
526  *      .variant = {NULL, "", "", "", "multix"},
527  *      .options = "grp:alts_toggle,ctrl:nocaps,compose:rwin"
528  */
529 static bool
530 make_multi_defs(struct multi_defs *mdefs, const struct xkb_rule_names *mlvo)
531 {
532     char *p;
533     int i;
534
535     memset(mdefs, 0, sizeof(*mdefs));
536
537     if (mlvo->model) {
538         mdefs->model = mlvo->model;
539     }
540
541     if (mlvo->options) {
542         mdefs->options = strdup(mlvo->options);
543         if (mdefs->options == NULL)
544             return false;
545
546         squeeze_spaces(mdefs->options);
547     }
548
549     if (mlvo->layout) {
550         if (!strchr(mlvo->layout, ',')) {
551             mdefs->layout[0] = mlvo->layout;
552         }
553         else {
554             p = strdup(mlvo->layout);
555             if (p == NULL)
556                 return false;
557
558             squeeze_spaces(p);
559             mdefs->layout[1] = p;
560
561             for (i = 2; i <= XkbNumKbdGroups; i++) {
562                 if ((p = strchr(p, ','))) {
563                     *p++ = '\0';
564                     mdefs->layout[i] = p;
565                 }
566                 else {
567                     break;
568                 }
569             }
570
571             if (p && (p = strchr(p, ',')))
572                 *p = '\0';
573         }
574     }
575
576     if (mlvo->variant) {
577         if (!strchr(mlvo->variant, ',')) {
578             mdefs->variant[0] = mlvo->variant;
579         }
580         else {
581             p = strdup(mlvo->variant);
582             if (p == NULL)
583                 return false;
584
585             squeeze_spaces(p);
586             mdefs->variant[1] = p;
587
588             for (i = 2; i <= XkbNumKbdGroups; i++) {
589                 if ((p = strchr(p, ','))) {
590                     *p++ = '\0';
591                     mdefs->variant[i] = p;
592                 } else {
593                     break;
594                 }
595             }
596
597             if (p && (p = strchr(p, ',')))
598                 *p = '\0';
599         }
600     }
601
602     return true;
603 }
604
605 static void
606 free_multi_defs(struct multi_defs *defs)
607 {
608     free(defs->options);
609     /*
610      * See make_multi_defs comment for the hack; the same strdup'd
611      * string is split among the indexes, but the one in [0] is const.
612      */
613     free(UNCONSTIFY(defs->layout[1]));
614     free(UNCONSTIFY(defs->variant[1]));
615 }
616
617 /* See apply_rule below. */
618 static void
619 apply(const char *src, char **dst)
620 {
621     int ret;
622     char *tmp;
623
624     if (!src)
625         return;
626
627     if (*src == '+' || *src == '!') {
628         tmp = *dst;
629         ret = asprintf(dst, "%s%s", *dst, src);
630         if (ret < 0)
631             *dst = NULL;
632         free(tmp);
633     }
634     else if (*dst == NULL) {
635         *dst = strdup(src);
636     }
637 }
638
639 /*
640  * Add the info from the matching rule to the resulting
641  * xkb_component_names. If we already had a match for something
642  * (e.g. keycodes), and the rule is not an appending one (e.g.
643  * +whatever), than we don't override but drop the new one.
644  */
645 static void
646 apply_rule(struct rule *rule, struct xkb_component_names *kccgst)
647 {
648     /* Clear the flag because it's applied. */
649     rule->flags &= ~RULE_FLAG_PENDING_MATCH;
650
651     apply(rule->keycodes, &kccgst->keycodes);
652     apply(rule->symbols, &kccgst->symbols);
653     apply(rule->types, &kccgst->types);
654     apply(rule->compat, &kccgst->compat);
655     apply(rule->keymap, &kccgst->keymap);
656 }
657
658 /*
659  * Match if name is part of the group, e.g. if the following
660  * group is defined:
661  *      ! $qwertz = al cz de hr hu ro si sk
662  * then
663  *      match_group_member(rules, "qwertz", "hr")
664  * will return true.
665  */
666 static bool
667 match_group_member(struct rules *rules, const char *group_name,
668                    const char *name)
669 {
670    int i;
671    const char *word;
672    struct group *iter, *group = NULL;
673
674    darray_foreach(iter, rules->groups) {
675        if (strcmp(iter->name, group_name) == 0) {
676            group = iter;
677            break;
678        }
679    }
680
681    if (!group)
682        return false;
683
684    word = group->words;
685    for (i = 0; i < group->number; i++, word += strlen(word) + 1)
686        if (strcmp(word, name) == 0)
687            return true;
688
689    return false;
690 }
691
692 /* Match @needle out of @sep-seperated @haystack. */
693 static bool
694 match_one_of(const char *haystack, const char *needle, char sep)
695 {
696     const char *s = strstr(haystack, needle);
697
698     if (s == NULL)
699         return false;
700
701     if (s != haystack && *s != sep)
702         return false;
703
704     s += strlen(needle);
705     if (*s != '\0' && *s != sep)
706         return false;
707
708     return true;
709 }
710
711 static int
712 apply_rule_if_matches(struct rules *rules, struct rule *rule,
713                       struct multi_defs *mdefs,
714                       struct xkb_component_names *kccgst)
715 {
716     bool pending = false;
717
718     if (rule->model) {
719         if (mdefs->model == NULL)
720             return 0;
721
722         if (strcmp(rule->model, "*") == 0) {
723             pending = true;
724         }
725         else if (rule->model[0] == '$') {
726             if (!match_group_member(rules, rule->model, mdefs->model))
727                 return 0;
728         }
729         else if (strcmp(rule->model, mdefs->model) != 0) {
730             return 0;
731         }
732     }
733
734     if (rule->option) {
735         if (mdefs->options == NULL)
736             return 0;
737
738         if (!match_one_of(mdefs->options, rule->option, ','))
739             return 0;
740     }
741
742     if (rule->layout) {
743         if (mdefs->layout[rule->layout_num] == NULL)
744             return 0;
745
746         if (strcmp(rule->layout, "*") == 0) {
747             pending = true;
748         }
749         else if (rule->layout[0] == '$') {
750             if (!match_group_member(rules, rule->layout,
751                                     mdefs->layout[rule->layout_num]))
752                   return 0;
753         }
754         else if (strcmp(rule->layout,
755                         mdefs->layout[rule->layout_num]) != 0) {
756             return 0;
757         }
758     }
759
760     if (rule->variant) {
761         if (mdefs->variant[rule->variant_num] == NULL)
762             return 0;
763
764         if (strcmp(rule->variant, "*") == 0) {
765             pending = true;
766         } else if (rule->variant[0] == '$') {
767             if (!match_group_member(rules, rule->variant,
768                                     mdefs->variant[rule->variant_num]))
769                 return 0;
770         }
771         else if (strcmp(rule->variant,
772                         mdefs->variant[rule->variant_num]) != 0) {
773             return 0;
774         }
775     }
776
777     if (pending) {
778         rule->flags |= RULE_FLAG_PENDING_MATCH;
779     } else {
780         /* Exact match, apply it now. */
781         apply_rule(rule, kccgst);
782     }
783
784     return rule->number;
785 }
786
787 static void
788 clear_partial_matches(struct rules *rules)
789 {
790     struct rule *rule;
791
792     darray_foreach(rule, rules->rules)
793         rule->flags &= ~RULE_FLAG_PENDING_MATCH;
794 }
795
796 static void
797 apply_partial_matches(struct rules *rules, struct xkb_component_names *kccgst)
798 {
799     struct rule *rule;
800
801     darray_foreach(rule, rules->rules)
802         if (rule->flags & RULE_FLAG_PENDING_MATCH)
803             apply_rule(rule, kccgst);
804 }
805
806 static void
807 apply_matching_rules(struct rules *rules, struct multi_defs *mdefs,
808                      struct xkb_component_names *kccgst, unsigned int flags)
809 {
810     int skip = -1;
811     struct rule *rule;
812
813     darray_foreach(rule, rules->rules) {
814         if ((rule->flags & flags) != flags)
815             continue;
816
817         if ((flags & RULE_FLAG_OPTION) == 0 && rule->number == skip)
818             continue;
819
820         skip = apply_rule_if_matches(rules, rule, mdefs, kccgst);
821     }
822 }
823
824 /***====================================================================***/
825
826 static char *
827 substitute_vars(char *name, struct multi_defs *mdefs)
828 {
829     char *str, *outstr, *var;
830     char *orig = name;
831     size_t len, extra_len;
832     char pfx, sfx;
833     int ndx;
834
835     if (!name)
836         return NULL;
837
838     str = strchr(name, '%');
839     if (str == NULL)
840         return name;
841
842     len = strlen(name);
843
844     while (str != NULL) {
845         pfx = str[1];
846         extra_len = 0;
847
848         if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
849             extra_len = 1;
850             str++;
851         }
852         else if (pfx == '(') {
853             extra_len = 2;
854             str++;
855         }
856
857         var = str + 1;
858         str = get_index(var + 1, &ndx);
859         if (ndx == -1) {
860             str = strchr(str, '%');
861             continue;
862         }
863
864         if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx])
865             len += strlen(mdefs->layout[ndx]) + extra_len;
866         else if (*var == 'm' && mdefs->model)
867             len += strlen(mdefs->model) + extra_len;
868         else if (*var == 'v' && mdefs->variant[ndx] && *mdefs->variant[ndx])
869             len += strlen(mdefs->variant[ndx]) + extra_len;
870
871         if (pfx == '(' && *str == ')')
872             str++;
873
874         str = strchr(&str[0], '%');
875     }
876
877     name = malloc(len + 1);
878     str = orig;
879     outstr = name;
880
881     while (*str != '\0') {
882         if (str[0] == '%') {
883             str++;
884             pfx = str[0];
885             sfx = '\0';
886
887             if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
888                 str++;
889             }
890             else if (pfx == '(') {
891                 sfx = ')';
892                 str++;
893             }
894             else {
895                 pfx = '\0';
896             }
897
898             var = str;
899             str = get_index(var + 1, &ndx);
900             if (ndx == -1)
901                 continue;
902
903             if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx]) {
904                 if (pfx)
905                     *outstr++ = pfx;
906
907                 strcpy(outstr, mdefs->layout[ndx]);
908                 outstr += strlen(mdefs->layout[ndx]);
909
910                 if (sfx)
911                     *outstr++ = sfx;
912             }
913             else if (*var == 'm' && mdefs->model) {
914                 if (pfx)
915                     *outstr++ = pfx;
916
917                 strcpy(outstr, mdefs->model);
918                 outstr += strlen(mdefs->model);
919
920                 if (sfx)
921                     *outstr++ = sfx;
922             }
923             else if (*var == 'v' && mdefs->variant[ndx] && *mdefs->variant[ndx]) {
924                 if (pfx)
925                     *outstr++ = pfx;
926
927                 strcpy(outstr, mdefs->variant[ndx]);
928                 outstr += strlen(mdefs->variant[ndx]);
929
930                 if (sfx)
931                     *outstr++ = sfx;
932             }
933
934             if (pfx == '(' && *str == ')')
935                 str++;
936         }
937         else {
938             *outstr++= *str++;
939         }
940     }
941
942     *outstr++= '\0';
943
944     if (orig != name)
945         free(orig);
946
947     return name;
948 }
949
950 /***====================================================================***/
951
952 static bool
953 get_components(struct rules *rules, const struct xkb_rule_names *mlvo,
954                struct xkb_component_names *kccgst)
955 {
956     struct multi_defs mdefs;
957
958     make_multi_defs(&mdefs, mlvo);
959
960     clear_partial_matches(rules);
961
962     apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_NORMAL);
963     apply_partial_matches(rules, kccgst);
964
965     apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_APPEND);
966     apply_partial_matches(rules, kccgst);
967
968     apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_OPTION);
969     apply_partial_matches(rules, kccgst);
970
971     kccgst->keycodes = substitute_vars(kccgst->keycodes, &mdefs);
972     kccgst->symbols = substitute_vars(kccgst->symbols, &mdefs);
973     kccgst->types = substitute_vars(kccgst->types, &mdefs);
974     kccgst->compat = substitute_vars(kccgst->compat, &mdefs);
975     kccgst->keymap = substitute_vars(kccgst->keymap, &mdefs);
976
977     free_multi_defs(&mdefs);
978
979     return (kccgst->keycodes && kccgst->symbols && kccgst->types &&
980             kccgst->compat) || kccgst->keymap;
981 }
982
983 static struct rules *
984 load_rules(FILE *file)
985 {
986     darray_char line;
987     struct mapping mapping;
988     struct rule trule;
989     struct group tgroup;
990     struct rules *rules;
991
992     rules = calloc(1, sizeof(*rules));
993     if (!rules)
994         return NULL;
995     darray_init(rules->rules);
996     darray_growalloc(rules->rules, 16);
997
998     memset(&mapping, 0, sizeof(mapping));
999     memset(&tgroup, 0, sizeof(tgroup));
1000     darray_init(line);
1001     darray_growalloc(line, 128);
1002
1003     while (input_line_get(file, &line)) {
1004         if (match_line(&line, &mapping, &trule, &tgroup)) {
1005             if (tgroup.number) {
1006                 darray_append(rules->groups, tgroup);
1007                 memset(&tgroup, 0, sizeof(tgroup));
1008             } else {
1009                 darray_append(rules->rules, trule);
1010                 memset(&trule, 0, sizeof(trule));
1011             }
1012         }
1013
1014         darray_resize(line, 0);
1015     }
1016
1017     darray_free(line);
1018     return rules;
1019 }
1020
1021 static void
1022 free_rules(struct rules *rules)
1023 {
1024     struct rule *rule;
1025     struct group *group;
1026
1027     if (!rules)
1028         return;
1029
1030     darray_foreach(rule, rules->rules) {
1031         free(rule->model);
1032         free(rule->layout);
1033         free(rule->variant);
1034         free(rule->option);
1035         free(rule->keycodes);
1036         free(rule->symbols);
1037         free(rule->types);
1038         free(rule->compat);
1039         free(rule->keymap);
1040     }
1041     darray_free(rules->rules);
1042
1043     darray_foreach(group, rules->groups) {
1044         free(group->name);
1045         free(group->words);
1046     }
1047     darray_free(rules->groups);
1048
1049     free(rules);
1050 }
1051
1052 struct xkb_component_names *
1053 xkb_components_from_rules(struct xkb_context *ctx,
1054                           const struct xkb_rule_names *rmlvo)
1055 {
1056     int i;
1057     FILE *file;
1058     char *path;
1059     struct rules *rules;
1060     struct xkb_component_names *kccgst = NULL;
1061
1062     file = XkbFindFileInPath(ctx, rmlvo->rules, XkmRulesFile, &path);
1063     if (!file) {
1064         ERROR("could not find \"%s\" rules in XKB path\n", rmlvo->rules);
1065         ERROR("%d include paths searched:\n",
1066               xkb_context_num_include_paths(ctx));
1067         for (i = 0; i < xkb_context_num_include_paths(ctx); i++)
1068             ERROR("\t%s\n", xkb_context_include_path_get(ctx, i));
1069         return NULL;
1070     }
1071
1072     rules = load_rules(file);
1073     if (!rules) {
1074         ERROR("failed to load XKB rules \"%s\"\n", path);
1075         goto err;
1076     }
1077
1078     kccgst = calloc(1, sizeof(*kccgst));
1079     if (!kccgst) {
1080         ERROR("failed to allocate XKB components\n");
1081         goto err;
1082     }
1083
1084     if (!get_components(rules, rmlvo, kccgst)) {
1085         free(kccgst->keymap);
1086         free(kccgst->keycodes);
1087         free(kccgst->types);
1088         free(kccgst->compat);
1089         free(kccgst->symbols);
1090         free(kccgst);
1091         kccgst = NULL;
1092         ERROR("no components returned from XKB rules \"%s\"\n", path);
1093         goto err;
1094     }
1095
1096 err:
1097     free_rules(rules);
1098     if (file)
1099         fclose(file);
1100     free(path);
1101     return kccgst;
1102 }