02d7b2335230a3d332319165c4a704fd9e2ded5f
[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 #include <fcntl.h>
30 #include <unistd.h>
31 #include <sys/mman.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34
35 #include "rules.h"
36 #include "path.h"
37
38 static const char *
39 input_line_get(struct xkb_context *ctx, const char *buf, const char *end,
40                darray_char *line)
41 {
42     int ch;
43     bool space_pending;
44     bool slash_pending;
45     bool in_comment;
46
47     while (buf < end && darray_empty(*line)) {
48         space_pending = slash_pending = in_comment = false;
49
50         while (buf < end && (ch = *buf++) != '\n') {
51             if (ch == '\\') {
52                 buf++;
53                 if (buf >= end)
54                     break;
55                 ch = *buf;
56
57                 if (ch == '\n') {
58                     in_comment = false;
59                     ch = ' ';
60                 }
61             }
62
63             if (in_comment)
64                 continue;
65
66             if (ch == '/') {
67                 if (slash_pending) {
68                     in_comment = true;
69                     slash_pending = false;
70                 }
71                 else {
72                     slash_pending = true;
73                 }
74
75                 continue;
76             }
77
78             if (slash_pending) {
79                 if (space_pending) {
80                     darray_append(*line, ' ');
81                     space_pending = false;
82                 }
83
84                 darray_append(*line, '/');
85                 slash_pending = false;
86             }
87
88             if (isspace(ch)) {
89                 while (buf < end && isspace(ch) && ch != '\n')
90                     ch = *buf++;
91
92                 if (buf >= end)
93                     break;
94
95                 if (ch != '\n' && !darray_empty(*line))
96                     space_pending = true;
97
98                 buf--;
99             }
100             else {
101                 if (space_pending) {
102                     darray_append(*line, ' ');
103                     space_pending = false;
104                 }
105
106                 if (ch == '!') {
107                     if (!darray_empty(*line)) {
108                         log_warn(ctx,
109                                  "The '!' is legal only at start of line; "
110                                  "Line containing '!' ignored\n");
111                         darray_resize(*line, 0);
112                         break;
113                     }
114                 }
115
116                 darray_append(*line, ch);
117             }
118         }
119     }
120
121     if (darray_empty(*line) && buf >= end)
122         return NULL;
123
124     darray_append(*line, '\0');
125     return buf;
126 }
127
128 /***====================================================================***/
129
130 enum {
131     /* "Parts" - the MLVO which rules file maps to components. */
132     MODEL = 0,
133     LAYOUT,
134     VARIANT,
135     OPTION,
136
137 #define PART_MASK \
138     ((1 << MODEL) | (1 << LAYOUT) | (1 << VARIANT) | (1 << OPTION))
139
140     /* Components */
141     KEYCODES,
142     SYMBOLS,
143     TYPES,
144     COMPAT,
145     GEOMETRY,
146
147 #define COMPONENT_MASK \
148     ((1 << KEYCODES) | (1 << SYMBOLS) | (1 << TYPES) | (1 << COMPAT) | \
149      (1 << GEOMETRY))
150
151     MAX_WORDS
152 };
153
154 static const char *cname[] = {
155     [MODEL] = "model",
156     [LAYOUT] = "layout",
157     [VARIANT] = "variant",
158     [OPTION] = "option",
159
160     [KEYCODES] = "keycodes",
161     [SYMBOLS] = "symbols",
162     [TYPES] = "types",
163     [COMPAT] = "compat",
164     [GEOMETRY] = "geometry",
165 };
166
167 struct multi_defs {
168     const char *model;
169     const char *layout[XkbNumKbdGroups + 1];
170     const char *variant[XkbNumKbdGroups + 1];
171     char *options;
172 };
173
174 struct mapping {
175     /* Sequential id for the mappings. */
176     int number;
177     size_t num_maps;
178
179     struct {
180         int word;
181         int index;
182     } map[MAX_WORDS];
183 };
184
185 struct var_desc {
186     char *name;
187     char *desc;
188 };
189
190 struct group {
191     int number;
192     char *name;
193     char *words;
194 };
195
196 enum rule_flag {
197     RULE_FLAG_PENDING_MATCH = (1L << 1),
198     RULE_FLAG_OPTION = (1L << 2),
199     RULE_FLAG_APPEND = (1L << 3),
200     RULE_FLAG_NORMAL = (1L << 4),
201 };
202
203 struct rule {
204     int number;
205
206     char *model;
207     char *layout;
208     int layout_num;
209     char *variant;
210     int variant_num;
211     char *option;
212
213     /* yields */
214
215     char *keycodes;
216     char *symbols;
217     char *types;
218     char *compat;
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     }
243     else if (empty > 0) {
244         *ndx = -1;
245     }
246     else {
247         *ndx = 0;
248     }
249
250     return str;
251 }
252
253 /*
254  * Match a mapping line which opens a rule, e.g:
255  * ! model      layout[4]       variant[4]      =       symbols       geometry
256  * Which will be followed by lines such as:
257  *   *          ben             basic           =       +in(ben):4    nec(pc98)
258  * So if the MLVO matches the LHS of some line, we'll get the components
259  * on the RHS.
260  * In this example, we will get for the second and fourth columns:
261  * mapping->map[1] = {.word = LAYOUT, .index = 4}
262  * mapping->map[3] = {.word = SYMBOLS, .index = 0}
263  */
264 static void
265 match_mapping_line(struct xkb_context *ctx, darray_char *line,
266                    struct mapping *mapping)
267 {
268     char *tok;
269     char *str = darray_mem(*line, 1);
270     unsigned present = 0, layout_ndx_present = 0, variant_ndx_present = 0;
271     int i, tmp;
272     size_t len;
273     int ndx;
274     char *strtok_buf;
275     bool found;
276
277     /*
278      * Remember the last sequential mapping id (incremented if the match
279      * is successful).
280      */
281     tmp = mapping->number;
282     memset(mapping, 0, sizeof(*mapping));
283     mapping->number = tmp;
284
285     while ((tok = strtok_r(str, " ", &strtok_buf)) != NULL) {
286         found = false;
287         str = NULL;
288
289         if (streq(tok, "="))
290             continue;
291
292         for (i = 0; i < MAX_WORDS; i++) {
293             len = strlen(cname[i]);
294
295             if (strncmp(cname[i], tok, len) == 0) {
296                 if (strlen(tok) > len) {
297                     char *end = get_index(tok + len, &ndx);
298
299                     if ((i != LAYOUT && i != VARIANT) ||
300                         *end != '\0' || ndx == -1) {
301                         log_warn(ctx,
302                                  "Illegal %s index: %d\n", cname[i], ndx);
303                         log_warn(ctx, "Can only index layout and variant\n");
304                         break;
305                     }
306
307                     if (ndx < 1 || ndx > XkbNumKbdGroups) {
308                         log_warn(ctx, "Illegal %s index: %d\n",
309                                  cname[i], ndx);
310                         log_warn(ctx, "Index must be in range 1..%d\n",
311                                  XkbNumKbdGroups);
312                         break;
313                     }
314                 }
315                 else {
316                     ndx = 0;
317                 }
318
319                 found = true;
320
321                 if (present & (1 << i)) {
322                     if ((i == LAYOUT && layout_ndx_present & (1 << ndx)) ||
323                         (i == VARIANT && variant_ndx_present & (1 << ndx))) {
324                         log_warn(ctx,
325                                  "Component \"%s\" listed twice; "
326                                  "Second definition ignored\n", tok);
327                         break;
328                     }
329                 }
330
331                 present |= (1 << i);
332                 if (i == LAYOUT)
333                     layout_ndx_present |= 1 << ndx;
334                 if (i == VARIANT)
335                     variant_ndx_present |= 1 << ndx;
336
337                 mapping->map[mapping->num_maps].word = i;
338                 mapping->map[mapping->num_maps].index = ndx;
339                 mapping->num_maps++;
340                 break;
341             }
342         }
343
344         if (!found)
345             log_warn(ctx, "Unknown component \"%s\"; Ignored\n", tok);
346     }
347
348     if ((present & PART_MASK) == 0) {
349         log_warn(ctx,
350                  "Mapping needs at least one MLVO part; "
351                  "Illegal mapping ignored\n");
352         mapping->num_maps = 0;
353         return;
354     }
355
356     if ((present & COMPONENT_MASK) == 0) {
357         log_warn(ctx,
358                  "Mapping needs at least one component; "
359                  "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_mem(*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(struct xkb_context *ctx, darray_char *line,
410                 struct mapping *mapping, 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         log_warn(ctx,
420                  "Must have a mapping before first line of data; "
421                  "Illegal line of data ignored\n");
422         return false;
423     }
424
425     str = darray_mem(*line, 0);
426
427     for (nread = 0; (tok = strtok_r(str, " ", &strtok_buf)) != NULL;
428          nread++) {
429         str = NULL;
430
431         if (streq(tok, "=")) {
432             nread--;
433             continue;
434         }
435
436         if (nread > mapping->num_maps) {
437             log_warn(ctx,
438                      "Too many words on a line; "
439                      "Extra word \"%s\" ignored\n", tok);
440             continue;
441         }
442
443         names[mapping->map[nread].word] = tok;
444         if (*tok == '+' || *tok == '|')
445             append = true;
446     }
447
448     if (nread < mapping->num_maps) {
449         log_warn(ctx, "Too few words on a line: %s; Line ignored\n",
450                  darray_mem(*line, 0));
451         return false;
452     }
453
454     rule->flags = 0;
455     rule->number = mapping->number;
456
457     if (names[OPTION])
458         rule->flags |= RULE_FLAG_OPTION;
459     else if (append)
460         rule->flags |= RULE_FLAG_APPEND;
461     else
462         rule->flags |= RULE_FLAG_NORMAL;
463
464     rule->model = strdup_safe(names[MODEL]);
465     rule->layout = strdup_safe(names[LAYOUT]);
466     rule->variant = strdup_safe(names[VARIANT]);
467     rule->option = strdup_safe(names[OPTION]);
468
469     rule->keycodes = strdup_safe(names[KEYCODES]);
470     rule->symbols = strdup_safe(names[SYMBOLS]);
471     rule->types = strdup_safe(names[TYPES]);
472     rule->compat = strdup_safe(names[COMPAT]);
473
474     rule->layout_num = rule->variant_num = 0;
475     for (i = 0; i < nread; i++) {
476         if (mapping->map[i].index) {
477             if (mapping->map[i].word == LAYOUT)
478                 rule->layout_num = mapping->map[i].index;
479             if (mapping->map[i].word == VARIANT)
480                 rule->variant_num = mapping->map[i].index;
481         }
482     }
483
484     return true;
485 }
486
487 static bool
488 match_line(struct xkb_context *ctx, darray_char *line,
489            struct mapping *mapping, struct rule *rule,
490            struct group *group)
491 {
492     if (darray_item(*line, 0) != '!')
493         return match_rule_line(ctx, line, mapping, rule);
494
495     if (darray_item(*line, 1) == '$' ||
496         (darray_item(*line, 1) == ' ' && darray_item(*line, 2) == '$'))
497         return match_group_line(line, group);
498
499     match_mapping_line(ctx, line, mapping);
500     return false;
501 }
502
503 static void
504 squeeze_spaces(char *p1)
505 {
506     char *p2;
507
508     for (p2 = p1; *p2; p2++) {
509         *p1 = *p2;
510         if (*p1 != ' ')
511             p1++;
512     }
513
514     *p1 = '\0';
515 }
516
517 /*
518  * Expand the layout and variant of the rule_names and remove extraneous
519  * spaces. If there's one layout/variant, it is kept in
520  * .layout[0]/.variant[0], else is kept in [1], [2] and so on, and [0]
521  * remains empty. For example, this rule_names:
522  *      .model  = "pc105",
523  *      .layout = "us,il,ru,ca"
524  *      .variant = ",,,multix"
525  *      .options = "grp:alts_toggle,   ctrl:nocaps,  compose:rwin"
526  * Is expanded into this multi_defs:
527  *      .model = "pc105"
528  *      .layout = {NULL, "us", "il", "ru", "ca"},
529  *      .variant = {NULL, "", "", "", "multix"},
530  *      .options = "grp:alts_toggle,ctrl:nocaps,compose:rwin"
531  */
532 static bool
533 make_multi_defs(struct multi_defs *mdefs, const struct xkb_rule_names *mlvo)
534 {
535     char *p;
536     int i;
537
538     memset(mdefs, 0, sizeof(*mdefs));
539
540     if (mlvo->model) {
541         mdefs->model = mlvo->model;
542     }
543
544     if (mlvo->options) {
545         mdefs->options = strdup(mlvo->options);
546         if (mdefs->options == NULL)
547             return false;
548
549         squeeze_spaces(mdefs->options);
550     }
551
552     if (mlvo->layout) {
553         if (!strchr(mlvo->layout, ',')) {
554             mdefs->layout[0] = mlvo->layout;
555         }
556         else {
557             p = strdup(mlvo->layout);
558             if (p == NULL)
559                 return false;
560
561             squeeze_spaces(p);
562             mdefs->layout[1] = p;
563
564             for (i = 2; i <= XkbNumKbdGroups; i++) {
565                 if ((p = strchr(p, ','))) {
566                     *p++ = '\0';
567                     mdefs->layout[i] = p;
568                 }
569                 else {
570                     break;
571                 }
572             }
573
574             if (p && (p = strchr(p, ',')))
575                 *p = '\0';
576         }
577     }
578
579     if (mlvo->variant) {
580         if (!strchr(mlvo->variant, ',')) {
581             mdefs->variant[0] = mlvo->variant;
582         }
583         else {
584             p = strdup(mlvo->variant);
585             if (p == NULL)
586                 return false;
587
588             squeeze_spaces(p);
589             mdefs->variant[1] = p;
590
591             for (i = 2; i <= XkbNumKbdGroups; i++) {
592                 if ((p = strchr(p, ','))) {
593                     *p++ = '\0';
594                     mdefs->variant[i] = p;
595                 }
596                 else {
597                     break;
598                 }
599             }
600
601             if (p && (p = strchr(p, ',')))
602                 *p = '\0';
603         }
604     }
605
606     return true;
607 }
608
609 static void
610 free_multi_defs(struct multi_defs *defs)
611 {
612     free(defs->options);
613     /*
614      * See make_multi_defs comment for the hack; the same strdup'd
615      * string is split among the indexes, but the one in [0] is const.
616      */
617     free(UNCONSTIFY(defs->layout[1]));
618     free(UNCONSTIFY(defs->variant[1]));
619 }
620
621 /* See apply_rule below. */
622 static void
623 apply(const char *src, char **dst)
624 {
625     int ret;
626     char *tmp;
627
628     if (!src)
629         return;
630
631     if (*src == '+' || *src == '!') {
632         tmp = *dst;
633         ret = asprintf(dst, "%s%s", *dst, src);
634         if (ret < 0)
635             *dst = NULL;
636         free(tmp);
637     }
638     else if (*dst == NULL) {
639         *dst = strdup(src);
640     }
641 }
642
643 /*
644  * Add the info from the matching rule to the resulting
645  * xkb_component_names. If we already had a match for something
646  * (e.g. keycodes), and the rule is not an appending one (e.g.
647  * +whatever), than we don't override but drop the new one.
648  */
649 static void
650 apply_rule(struct rule *rule, struct xkb_component_names *kccgst)
651 {
652     /* Clear the flag because it's applied. */
653     rule->flags &= ~RULE_FLAG_PENDING_MATCH;
654
655     apply(rule->keycodes, &kccgst->keycodes);
656     apply(rule->symbols, &kccgst->symbols);
657     apply(rule->types, &kccgst->types);
658     apply(rule->compat, &kccgst->compat);
659 }
660
661 /*
662  * Match if name is part of the group, e.g. if the following
663  * group is defined:
664  *      ! $qwertz = al cz de hr hu ro si sk
665  * then
666  *      match_group_member(rules, "qwertz", "hr")
667  * will return true.
668  */
669 static bool
670 match_group_member(struct rules *rules, const char *group_name,
671                    const char *name)
672 {
673     int i;
674     const char *word;
675     struct group *iter, *group = NULL;
676
677     darray_foreach(iter, rules->groups) {
678         if (streq(iter->name, group_name)) {
679             group = iter;
680             break;
681         }
682     }
683
684     if (!group)
685         return false;
686
687     word = group->words;
688     for (i = 0; i < group->number; i++, word += strlen(word) + 1)
689         if (streq(word, name))
690             return true;
691
692     return false;
693 }
694
695 /* Match @needle out of @sep-seperated @haystack. */
696 static bool
697 match_one_of(const char *haystack, const char *needle, char sep)
698 {
699     const char *s = haystack;
700     const size_t len = strlen(needle);
701
702     do {
703         if (strncmp(s, needle, len) == 0 && (s[len] == '\0' || s[len] == sep))
704             return true;
705         s = strchr(s, sep);
706     } while (s++);
707
708     return false;
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 (streq(rule->model, "*")) {
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 (!streq(rule->model, mdefs->model)) {
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 (streq(rule->layout, "*")) {
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 (!streq(rule->layout, mdefs->layout[rule->layout_num])) {
755             return 0;
756         }
757     }
758
759     if (rule->variant) {
760         if (mdefs->variant[rule->variant_num] == NULL)
761             return 0;
762
763         if (streq(rule->variant, "*")) {
764             pending = true;
765         }
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 (!streq(rule->variant, mdefs->variant[rule->variant_num])) {
772             return 0;
773         }
774     }
775
776     if (pending) {
777         rule->flags |= RULE_FLAG_PENDING_MATCH;
778     }
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] &&
924                      *mdefs->variant[ndx]) {
925                 if (pfx)
926                     *outstr++ = pfx;
927
928                 strcpy(outstr, mdefs->variant[ndx]);
929                 outstr += strlen(mdefs->variant[ndx]);
930
931                 if (sfx)
932                     *outstr++ = sfx;
933             }
934
935             if (pfx == '(' && *str == ')')
936                 str++;
937         }
938         else {
939             *outstr++ = *str++;
940         }
941     }
942
943     *outstr++ = '\0';
944
945     if (orig != name)
946         free(orig);
947
948     return name;
949 }
950
951 /***====================================================================***/
952
953 static bool
954 get_components(struct rules *rules, const struct xkb_rule_names *mlvo,
955                struct xkb_component_names *kccgst)
956 {
957     struct multi_defs mdefs;
958
959     memset(kccgst, 0, sizeof(*kccgst));
960
961     make_multi_defs(&mdefs, mlvo);
962
963     clear_partial_matches(rules);
964
965     apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_NORMAL);
966     apply_partial_matches(rules, kccgst);
967
968     apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_APPEND);
969     apply_partial_matches(rules, kccgst);
970
971     apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_OPTION);
972     apply_partial_matches(rules, kccgst);
973
974     kccgst->keycodes = substitute_vars(kccgst->keycodes, &mdefs);
975     kccgst->symbols = substitute_vars(kccgst->symbols, &mdefs);
976     kccgst->types = substitute_vars(kccgst->types, &mdefs);
977     kccgst->compat = substitute_vars(kccgst->compat, &mdefs);
978
979     free_multi_defs(&mdefs);
980
981     if (!kccgst->keycodes || !kccgst->symbols || !kccgst->types ||
982         !kccgst->compat) {
983         free(kccgst->keycodes);
984         free(kccgst->symbols);
985         free(kccgst->types);
986         free(kccgst->compat);
987         return false;
988     }
989     return true;
990 }
991
992 static struct rules *
993 load_rules(struct xkb_context *ctx, FILE *file)
994 {
995     darray_char line;
996     struct mapping mapping;
997     struct rule trule;
998     struct group tgroup;
999     struct rules *rules;
1000     struct stat stat_buf;
1001     const char *buf, *end;
1002     char *orig;
1003     int fd = fileno(file);
1004
1005     if (fstat(fd, &stat_buf) != 0) {
1006         log_err(ctx, "couldn't stat rules file\n");
1007         return NULL;
1008     }
1009
1010     orig = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED, fd, 0);
1011     if (!orig) {
1012         log_err(ctx, "couldn't mmap rules file (%zu bytes)\n",
1013                 (size_t) stat_buf.st_size);
1014         return NULL;
1015     }
1016
1017     rules = calloc(1, sizeof(*rules));
1018     if (!rules)
1019         return NULL;
1020     darray_init(rules->rules);
1021     darray_growalloc(rules->rules, 16);
1022
1023     memset(&mapping, 0, sizeof(mapping));
1024     memset(&tgroup, 0, sizeof(tgroup));
1025     darray_init(line);
1026     darray_growalloc(line, 128);
1027
1028     buf = orig;
1029     end = orig + stat_buf.st_size;
1030     while ((buf = input_line_get(ctx, buf, end, &line))) {
1031         if (match_line(ctx, &line, &mapping, &trule, &tgroup)) {
1032             if (tgroup.number) {
1033                 darray_append(rules->groups, tgroup);
1034                 memset(&tgroup, 0, sizeof(tgroup));
1035             }
1036             else {
1037                 darray_append(rules->rules, trule);
1038                 memset(&trule, 0, sizeof(trule));
1039             }
1040         }
1041
1042         darray_resize(line, 0);
1043     }
1044
1045     munmap(orig, stat_buf.st_size);
1046
1047     darray_free(line);
1048     return rules;
1049 }
1050
1051 static void
1052 free_rules(struct rules *rules)
1053 {
1054     struct rule *rule;
1055     struct group *group;
1056
1057     if (!rules)
1058         return;
1059
1060     darray_foreach(rule, rules->rules) {
1061         free(rule->model);
1062         free(rule->layout);
1063         free(rule->variant);
1064         free(rule->option);
1065         free(rule->keycodes);
1066         free(rule->symbols);
1067         free(rule->types);
1068         free(rule->compat);
1069     }
1070     darray_free(rules->rules);
1071
1072     darray_foreach(group, rules->groups) {
1073         free(group->name);
1074         free(group->words);
1075     }
1076     darray_free(rules->groups);
1077
1078     free(rules);
1079 }
1080
1081 bool
1082 xkb_components_from_rules(struct xkb_context *ctx,
1083                           const struct xkb_rule_names *rmlvo,
1084                           struct xkb_component_names *out)
1085 {
1086     bool ret = false;
1087     struct rules *rules;
1088     FILE *file;
1089     char *path;
1090     char **include;
1091
1092     file = XkbFindFileInPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
1093     if (!file) {
1094         log_err(ctx, "Could not find \"%s\" rules in XKB path\n",
1095                 rmlvo->rules);
1096         log_err(ctx, "%zu include paths searched:\n",
1097                 darray_size(ctx->includes));
1098         darray_foreach(include, ctx->includes)
1099             log_err(ctx, "\t%s\n", *include);
1100         log_err(ctx, "%zu include paths could not be added:\n",
1101                 darray_size(ctx->failed_includes));
1102         darray_foreach(include, ctx->failed_includes)
1103             log_err(ctx, "\t%s\n", *include);
1104         return false;
1105     }
1106
1107     rules = load_rules(ctx, file);
1108     if (!rules) {
1109         log_err(ctx, "Failed to load XKB rules \"%s\"\n", path);
1110         goto err_file;
1111     }
1112
1113     if (!get_components(rules, rmlvo, out)) {
1114         log_err(ctx, "No components returned from XKB rules \"%s\"\n", path);
1115         goto err_rules;
1116     }
1117
1118     ret = true;
1119
1120 err_rules:
1121     free_rules(rules);
1122 err_file:
1123     free(path);
1124     fclose(file);
1125     return ret;
1126 }