/* ** Parse.c: routines for parsing in fvwm & modules */ #include "config.h" #include #include #include #include "fvwmlib.h" /* If the string s begins with a quote chracter SkipQuote returns a pointer * to the first unquoted character or to the final '\0'. If it does not, a * pointer to the next character in the string is returned. * There are three possible types of quoting: a backslash quotes the next * character only. Long quotes like " " or ' ' quoting everything in * between and quote pairs like ( ) or { }. * * precedence: * * 1) Backslashes are honoured always, even inside long or pair quotes. * 2) long quotes do quote quoting pair characters but not simple quotes. All * long quotes can quote all other types of long quotes). * 3) pair quotes none of the above. Text between a pair of quotes is treated * as a single token. * * qlong - string of long quoted (defaults to "'` ) * qstart - string of pair quote start characters (defaults to empty string) * qend - string of pair quote end characters (defaults to empty string) * * The defaults are used if NULL is passed for the corresponding string. */ char *SkipQuote(char *s, const char *qlong, const char *qstart, const char *qend) { char *t; if (s == NULL || *s == 0) return s; if (!qlong) qlong = "\"'`"; if (!qstart) qstart = ""; if (!qend) qend = ""; if (*s == '\\' && s[1] != 0) return s+2; else if (*qlong && (t = strchr(qlong, *s))) { char c = *t; s++; while(*s && *s != c) { /* Skip over escaped text, ie \quote */ if(*s == '\\' && *(s+1) != 0) s++; s++; } if(*s == c) s++; return s; } else if (*qstart && (t = strchr(qstart, *s))) { char c = *((t - qstart) + qend); while (*s && *s != c) s = SkipQuote(s, qlong, "", ""); return (*s == *t) ? ++s : s; } else return ++s; } /* Returns a string up to the first character from the string delims in a * malloc'd area just like GetNextToken. Quotes are not removed from the * returned string. The returned string is stored in *sout, the return value * of this call is a pointer to the first character after the delimiter or * to the terminating '\0'. Quoting is handled like in SkipQuote. */ char *GetQuotedString(char *sin, char **sout, const char *delims, const char *qlong, const char *qstart, const char *qend) { char *t = sin; unsigned int len; if (!sout || !sin) return NULL; while (*t && !strchr(delims, *t)) t = SkipQuote(t, qlong, qstart, qend); len = t - sin; *sout = (char *)safemalloc(len + 1); memcpy(*sout, sin, len); (*sout)[len] = 0; if (*t) t++; return t; } /* ** PeekToken: returns next token from string, leaving string intact ** (you should free returned string later) ** ** notes - quotes and brakets denote blocks, which are returned MINUS ** the start & end delimiters. Delims within block may be escaped ** with a backslash \ (which is removed when returned). Backslash ** must be escaped too, if you want one... (\\). Tokens may be up ** to MAX_TOKEN_LENGTH in size. */ char *PeekToken(const char *pstr) { char *tok=NULL; const char* p; char bc=0,be=0,tmptok[MAX_TOKEN_LENGTH]; int len=0; if (!pstr) return NULL; p=pstr; EatWS(p); /* skip leading space */ if (*p) { if (IsQuote(*p) || IsBlockStart(*p)) /* quoted string or block start? */ { bc = *p; /* save block start char */ p++; } /* find end of token */ while (*p && len < MAX_TOKEN_LENGTH) { /* first, check stop conditions based on block or normal token */ if (bc) { if ((IsQuote(*p) && bc == *p) || IsBlockEnd(*p,bc)) { be = *p; break; } } else /* normal token */ { if (isspace((unsigned char)*p) || *p == ',') break; } if (*p == '\\' && *(p+1)) /* if \, copy next char verbatim */ p++; tmptok[len] = *p; len++; p++; } /* sanity checks: */ if (bc && !be) /* did we have block start, but not end? */ { /* should yell about this */ return NULL; } if (len) { tok = (char *)malloc(len+1); strncpy(tok,tmptok,len); tok[len]='\0'; } } return tok; } /* ** CmpToken: does case-insensitive compare on next token in string, leaving ** string intact (return code like strcmp) */ int CmpToken(const char *pstr,char *tok) { int rc=0; char *ntok=PeekToken(pstr); if (ntok) { rc = strcasecmp(tok,ntok); free(ntok); } return rc; } /* ** MatchToken: does case-insensitive compare on next token in string, leaving ** string intact (returns true if matches, false otherwise) */ int MatchToken(const char *pstr,char *tok) { int rc=0; char *ntok=PeekToken(pstr); if (ntok) { rc = (strcasecmp(tok,ntok)==0); free(ntok); } return rc; } /* ** NukeToken: removes next token from string */ void NukeToken(char **pstr) { char *tok; char *next; char *temp = NULL; next = GetNextToken(*pstr, &tok); if (next != NULL); temp = strdup(next); if (pstr && *pstr) free(*pstr); *pstr = temp; if (tok) free(tok); } /**************************************************************************** * * Gets the next "word" of input from string indata. * "word" is a string with no spaces, or a qouted string. * Return value is ptr to indata,updated to point to text after the word * which is extracted. * token is the extracted word, which is copied into a malloced * space, and must be freed after use. DoGetNextToken *never* returns an * empty string or token. If the token consists only of whitespace or * delimiters, the returned token is NULL instead. If out_delim is given, * the character ending the string is returned therein. * * spaces = string of characters to treat as spaces * delims = string of characters delimiting token * * Use "spaces" and "delims" to define additional space/delimiter * characters (spaces are skipped before a token, delimiters are not). * **************************************************************************/ char *DoGetNextToken(char *indata, char **token, char *spaces, char *delims, char *out_delim) { char *t, *start, *end, *text; int snum; int dnum; snum = (spaces) ? strlen(spaces) : 0; dnum = (delims) ? strlen(delims) : 0; if(indata == NULL) { if (out_delim) *out_delim = '\0'; *token = NULL; return NULL; } t = indata; while ( (*t != 0) && ( isspace((unsigned char)*t) || (snum && strchr(spaces, *t)) ) ) t++; start = t; while ( (*t != 0) && !( isspace((unsigned char)*t) || (snum && strchr(spaces, *t)) || (dnum && strchr(delims, *t)) ) ) { /* Check for qouted text */ if (IsQuote(*t)) { char c = *t; t++; while((*t != c)&&(*t != 0)) { /* Skip over escaped text, ie \quote */ if((*t == '\\')&&(*(t+1) != 0)) t++; t++; } if(*t == c) t++; } else { /* Skip over escaped text, ie \" or \space */ if((*t == '\\')&&(*(t+1) != 0)) t++; t++; } } end = t; if (out_delim) *out_delim = *end; text = safemalloc(end-start+1); *token = text; /* copy token */ while(start < end) { /* Check for qouted text */ if(IsQuote(*start)) { char c = *start; start++; while((*start != c)&&(*start != 0)) { /* Skip over escaped text, ie \" or \space */ if((*start == '\\')&&(*(start+1) != 0)) start++; *text++ = *start++; } if(*start == c) start++; } else { /* Skip over escaped text, ie \" or \space */ if((*start == '\\')&&(*(start+1) != 0)) start++; *text++ = *start++; } } *text = 0; if(*end != 0) end++; if (**token == 0) { free(*token); *token = NULL; } return end; } char *GetNextToken(char *indata, char **token) { return DoGetNextToken(indata, token, NULL, NULL, NULL); } char *GetNextOption(char *indata, char **token) { return DoGetNextToken(indata, token, ",", NULL, NULL); } char *SkipNTokens(char *indata, unsigned int n) { char *tmp; char *junk; tmp = indata; for ( ; n > 0 ; n--) { tmp = GetNextToken(tmp, &junk); if (junk) free(junk); } return tmp; } /**************************************************************************** * * Works like GetNextToken, but with the following differences: * * If *indata begins with a "*" followed by the string module_name, * it returns the string following directly after module_name as the * new token. Otherwise NULL is returned. * e.g. GetModuleResource("*FvwmPagerGeometry", &token, "FvwmPager") * returns "Geometry" in token. * **************************************************************************/ char *GetModuleResource(char *indata, char **resource, char *module_name) { char *tmp; char *next; if (!module_name) { *resource = NULL; return indata; } next = GetNextToken(indata, &tmp); if (!tmp) return next; if (tmp[0] != '*' || strncasecmp(tmp+1, module_name, strlen(module_name))) { *resource = NULL; return indata; } CopyString(resource, tmp+1+strlen(module_name)); free(tmp); return next; } /**************************************************************************** * * This function uses GetNextToken to parse action for up to num integer * arguments. The number of values actually found is returned. * If ret_action is non-NULL, a pointer to the next token is returned there. * **************************************************************************/ int GetIntegerArguments(char *action, char **ret_action, int retvals[],int num) { int i; char *token; for (i = 0; i < num && action; i++) { action = GetNextToken(action, &token); if (token == NULL) break; if (sscanf(token, "%d", &(retvals[i])) != 1) break; free(token); token = NULL; } if (token) free(token); if (ret_action != NULL) *ret_action = action; return i; } /*************************************************************************** * * This function tries to match a token with a list of strings and returns * the position of token in the array or -1 if no match is found. The last * entry in the list must be NULL. * * len = 0 : only exact matches * len < 0 : match, if token begins with one of the strings in list * len > 0 : match, if the first len characters do match * * if next is non-NULL, *next will be set to point to the first character * in token after the match. * **************************************************************************/ int GetTokenIndex(char *token, char *list[], int len, char **next) { int i; int l; int k; if (!token || !list) { if (next) *next = NULL; return -1; } l = (len) ? len : strlen(token); for (i = 0; list[i] != NULL; i++) { k = strlen(list[i]); if (len < 0) l = k; if (len == 0 && k != l) continue; if (!strncasecmp(token, list[i], l)) break; } if (next) { *next = (list[i]) ? token + l : token; } return (list[i]) ? i : -1; } /*************************************************************************** * * This function does roughly the same as GetTokenIndex but reads the * token from string action with GetNextToken. The index is returned * in *index. The return value is a pointer to the character after the * token (just like the return value of GetNextToken). * **************************************************************************/ char *GetNextTokenIndex(char *action, char *list[], int len, int *index) { char *token; char *next; if (!index) return action; next = GetNextToken(action, &token); if (!token) { *index = -1; return action; } *index = GetTokenIndex(token, list, len, NULL); free(token); return (*index == -1) ? action : next; } int GetRectangleArguments(char *action, int *width, int *height) { char *token; int n; GetNextToken(action, &token); if (!token) return 0; /* now try MxN style number, specifically for DeskTopSize: */ n = sscanf(token, "%d%*c%d", width, height); free(token); return (n == 2) ? 2 : 0; } /* unit_io is input as well as output. If action has a postfix 'p' or 'P', * *unit_io is set to 100, otherwise it is left untouched. */ int GetOnePercentArgument(char *action, int *value, int *unit_io) { unsigned int len; char *token; int n; *value = 0; if (!action) return 0; GetNextToken(action, &token); if (!token) return 0; len = strlen(token); /* token never contains an empty string, so this is ok */ if (token[len - 1] == 'p' || token[len - 1] == 'P') { *unit_io = 100; token[len - 1] = '\0'; } n = sscanf(token, "%d", value); free(token); return n; } int GetTwoPercentArguments(char *action, int *val1, int *val2, int *val1_unit, int *val2_unit) { char *tok1, *tok2; int n = 0; *val1 = 0; *val2 = 0; action = GetNextToken(action, &tok1); if (!tok1) return 0; GetNextToken(action, &tok2); if (GetOnePercentArgument(tok2, val2, val2_unit) == 1 && GetOnePercentArgument(tok1, val1, val1_unit) == 1) { free(tok1); free(tok2); return 2; } /* now try MxN style number, specifically for DeskTopSize: */ n = GetRectangleArguments(tok1, val1, val2); free(tok1); if (tok2) free(tok2); return n; }