581 lines
13 KiB
C
581 lines
13 KiB
C
/*
|
|
** Parse.c: routines for parsing in fvwm & modules
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#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;
|
|
}
|