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