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