Edit

IABSD.fr/xenocara/lib/libxkbfile/src/maprules.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2015-05-10 09:32:36
    Hash : f8db8710
    Message : update to libxkbfile 1.0.9

  • lib/libxkbfile/src/maprules.c
  • /************************************************************
     Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
    
     Permission to use, copy, modify, and distribute this
     software and its documentation for any purpose and without
     fee is hereby granted, provided that the above copyright
     notice appear in all copies and that both that copyright
     notice and this permission notice appear in supporting
     documentation, and that the name of Silicon Graphics not be
     used in advertising or publicity pertaining to distribution
     of the software without specific prior written permission.
     Silicon Graphics makes no representation about the suitability
     of this software for any purpose. It is provided "as is"
     without any express or implied warranty.
    
     SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
     SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
     AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
     GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
     DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
     DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
     OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
     THE USE OR PERFORMANCE OF THIS SOFTWARE.
    
     ********************************************************/
    
    #ifdef HAVE_DIX_CONFIG_H
    #include <dix-config.h>
    #elif defined(HAVE_CONFIG_H)
    #include <config.h>
    #endif
    
    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    
    #define X_INCLUDE_STRING_H
    #define XOS_USE_NO_LOCKING
    #include <X11/Xos_r.h>
    
    
    #include <X11/Xproto.h>
    #include <X11/Xlib.h>
    #include <X11/Xos.h>
    #include <X11/Xfuncs.h>
    #include <X11/Xatom.h>
    #include <X11/keysym.h>
    #include <X11/XKBlib.h>
    #include <X11/extensions/XKBgeom.h>
    #include "XKMformat.h"
    #include "XKBfileInt.h"
    #include "XKBrules.h"
    
    
    #ifdef DEBUG
    #define PR_DEBUG(s)		fprintf(stderr,s)
    #define PR_DEBUG1(s,a)		fprintf(stderr,s,a)
    #define PR_DEBUG2(s,a,b)	fprintf(stderr,s,a,b)
    #else
    #define PR_DEBUG(s)
    #define PR_DEBUG1(s,a)
    #define PR_DEBUG2(s,a,b)
    #endif
    
    /***====================================================================***/
    
    #define DFLT_LINE_SIZE	128
    
    typedef struct {
        int         line_num;
        int         sz_line;
        int         num_line;
        char        buf[DFLT_LINE_SIZE];
        char *      line;
    } InputLine;
    
    static void
    InitInputLine(InputLine *line)
    {
        line->line_num = 1;
        line->num_line = 0;
        line->sz_line = DFLT_LINE_SIZE;
        line->line = line->buf;
        return;
    }
    
    static void
    FreeInputLine(InputLine *line)
    {
        if (line->line != line->buf)
            _XkbFree(line->line);
        line->line_num = 1;
        line->num_line = 0;
        line->sz_line = DFLT_LINE_SIZE;
        line->line = line->buf;
        return;
    }
    
    static int
    InputLineAddChar(InputLine *line, int ch)
    {
        if (line->num_line >= line->sz_line) {
            if (line->line == line->buf) {
                line->line = (char *) _XkbAlloc(line->sz_line * 2);
                memcpy(line->line, line->buf, line->sz_line);
            }
            else {
                line->line =
                    (char *) _XkbRealloc((char *) line->line, line->sz_line * 2);
            }
            line->sz_line *= 2;
        }
        line->line[line->num_line++] = ch;
        return ch;
    }
    
    #define	ADD_CHAR(l,c)	((l)->num_line<(l)->sz_line?\
    				(int)((l)->line[(l)->num_line++]= (c)):\
    				InputLineAddChar(l,c))
    
    #ifdef HAVE_UNLOCKED_STDIO
    #undef getc
    #define getc(x) getc_unlocked(x)
    #else
    #define flockfile(x) do {} while (0)
    #define funlockfile(x) do {} while (0)
    #endif
    
    static Bool
    GetInputLine(FILE *file, InputLine *line, Bool checkbang)
    {
        int ch;
        Bool endOfFile, spacePending, slashPending, inComment;
    
        endOfFile = False;
        flockfile(file);
        while ((!endOfFile) && (line->num_line == 0)) {
            spacePending = slashPending = inComment = False;
            while (((ch = getc(file)) != '\n') && (ch != EOF)) {
                if (ch == '\\') {
                    if ((ch = getc(file)) == EOF)
                        break;
                    if (ch == '\n') {
                        inComment = False;
                        ch = ' ';
                        line->line_num++;
                    }
                }
                if (inComment)
                    continue;
                if (ch == '/') {
                    if (slashPending) {
                        inComment = True;
                        slashPending = False;
                    }
                    else {
                        slashPending = True;
                    }
                    continue;
                }
                else if (slashPending) {
                    if (spacePending) {
                        ADD_CHAR(line, ' ');
                        spacePending = False;
                    }
                    ADD_CHAR(line, '/');
                    slashPending = False;
                }
                if (isspace(ch)) {
                    while (isspace(ch) && (ch != '\n') && (ch != EOF)) {
                        ch = getc(file);
                    }
                    if (ch == EOF)
                        break;
                    if ((ch != '\n') && (line->num_line > 0))
                        spacePending = True;
                    ungetc(ch, file);
                }
                else {
                    if (spacePending) {
                        ADD_CHAR(line, ' ');
                        spacePending = False;
                    }
                    if (checkbang && ch == '!') {
                        if (line->num_line != 0) {
                            PR_DEBUG("The '!' legal only at start of line\n");
                            PR_DEBUG("Line containing '!' ignored\n");
                            line->num_line = 0;
                            inComment = 0;
                            break;
                        }
    
                    }
                    ADD_CHAR(line, ch);
                }
            }
            if (ch == EOF)
                endOfFile = True;
    /*	else line->num_line++;*/
        }
        funlockfile(file);
        if ((line->num_line == 0) && (endOfFile))
            return False;
        ADD_CHAR(line, '\0');
        return True;
    }
    
    /***====================================================================***/
    
    #define	MODEL		0
    #define	LAYOUT		1
    #define	VARIANT		2
    #define	OPTION		3
    #define	KEYCODES	4
    #define SYMBOLS		5
    #define	TYPES		6
    #define	COMPAT		7
    #define	GEOMETRY	8
    #define	KEYMAP		9
    #define	MAX_WORDS	10
    
    #define	PART_MASK	0x000F
    #define	COMPONENT_MASK	0x03F0
    
    static const char *cname[MAX_WORDS] = {
        "model", "layout", "variant", "option",
        "keycodes", "symbols", "types", "compat", "geometry", "keymap"
    };
    
    typedef struct _RemapSpec {
        int number;
        int num_remap;
        struct {
            int word;
            int index;
        } remap[MAX_WORDS];
    } RemapSpec;
    
    typedef struct _FileSpec {
        char *name[MAX_WORDS];
        struct _FileSpec *pending;
    } FileSpec;
    
    typedef struct {
        char *model;
        char *layout[XkbNumKbdGroups + 1];
        char *variant[XkbNumKbdGroups + 1];
        char *options;
    } XkbRF_MultiDefsRec, *XkbRF_MultiDefsPtr;
    
    #define NDX_BUFF_SIZE	4
    
    /***====================================================================***/
    
    static char *
    get_index(char *str, int *ndx)
    {
        char ndx_buf[NDX_BUFF_SIZE];
    
        char *end;
    
        if (*str != '[') {
            *ndx = 0;
            return str;
        }
        str++;
        end = strchr(str, ']');
        if (end == NULL) {
            *ndx = -1;
            return str - 1;
        }
        if ((end - str) >= NDX_BUFF_SIZE) {
            *ndx = -1;
            return end + 1;
        }
        strncpy(ndx_buf, str, end - str);
        ndx_buf[end - str] = '\0';
        *ndx = atoi(ndx_buf);
        return end + 1;
    }
    
    static void
    SetUpRemap(InputLine *line, RemapSpec *remap)
    {
        char *tok, *str;
        unsigned present, l_ndx_present, v_ndx_present;
        register int i;
        int len, ndx;
        _Xstrtokparams strtok_buf;
    
    #ifdef DEBUG
        Bool found;
    #endif
    
        l_ndx_present = v_ndx_present = present = 0;
        str = &line->line[1];
        len = remap->number;
        bzero((char *) remap, sizeof(RemapSpec));
        remap->number = len;
        while ((tok = _XStrtok(str, " ", strtok_buf)) != NULL) {
    #ifdef DEBUG
            found = False;
    #endif
            str = NULL;
            if (strcmp(tok, "=") == 0)
                continue;
            for (i = 0; i < MAX_WORDS; i++) {
                len = strlen(cname[i]);
                if (strncmp(cname[i], tok, len) == 0) {
                    if (strlen(tok) > len) {
                        char *end = get_index(tok + len, &ndx);
    
                        if ((i != LAYOUT && i != VARIANT) ||
                            *end != '\0' || ndx == -1)
                            break;
                        if (ndx < 1 || ndx > XkbNumKbdGroups) {
                            PR_DEBUG2("Illegal %s index: %d\n", cname[i], ndx);
                            PR_DEBUG1("Index must be in range 1..%d\n",
                                      XkbNumKbdGroups);
                            break;
                        }
                    }
                    else {
                        ndx = 0;
                    }
    #ifdef DEBUG
                    found = True;
    #endif
                    if (present & (1 << i)) {
                        if ((i == LAYOUT && l_ndx_present & (1 << ndx)) ||
                            (i == VARIANT && v_ndx_present & (1 << ndx))) {
                            PR_DEBUG1("Component \"%s\" listed twice\n", tok);
                            PR_DEBUG("Second definition ignored\n");
                            break;
                        }
                    }
                    present |= (1 << i);
                    if (i == LAYOUT)
                        l_ndx_present |= 1 << ndx;
                    if (i == VARIANT)
                        v_ndx_present |= 1 << ndx;
                    remap->remap[remap->num_remap].word = i;
                    remap->remap[remap->num_remap++].index = ndx;
                    break;
                }
            }
    #ifdef DEBUG
            if (!found) {
                fprintf(stderr, "Unknown component \"%s\" ignored\n", tok);
            }
    #endif
        }
        if ((present & PART_MASK) == 0) {
    #ifdef DEBUG
            unsigned mask = PART_MASK;
    
            fprintf(stderr, "Mapping needs at least one of ");
            for (i = 0; (i < MAX_WORDS); i++) {
                if ((1L << i) & mask) {
                    mask &= ~(1L << i);
                    if (mask)
                        fprintf(stderr, "\"%s,\" ", cname[i]);
                    else
                        fprintf(stderr, "or \"%s\"\n", cname[i]);
                }
            }
            fprintf(stderr, "Illegal mapping ignored\n");
    #endif
            remap->num_remap = 0;
            return;
        }
        if ((present & COMPONENT_MASK) == 0) {
            PR_DEBUG("Mapping needs at least one component\n");
            PR_DEBUG("Illegal mapping ignored\n");
            remap->num_remap = 0;
            return;
        }
        if (((present & COMPONENT_MASK) & (1 << KEYMAP)) &&
            ((present & COMPONENT_MASK) != (1 << KEYMAP))) {
            PR_DEBUG("Keymap cannot appear with other components\n");
            PR_DEBUG("Illegal mapping ignored\n");
            remap->num_remap = 0;
            return;
        }
        remap->number++;
        return;
    }
    
    static Bool
    MatchOneOf(char *wanted, char *vals_defined)
    {
        char *str, *next;
        int want_len = strlen(wanted);
    
        for (str = vals_defined, next = NULL; str != NULL; str = next) {
            int len;
    
            next = strchr(str, ',');
            if (next) {
                len = next - str;
                next++;
            }
            else {
                len = strlen(str);
            }
            if ((len == want_len) && (strncmp(wanted, str, len) == 0))
                return True;
        }
        return False;
    }
    
    /***====================================================================***/
    
    static Bool
    CheckLine(InputLine *    line,
              RemapSpec *    remap,
              XkbRF_RulePtr  rule,
              XkbRF_GroupPtr group)
    {
        char *str, *tok;
        register int nread, i;
        FileSpec tmp;
        _Xstrtokparams strtok_buf;
        Bool append = False;
    
        if (line->line[0] == '!') {
            if (line->line[1] == '$' ||
                (line->line[1] == ' ' && line->line[2] == '$')) {
                char *gname = strchr(line->line, '$');
                char *words = strchr(gname, ' ');
    
                if (!words)
                    return False;
                *words++ = '\0';
                for (; *words; words++) {
                    if (*words != '=' && *words != ' ')
                        break;
                }
                if (*words == '\0')
                    return False;
                group->name = _XkbDupString(gname);
                group->words = _XkbDupString(words);
                for (i = 1, words = group->words; *words; words++) {
                    if (*words == ' ') {
                        *words++ = '\0';
                        i++;
                    }
                }
                group->number = i;
                return True;
            }
            else {
                SetUpRemap(line, remap);
                return False;
            }
        }
    
        if (remap->num_remap == 0) {
            PR_DEBUG("Must have a mapping before first line of data\n");
            PR_DEBUG("Illegal line of data ignored\n");
            return False;
        }
        bzero((char *) &tmp, sizeof(FileSpec));
        str = line->line;
        for (nread = 0; (tok = _XStrtok(str, " ", strtok_buf)) != NULL; nread++) {
            str = NULL;
            if (strcmp(tok, "=") == 0) {
                nread--;
                continue;
            }
            if (nread > remap->num_remap) {
                PR_DEBUG("Too many words on a line\n");
                PR_DEBUG1("Extra word \"%s\" ignored\n", tok);
                continue;
            }
            tmp.name[remap->remap[nread].word] = tok;
            if (*tok == '+' || *tok == '|')
                append = True;
        }
        if (nread < remap->num_remap) {
            PR_DEBUG1("Too few words on a line: %s\n", line->line);
            PR_DEBUG("line ignored\n");
            return False;
        }
    
        rule->flags = 0;
        rule->number = remap->number;
        if (tmp.name[OPTION])
            rule->flags |= XkbRF_Option;
        else if (append)
            rule->flags |= XkbRF_Append;
        else
            rule->flags |= XkbRF_Normal;
        rule->model = _XkbDupString(tmp.name[MODEL]);
        rule->layout = _XkbDupString(tmp.name[LAYOUT]);
        rule->variant = _XkbDupString(tmp.name[VARIANT]);
        rule->option = _XkbDupString(tmp.name[OPTION]);
    
        rule->keycodes = _XkbDupString(tmp.name[KEYCODES]);
        rule->symbols = _XkbDupString(tmp.name[SYMBOLS]);
        rule->types = _XkbDupString(tmp.name[TYPES]);
        rule->compat = _XkbDupString(tmp.name[COMPAT]);
        rule->geometry = _XkbDupString(tmp.name[GEOMETRY]);
        rule->keymap = _XkbDupString(tmp.name[KEYMAP]);
    
        rule->layout_num = rule->variant_num = 0;
        for (i = 0; i < nread; i++) {
            if (remap->remap[i].index) {
                if (remap->remap[i].word == LAYOUT)
                    rule->layout_num = remap->remap[i].index;
                if (remap->remap[i].word == VARIANT)
                    rule->variant_num = remap->remap[i].index;
            }
        }
        return True;
    }
    
    static char *
    _Concat(char *str1, char *str2)
    {
        int len;
    
        if ((!str1) || (!str2))
            return str1;
        len = strlen(str1) + strlen(str2) + 1;
        str1 = _XkbTypedRealloc(str1, len, char);
        if (str1)
            strcat(str1, str2);
        return str1;
    }
    
    static void
    squeeze_spaces(char *p1)
    {
        char *p2;
    
        for (p2 = p1; *p2; p2++) {
            *p1 = *p2;
            if (*p1 != ' ')
                p1++;
        }
        *p1 = '\0';
    }
    
    static Bool
    MakeMultiDefs(XkbRF_MultiDefsPtr mdefs, XkbRF_VarDefsPtr defs)
    {
    
        bzero((char *) mdefs, sizeof(XkbRF_MultiDefsRec));
        mdefs->model = defs->model;
        mdefs->options = _XkbDupString(defs->options);
        if (mdefs->options)
            squeeze_spaces(mdefs->options);
    
        if (defs->layout) {
            if (!strchr(defs->layout, ',')) {
                mdefs->layout[0] = defs->layout;
            }
            else {
                char *p;
    
                int i;
    
                mdefs->layout[1] = _XkbDupString(defs->layout);
                if (mdefs->layout[1] == NULL)
                    return False;
                squeeze_spaces(mdefs->layout[1]);
                p = mdefs->layout[1];
                for (i = 2; i <= XkbNumKbdGroups; i++) {
                    if ((p = strchr(p, ','))) {
                        *p++ = '\0';
                        mdefs->layout[i] = p;
                    }
                    else {
                        break;
                    }
                }
                if (p && (p = strchr(p, ',')))
                    *p = '\0';
            }
        }
    
        if (defs->variant) {
            if (!strchr(defs->variant, ',')) {
                mdefs->variant[0] = defs->variant;
            }
            else {
                char *p;
    
                int i;
    
                mdefs->variant[1] = _XkbDupString(defs->variant);
                if (mdefs->variant[1] == NULL)
                    return False;
                squeeze_spaces(mdefs->variant[1]);
                p = mdefs->variant[1];
                for (i = 2; i <= XkbNumKbdGroups; i++) {
                    if ((p = strchr(p, ','))) {
                        *p++ = '\0';
                        mdefs->variant[i] = p;
                    }
                    else {
                        break;
                    }
                }
                if (p && (p = strchr(p, ',')))
                    *p = '\0';
            }
        }
        return True;
    }
    
    static void
    FreeMultiDefs(XkbRF_MultiDefsPtr defs)
    {
        if (defs->options)
            _XkbFree(defs->options);
        if (defs->layout[1])
            _XkbFree(defs->layout[1]);
        if (defs->variant[1])
            _XkbFree(defs->variant[1]);
    }
    
    static void
    Apply(char *src, char **dst)
    {
        if (src) {
            if (*src == '+' || *src == '!') {
                *dst = _Concat(*dst, src);
            }
            else {
                if (*dst == NULL)
                    *dst = _XkbDupString(src);
            }
        }
    }
    
    static void
    XkbRF_ApplyRule(XkbRF_RulePtr rule, XkbComponentNamesPtr names)
    {
        rule->flags &= ~XkbRF_PendingMatch; /* clear the flag because it's applied */
    
        Apply(rule->keycodes, &names->keycodes);
        Apply(rule->symbols,  &names->symbols);
        Apply(rule->types,    &names->types);
        Apply(rule->compat,   &names->compat);
        Apply(rule->geometry, &names->geometry);
        Apply(rule->keymap,   &names->keymap);
    }
    
    static Bool
    CheckGroup(XkbRF_RulesPtr rules, char *group_name, char *name)
    {
        int i;
        char *p;
        XkbRF_GroupPtr group;
    
        for (i = 0, group = rules->groups; i < rules->num_groups; i++, group++) {
            if (!strcmp(group->name, group_name)) {
                break;
            }
        }
        if (i == rules->num_groups)
            return False;
        for (i = 0, p = group->words; i < group->number; i++, p += strlen(p) + 1) {
            if (!strcmp(p, name)) {
                return True;
            }
        }
        return False;
    }
    
    static int
    XkbRF_CheckApplyRule(XkbRF_RulePtr        rule,
                         XkbRF_MultiDefsPtr   mdefs,
                         XkbComponentNamesPtr names,
                         XkbRF_RulesPtr       rules)
    {
        Bool pending = False;
    
        if (rule->model != NULL) {
            if (mdefs->model == NULL)
                return 0;
            if (strcmp(rule->model, "*") == 0) {
                pending = True;
            }
            else {
                if (rule->model[0] == '$') {
                    if (!CheckGroup(rules, rule->model, mdefs->model))
                        return 0;
                }
                else {
                    if (strcmp(rule->model, mdefs->model) != 0)
                        return 0;
                }
            }
        }
        if (rule->option != NULL) {
            if (mdefs->options == NULL)
                return 0;
            if ((!MatchOneOf(rule->option, mdefs->options)))
                return 0;
        }
    
        if (rule->layout != NULL) {
            if (mdefs->layout[rule->layout_num] == NULL ||
                *mdefs->layout[rule->layout_num] == '\0')
                return 0;
            if (strcmp(rule->layout, "*") == 0) {
                pending = True;
            }
            else {
                if (rule->layout[0] == '$') {
                    if (!CheckGroup(rules, rule->layout,
                                    mdefs->layout[rule->layout_num]))
                        return 0;
                }
                else {
                    if (strcmp(rule->layout, mdefs->layout[rule->layout_num]) != 0)
                        return 0;
                }
            }
        }
        if (rule->variant != NULL) {
            if (mdefs->variant[rule->variant_num] == NULL ||
                *mdefs->variant[rule->variant_num] == '\0')
                return 0;
            if (strcmp(rule->variant, "*") == 0) {
                pending = True;
            }
            else {
                if (rule->variant[0] == '$') {
                    if (!CheckGroup(rules, rule->variant,
                                    mdefs->variant[rule->variant_num]))
                        return 0;
                }
                else {
                    if (strcmp(rule->variant,
                               mdefs->variant[rule->variant_num]) != 0)
                        return 0;
                }
            }
        }
        if (pending) {
            rule->flags |= XkbRF_PendingMatch;
            return rule->number;
        }
        /* exact match, apply it now */
        XkbRF_ApplyRule(rule, names);
        return rule->number;
    }
    
    static void
    XkbRF_ClearPartialMatches(XkbRF_RulesPtr rules)
    {
        register int i;
        XkbRF_RulePtr rule;
    
        for (i = 0, rule = rules->rules; i < rules->num_rules; i++, rule++) {
            rule->flags &= ~XkbRF_PendingMatch;
        }
    }
    
    static void
    XkbRF_ApplyPartialMatches(XkbRF_RulesPtr rules, XkbComponentNamesPtr names)
    {
        int i;
        XkbRF_RulePtr rule;
    
        for (rule = rules->rules, i = 0; i < rules->num_rules; i++, rule++) {
            if ((rule->flags & XkbRF_PendingMatch) == 0)
                continue;
            XkbRF_ApplyRule(rule, names);
        }
    }
    
    static void
    XkbRF_CheckApplyRules(XkbRF_RulesPtr       rules,
                          XkbRF_MultiDefsPtr   mdefs,
                          XkbComponentNamesPtr names,
                          int                  flags)
    {
        int i;
        XkbRF_RulePtr rule;
        int skip;
    
        for (rule = rules->rules, i = 0; i < rules->num_rules; rule++, i++) {
            if ((rule->flags & flags) != flags)
                continue;
            skip = XkbRF_CheckApplyRule(rule, mdefs, names, rules);
            if (skip && !(flags & XkbRF_Option)) {
                for (; (i < rules->num_rules) && (rule->number == skip);
                     rule++, i++);
                rule--;
                i--;
            }
        }
    }
    
    /***====================================================================***/
    
    static char *
    XkbRF_SubstituteVars(char *name, XkbRF_MultiDefsPtr mdefs)
    {
        char *str, *outstr, *orig, *var;
        int len, ndx;
    
        orig = name;
        str = index(name, '%');
        if (str == NULL)
            return name;
        len = strlen(name);
        while (str != NULL) {
            char pfx = str[1];
            int extra_len = 0;
    
            if ((pfx == '+') || (pfx == '|') || (pfx == '_') || (pfx == '-')) {
                extra_len = 1;
                str++;
            }
            else if (pfx == '(') {
                extra_len = 2;
                str++;
            }
            var = str + 1;
            str = get_index(var + 1, &ndx);
            if (ndx == -1) {
                str = index(str, '%');
                continue;
            }
            if ((*var == 'l') && mdefs->layout[ndx] && *mdefs->layout[ndx])
                len += strlen(mdefs->layout[ndx]) + extra_len;
            else if ((*var == 'm') && mdefs->model)
                len += strlen(mdefs->model) + extra_len;
            else if ((*var == 'v') && mdefs->variant[ndx] && *mdefs->variant[ndx])
                len += strlen(mdefs->variant[ndx]) + extra_len;
            if ((pfx == '(') && (*str == ')')) {
                str++;
            }
            str = index(&str[0], '%');
        }
        name = (char *) _XkbAlloc(len + 1);
        str = orig;
        outstr = name;
        while (*str != '\0') {
            if (str[0] == '%') {
                char pfx, sfx;
    
                str++;
                pfx = str[0];
                sfx = '\0';
                if ((pfx == '+') || (pfx == '|') || (pfx == '_') || (pfx == '-')) {
                    str++;
                }
                else if (pfx == '(') {
                    sfx = ')';
                    str++;
                }
                else
                    pfx = '\0';
    
                var = str;
                str = get_index(var + 1, &ndx);
                if (ndx == -1) {
                    continue;
                }
                if ((*var == 'l') && mdefs->layout[ndx] && *mdefs->layout[ndx]) {
                    if (pfx)
                        *outstr++ = pfx;
                    strcpy(outstr, mdefs->layout[ndx]);
                    outstr += strlen(mdefs->layout[ndx]);
                    if (sfx)
                        *outstr++ = sfx;
                }
                else if ((*var == 'm') && (mdefs->model)) {
                    if (pfx)
                        *outstr++ = pfx;
                    strcpy(outstr, mdefs->model);
                    outstr += strlen(mdefs->model);
                    if (sfx)
                        *outstr++ = sfx;
                }
                else if ((*var == 'v') && mdefs->variant[ndx] &&
                         *mdefs->variant[ndx]) {
                    if (pfx)
                        *outstr++ = pfx;
                    strcpy(outstr, mdefs->variant[ndx]);
                    outstr += strlen(mdefs->variant[ndx]);
                    if (sfx)
                        *outstr++ = sfx;
                }
                if ((pfx == '(') && (*str == ')'))
                    str++;
            }
            else {
                *outstr++ = *str++;
            }
        }
        *outstr++ = '\0';
        if (orig != name)
            _XkbFree(orig);
        return name;
    }
    
    /***====================================================================***/
    
    Bool
    XkbRF_GetComponents(XkbRF_RulesPtr       rules,
                        XkbRF_VarDefsPtr     defs,
                        XkbComponentNamesPtr names)
    {
        XkbRF_MultiDefsRec mdefs;
    
        MakeMultiDefs(&mdefs, defs);
    
        bzero((char *) names, sizeof(XkbComponentNamesRec));
        XkbRF_ClearPartialMatches(rules);
        XkbRF_CheckApplyRules(rules, &mdefs, names, XkbRF_Normal);
        XkbRF_ApplyPartialMatches(rules, names);
        XkbRF_CheckApplyRules(rules, &mdefs, names, XkbRF_Append);
        XkbRF_ApplyPartialMatches(rules, names);
        XkbRF_CheckApplyRules(rules, &mdefs, names, XkbRF_Option);
        XkbRF_ApplyPartialMatches(rules, names);
    
        if (names->keycodes)
            names->keycodes = XkbRF_SubstituteVars(names->keycodes, &mdefs);
        if (names->symbols)
            names->symbols  = XkbRF_SubstituteVars(names->symbols, &mdefs);
        if (names->types)
            names->types    = XkbRF_SubstituteVars(names->types, &mdefs);
        if (names->compat)
            names->compat   = XkbRF_SubstituteVars(names->compat, &mdefs);
        if (names->geometry)
            names->geometry = XkbRF_SubstituteVars(names->geometry, &mdefs);
        if (names->keymap)
            names->keymap   = XkbRF_SubstituteVars(names->keymap, &mdefs);
    
        FreeMultiDefs(&mdefs);
        return (names->keycodes && names->symbols && names->types &&
                names->compat && names->geometry) || names->keymap;
    }
    
    XkbRF_RulePtr
    XkbRF_AddRule(XkbRF_RulesPtr rules)
    {
        if (rules->sz_rules < 1) {
            rules->sz_rules = 16;
            rules->num_rules = 0;
            rules->rules = _XkbTypedCalloc(rules->sz_rules, XkbRF_RuleRec);
        }
        else if (rules->num_rules >= rules->sz_rules) {
            rules->sz_rules *= 2;
            rules->rules = _XkbTypedRealloc(rules->rules, rules->sz_rules,
                                            XkbRF_RuleRec);
        }
        if (!rules->rules) {
            rules->sz_rules = rules->num_rules = 0;
    #ifdef DEBUG
            fprintf(stderr, "Allocation failure in XkbRF_AddRule\n");
    #endif
            return NULL;
        }
        bzero((char *) &rules->rules[rules->num_rules], sizeof(XkbRF_RuleRec));
        return &rules->rules[rules->num_rules++];
    }
    
    XkbRF_GroupPtr
    XkbRF_AddGroup(XkbRF_RulesPtr rules)
    {
        if (rules->sz_groups < 1) {
            rules->sz_groups = 16;
            rules->num_groups = 0;
            rules->groups = _XkbTypedCalloc(rules->sz_groups, XkbRF_GroupRec);
        }
        else if (rules->num_groups >= rules->sz_groups) {
            rules->sz_groups *= 2;
            rules->groups = _XkbTypedRealloc(rules->groups, rules->sz_groups,
                                             XkbRF_GroupRec);
        }
        if (!rules->groups) {
            rules->sz_groups = rules->num_groups = 0;
            return NULL;
        }
    
        bzero((char *) &rules->groups[rules->num_groups], sizeof(XkbRF_GroupRec));
        return &rules->groups[rules->num_groups++];
    }
    
    Bool
    XkbRF_LoadRules(FILE *file, XkbRF_RulesPtr rules)
    {
        InputLine line;
        RemapSpec remap;
        XkbRF_RuleRec trule, *rule;
        XkbRF_GroupRec tgroup, *group;
    
        if (!(rules && file))
            return False;
        bzero((char *) &remap, sizeof(RemapSpec));
        bzero((char *) &tgroup, sizeof(XkbRF_GroupRec));
        InitInputLine(&line);
        while (GetInputLine(file, &line, True)) {
            if (CheckLine(&line, &remap, &trule, &tgroup)) {
                if (tgroup.number) {
                    if ((group = XkbRF_AddGroup(rules)) != NULL) {
                        *group = tgroup;
                        bzero((char *) &tgroup, sizeof(XkbRF_GroupRec));
                    }
                }
                else {
                    if ((rule = XkbRF_AddRule(rules)) != NULL) {
                        *rule = trule;
                        bzero((char *) &trule, sizeof(XkbRF_RuleRec));
                    }
                }
            }
            line.num_line = 0;
        }
        FreeInputLine(&line);
        return True;
    }
    
    Bool
    XkbRF_LoadRulesByName(char *base, char *locale, XkbRF_RulesPtr rules)
    {
        FILE *file;
        char buf[PATH_MAX];
        Bool ok;
    
        if ((!base) || (!rules))
            return False;
        if (locale) {
            if (strlen(base) + strlen(locale) + 2 > PATH_MAX)
                return False;
            snprintf(buf, sizeof(buf), "%s-%s", base, locale);
        }
        else {
            if (strlen(base) + 1 > PATH_MAX)
                return False;
            strcpy(buf, base);
        }
    
        file = fopen(buf, "r");
        if ((!file) && (locale)) {  /* fallback if locale was specified */
            strcpy(buf, base);
            file = fopen(buf, "r");
        }
        if (!file)
            return False;
        ok = XkbRF_LoadRules(file, rules);
        fclose(file);
        return ok;
    }
    
    /***====================================================================***/
    
    #define HEAD_NONE	0
    #define HEAD_MODEL	1
    #define HEAD_LAYOUT	2
    #define HEAD_VARIANT	3
    #define HEAD_OPTION	4
    #define	HEAD_EXTRA	5
    
    XkbRF_VarDescPtr
    XkbRF_AddVarDesc(XkbRF_DescribeVarsPtr vars)
    {
        if (vars->sz_desc < 1) {
            vars->sz_desc = 16;
            vars->num_desc = 0;
            vars->desc = _XkbTypedCalloc(vars->sz_desc, XkbRF_VarDescRec);
        }
        else if (vars->num_desc >= vars->sz_desc) {
            vars->sz_desc *= 2;
            vars->desc =
                _XkbTypedRealloc(vars->desc, vars->sz_desc, XkbRF_VarDescRec);
        }
        if (!vars->desc) {
            vars->sz_desc = vars->num_desc = 0;
            PR_DEBUG("Allocation failure in XkbRF_AddVarDesc\n");
            return NULL;
        }
        vars->desc[vars->num_desc].name = NULL;
        vars->desc[vars->num_desc].desc = NULL;
        return &vars->desc[vars->num_desc++];
    }
    
    XkbRF_VarDescPtr
    XkbRF_AddVarDescCopy(XkbRF_DescribeVarsPtr vars, XkbRF_VarDescPtr from)
    {
        XkbRF_VarDescPtr nd;
    
        if ((nd = XkbRF_AddVarDesc(vars)) != NULL) {
            nd->name = _XkbDupString(from->name);
            nd->desc = _XkbDupString(from->desc);
        }
        return nd;
    }
    
    XkbRF_DescribeVarsPtr
    XkbRF_AddVarToDescribe(XkbRF_RulesPtr rules, char *name)
    {
        if (rules->sz_extra < 1) {
            rules->num_extra = 0;
            rules->sz_extra = 1;
            rules->extra_names = _XkbTypedCalloc(rules->sz_extra, char *);
    
            rules->extra = _XkbTypedCalloc(rules->sz_extra, XkbRF_DescribeVarsRec);
        }
        else if (rules->num_extra >= rules->sz_extra) {
            rules->sz_extra *= 2;
            rules->extra_names =
                _XkbTypedRealloc(rules->extra_names, rules->sz_extra, char *);
            rules->extra =
                _XkbTypedRealloc(rules->extra, rules->sz_extra,
                                 XkbRF_DescribeVarsRec);
        }
        if ((!rules->extra_names) || (!rules->extra)) {
            PR_DEBUG("allocation error in extra parts\n");
            rules->sz_extra = rules->num_extra = 0;
            rules->extra_names = NULL;
            rules->extra = NULL;
            return NULL;
        }
        rules->extra_names[rules->num_extra] = _XkbDupString(name);
        bzero(&rules->extra[rules->num_extra], sizeof(XkbRF_DescribeVarsRec));
        return &rules->extra[rules->num_extra++];
    }
    
    Bool
    XkbRF_LoadDescriptions(FILE *file, XkbRF_RulesPtr rules)
    {
        InputLine line;
        XkbRF_VarDescRec tmp;
        char *tok;
        int len, headingtype, extra_ndx = 0;
    
        bzero((char *) &tmp, sizeof(XkbRF_VarDescRec));
        headingtype = HEAD_NONE;
        InitInputLine(&line);
        for (; GetInputLine(file, &line, False); line.num_line = 0) {
            if (line.line[0] == '!') {
                tok = strtok(&(line.line[1]), " \t");
                if (strcmp(tok, "model") == 0)
                    headingtype = HEAD_MODEL;
                else if (_XkbStrCaseCmp(tok, "layout") == 0)
                    headingtype = HEAD_LAYOUT;
                else if (_XkbStrCaseCmp(tok, "variant") == 0)
                    headingtype = HEAD_VARIANT;
                else if (_XkbStrCaseCmp(tok, "option") == 0)
                    headingtype = HEAD_OPTION;
                else {
                    int i;
    
                    headingtype = HEAD_EXTRA;
                    extra_ndx = -1;
                    for (i = 0; (i < rules->num_extra) && (extra_ndx < 0); i++) {
                        if (_XkbStrCaseCmp(tok, rules->extra_names[i]))
                            extra_ndx = i;
                    }
                    if (extra_ndx < 0) {
                        XkbRF_DescribeVarsPtr var;
    
                        PR_DEBUG1("Extra heading \"%s\" encountered\n", tok);
                        var = XkbRF_AddVarToDescribe(rules, tok);
                        if (var)
                            extra_ndx = var - rules->extra;
                        else
                            headingtype = HEAD_NONE;
                    }
                }
                continue;
            }
    
            if (headingtype == HEAD_NONE) {
                PR_DEBUG("Must have a heading before first line of data\n");
                PR_DEBUG("Illegal line of data ignored\n");
                continue;
            }
    
            len = strlen(line.line);
            if ((tmp.name = strtok(line.line, " \t")) == NULL) {
                PR_DEBUG("Huh? No token on line\n");
                PR_DEBUG("Illegal line of data ignored\n");
                continue;
            }
            if (strlen(tmp.name) == len) {
                PR_DEBUG("No description found\n");
                PR_DEBUG("Illegal line of data ignored\n");
                continue;
            }
    
            tok = line.line + strlen(tmp.name) + 1;
            while ((*tok != '\n') && isspace(*tok))
                tok++;
            if (*tok == '\0') {
                PR_DEBUG("No description found\n");
                PR_DEBUG("Illegal line of data ignored\n");
                continue;
            }
            tmp.desc = tok;
            switch (headingtype) {
            case HEAD_MODEL:
                XkbRF_AddVarDescCopy(&rules->models, &tmp);
                break;
            case HEAD_LAYOUT:
                XkbRF_AddVarDescCopy(&rules->layouts, &tmp);
                break;
            case HEAD_VARIANT:
                XkbRF_AddVarDescCopy(&rules->variants, &tmp);
                break;
            case HEAD_OPTION:
                XkbRF_AddVarDescCopy(&rules->options, &tmp);
                break;
            case HEAD_EXTRA:
                XkbRF_AddVarDescCopy(&rules->extra[extra_ndx], &tmp);
                break;
            }
        }
        FreeInputLine(&line);
        if ((rules->models.num_desc == 0) && (rules->layouts.num_desc == 0) &&
            (rules->variants.num_desc == 0) && (rules->options.num_desc == 0) &&
            (rules->num_extra == 0)) {
            return False;
        }
        return True;
    }
    
    Bool
    XkbRF_LoadDescriptionsByName(char *base, char *locale, XkbRF_RulesPtr rules)
    {
        FILE *file;
        char buf[PATH_MAX];
        Bool ok;
    
        if ((!base) || (!rules))
            return False;
        if (locale) {
            if (strlen(base) + strlen(locale) + 6 > PATH_MAX)
                return False;
            snprintf(buf, sizeof(buf), "%s-%s.lst", base, locale);
        }
        else {
            if (strlen(base) + 5 > PATH_MAX)
                return False;
            snprintf(buf, sizeof(buf), "%s.lst", base);
        }
    
        file = fopen(buf, "r");
        if ((!file) && (locale)) {  /* fallback if locale was specified */
            snprintf(buf, sizeof(buf), "%s.lst", base);
    
            file = fopen(buf, "r");
        }
        if (!file)
            return False;
        ok = XkbRF_LoadDescriptions(file, rules);
        fclose(file);
        return ok;
    }
    
    /***====================================================================***/
    
    XkbRF_RulesPtr
    XkbRF_Load(char *base, char *locale, Bool wantDesc, Bool wantRules)
    {
        XkbRF_RulesPtr rules;
    
        if ((!base) || ((!wantDesc) && (!wantRules)))
            return NULL;
        if ((rules = _XkbTypedCalloc(1, XkbRF_RulesRec)) == NULL)
            return NULL;
        if (wantDesc && (!XkbRF_LoadDescriptionsByName(base, locale, rules))) {
            XkbRF_Free(rules, True);
            return NULL;
        }
        if (wantRules && (!XkbRF_LoadRulesByName(base, locale, rules))) {
            XkbRF_Free(rules, True);
            return NULL;
        }
        return rules;
    }
    
    XkbRF_RulesPtr
    XkbRF_Create(int szRules, int szExtra)
    {
        XkbRF_RulesPtr rules;
    
        if ((rules = _XkbTypedCalloc(1, XkbRF_RulesRec)) == NULL)
            return NULL;
        if (szRules > 0) {
            rules->sz_rules = szRules;
            rules->rules = _XkbTypedCalloc(rules->sz_rules, XkbRF_RuleRec);
            if (!rules->rules) {
                _XkbFree(rules);
                return NULL;
            }
        }
        if (szExtra > 0) {
            rules->sz_extra = szExtra;
            rules->extra = _XkbTypedCalloc(rules->sz_extra, XkbRF_DescribeVarsRec);
            if (!rules->extra) {
                if (rules->rules)
                    _XkbFree(rules->rules);
                _XkbFree(rules);
                return NULL;
            }
        }
        return rules;
    }
    
    /***====================================================================***/
    
    static void
    XkbRF_ClearVarDescriptions(XkbRF_DescribeVarsPtr var)
    {
        register int i;
    
        for (i = 0; i < var->num_desc; i++) {
            if (var->desc[i].name)
                _XkbFree(var->desc[i].name);
            if (var->desc[i].desc)
                _XkbFree(var->desc[i].desc);
            var->desc[i].name = var->desc[i].desc = NULL;
        }
        if (var->desc)
            _XkbFree(var->desc);
        var->desc = NULL;
        return;
    }
    
    void
    XkbRF_Free(XkbRF_RulesPtr rules, Bool freeRules)
    {
        int i;
        XkbRF_RulePtr rule;
        XkbRF_GroupPtr group;
    
        if (!rules)
            return;
        XkbRF_ClearVarDescriptions(&rules->models);
        XkbRF_ClearVarDescriptions(&rules->layouts);
        XkbRF_ClearVarDescriptions(&rules->variants);
        XkbRF_ClearVarDescriptions(&rules->options);
        if (rules->extra) {
            for (i = 0; i < rules->num_extra; i++) {
                XkbRF_ClearVarDescriptions(&rules->extra[i]);
            }
            _XkbFree(rules->extra);
            rules->num_extra = rules->sz_extra = 0;
            rules->extra = NULL;
        }
        if (rules->rules) {
            for (i = 0, rule = rules->rules; i < rules->num_rules; i++, rule++) {
                if (rule->model)
                    _XkbFree(rule->model);
                if (rule->layout)
                    _XkbFree(rule->layout);
                if (rule->variant)
                    _XkbFree(rule->variant);
                if (rule->option)
                    _XkbFree(rule->option);
                if (rule->keycodes)
                    _XkbFree(rule->keycodes);
                if (rule->symbols)
                    _XkbFree(rule->symbols);
                if (rule->types)
                    _XkbFree(rule->types);
                if (rule->compat)
                    _XkbFree(rule->compat);
                if (rule->geometry)
                    _XkbFree(rule->geometry);
                if (rule->keymap)
                    _XkbFree(rule->keymap);
                bzero((char *) rule, sizeof(XkbRF_RuleRec));
            }
            _XkbFree(rules->rules);
            rules->num_rules = rules->sz_rules = 0;
            rules->rules = NULL;
        }
    
        if (rules->groups) {
            for (i = 0, group = rules->groups; i < rules->num_groups; i++, group++) {
                if (group->name)
                    _XkbFree(group->name);
                if (group->words)
                    _XkbFree(group->words);
            }
            _XkbFree(rules->groups);
            rules->num_groups = 0;
            rules->groups = NULL;
        }
        if (freeRules)
            _XkbFree(rules);
        return;
    }
    
    
    Bool
    XkbRF_GetNamesProp(Display * dpy, char **rf_rtrn, XkbRF_VarDefsPtr vd_rtrn)
    {
        Atom rules_atom, actual_type;
        int fmt;
        unsigned long nitems, bytes_after;
        unsigned char *data;
        char *out, *end;
        Status rtrn;
    
        rules_atom = XInternAtom(dpy, _XKB_RF_NAMES_PROP_ATOM, True);
        if (rules_atom == None)     /* property cannot exist */
            return False;
        rtrn = XGetWindowProperty(dpy, DefaultRootWindow(dpy), rules_atom,
                                  0L, _XKB_RF_NAMES_PROP_MAXLEN, False,
                                  XA_STRING, &actual_type,
                                  &fmt, &nitems, &bytes_after,
                                  (unsigned char **) &data);
        if (rtrn != Success)
            return False;
        if (rf_rtrn)
            *rf_rtrn = NULL;
        (void) bzero((char *) vd_rtrn, sizeof(XkbRF_VarDefsRec));
        if ((bytes_after > 0) || (actual_type != XA_STRING) || (fmt != 8)) {
            if (data)
                XFree(data);
            return (fmt == 0 ? True : False);
        }
    
        out = (char *) data;
        end = out + nitems;
        if (out && (*out) && rf_rtrn)
            *rf_rtrn = _XkbDupString(out);
        out += strlen(out) + 1;
    
        if (out < end) {
            if (*out)
                vd_rtrn->model = _XkbDupString(out);
            out += strlen(out) + 1;
        }
    
        if (out < end) {
            if (*out)
                vd_rtrn->layout = _XkbDupString(out);
            out += strlen(out) + 1;
        }
    
        if (out < end) {
            if (*out)
                vd_rtrn->variant = _XkbDupString(out);
            out += strlen(out) + 1;
        }
    
        if (out < end) {
            if (*out)
                vd_rtrn->options = _XkbDupString(out);
            out += strlen(out) + 1;
        }
    
        XFree(data);
        return True;
    }
    
    Bool
    XkbRF_SetNamesProp(Display *dpy, char *rules_file, XkbRF_VarDefsPtr var_defs)
    {
        int len, out;
        Atom name;
        char *pval;
    
        len = (rules_file ? strlen(rules_file) : 0);
        len += (var_defs->model ? strlen(var_defs->model) : 0);
        len += (var_defs->layout ? strlen(var_defs->layout) : 0);
        len += (var_defs->variant ? strlen(var_defs->variant) : 0);
        len += (var_defs->options ? strlen(var_defs->options) : 0);
        if (len < 1)
            return True;
    
        len += 5;                   /* trailing NULs */
    
        name = XInternAtom(dpy, _XKB_RF_NAMES_PROP_ATOM, False);
        if (name == None) {         /* should never happen */
            _XkbLibError(_XkbErrXReqFailure, "XkbRF_SetNamesProp", X_InternAtom);
            return False;
        }
        pval = (char *) _XkbAlloc(len);
        if (!pval) {
            _XkbLibError(_XkbErrBadAlloc, "XkbRF_SetNamesProp", len);
            return False;
        }
        out = 0;
        if (rules_file) {
            strcpy(&pval[out], rules_file);
            out += strlen(rules_file);
        }
        pval[out++] = '\0';
        if (var_defs->model) {
            strcpy(&pval[out], var_defs->model);
            out += strlen(var_defs->model);
        }
        pval[out++] = '\0';
        if (var_defs->layout) {
            strcpy(&pval[out], var_defs->layout);
            out += strlen(var_defs->layout);
        }
        pval[out++] = '\0';
        if (var_defs->variant) {
            strcpy(&pval[out], var_defs->variant);
            out += strlen(var_defs->variant);
        }
        pval[out++] = '\0';
        if (var_defs->options) {
            strcpy(&pval[out], var_defs->options);
            out += strlen(var_defs->options);
        }
        pval[out++] = '\0';
        if (out != len) {
            _XkbLibError(_XkbErrBadLength, "XkbRF_SetNamesProp", out);
            _XkbFree(pval);
            return False;
        }
    
        XChangeProperty(dpy, DefaultRootWindow(dpy), name, XA_STRING, 8,
                        PropModeReplace, (unsigned char *) pval, len);
        _XkbFree(pval);
        return True;
    }