xenocara/app/fvwm/libs/Parse.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;
}