/* proplist.c- Hand made proplist parser. * The one in libPropList causes wmaker to crash if an error is found in * the parsed file. This parser is also more rigid: it will not accept any * property lists with errors, but will print more descriptive error messages * and will hopefully not crash. * * Window Maker window manager * * Copyright (c) 1998 Alfredo K. Kojima * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #include "wconfig.h" #include "WindowMaker.h" #include #include #include #include #if 0 #define DPUT(s) puts(s) #else #define DPUT(s) #endif #define INITIAL_BUFFER_SIZE (16*1024) #define BUFFER_SIZE_INCREMENT 1024 static int line_number = 1; static int buffer_size = 0; static char *buffer = NULL; static char *file_name; static proplist_t get_object(FILE *f); static proplist_t get_array(FILE *f); static proplist_t get_string(FILE *f); static proplist_t get_qstring(FILE *f); static proplist_t get_dictionary(FILE *f); static INLINE int get_char(FILE *f) { int c; c = fgetc(f); if (c=='\n') line_number++; return c; } static INLINE int get_non_space_char(FILE *f) { int c; while (1) { c = fgetc(f); if (c=='\n') line_number++; else if (!isspace(c)) break; } if (c!=EOF) { return c; } else { return EOF; } } static char * unescapestr(char *src) { char *dest = wmalloc(strlen(src)+1); char *src_ptr, *dest_ptr; char ch; for (src_ptr=src, dest_ptr=dest; *src_ptr; src_ptr++, dest_ptr++) { if(*src_ptr != '\\') *dest_ptr = *src_ptr; else { ch = *(++src_ptr); if((ch>='0') && (ch<='3')) /* assume next 2 chars are octal too */ { *dest_ptr = ((ch & 07) << 6); *dest_ptr |= ((*(++src_ptr)&07)<<3); *dest_ptr |= *(++src_ptr)&07; } else { switch(ch) { case 'a' : *dest_ptr = '\a'; break; case 'b' : *dest_ptr = '\b'; break; case 't' : *dest_ptr = '\t'; break; case 'r' : *dest_ptr = '\r'; break; case 'n' : *dest_ptr = '\n'; break; case 'v' : *dest_ptr = '\v'; break; case 'f' : *dest_ptr = '\f'; break; default : *dest_ptr = *src_ptr; } } } } *dest_ptr = 0; return dest; } #define CHECK_BUFFER_SIZE(ptr) \ if ((ptr) >= buffer_size-1) {\ buffer_size += BUFFER_SIZE_INCREMENT;\ buffer = wrealloc(buffer, buffer_size);\ } #define ISSTRINGABLE(c) (isalnum(c) || (c)=='.' || (c)=='_' || (c)=='/' \ || (c)=='+') #define COMPLAIN(msg) wwarning(_("syntax error in %s, line %i:%s"), \ file_name, line_number, msg) static proplist_t get_qstring(FILE *f) { int c; int ptr = 0; int escaping = 0; int ok = 1; while (1) { c = get_char(f); if (!escaping) { if (c=='\\') { escaping = 1; continue; } if (c=='"') break; } else { CHECK_BUFFER_SIZE(ptr); buffer[ptr++] = '\\'; escaping = 0; } if (c==EOF) { ptr--; ok = 0; COMPLAIN(_("unterminated string")); break; } else { CHECK_BUFFER_SIZE(ptr); buffer[ptr++] = c; } } buffer[ptr] = 0; if (!ok) return NULL; else { char *tmp = unescapestr(buffer); proplist_t pl = PLMakeString(tmp); free(tmp); return pl; } } static proplist_t get_string(FILE *f) { int c; int ptr = 0; while (1) { c = get_char(f); if (ISSTRINGABLE(c)) { CHECK_BUFFER_SIZE(ptr); buffer[ptr++] = c; } else { if (c!=EOF) { ungetc(c, f); } break; } } buffer[ptr] = 0; if (ptr==0) return NULL; else { char *tmp = unescapestr(buffer); proplist_t pl = PLMakeString(tmp); free(tmp); return pl; } } static proplist_t get_array(FILE *f) { int c; int ok=1, first=1; proplist_t list, obj; list = PLMakeArrayFromElements(NULL); while (1) { c = get_non_space_char(f); if (c==EOF) { COMPLAIN(_("unterminated array")); ok = 0; break; } else if (c==')') { break; } else if (c==',') { /* continue normally */ } else { if (!first) { COMPLAIN(_("missing , in array or unterminated array")); ok = 0; break; } else { ungetc(c, f); } } first = 0; /* get the data */ obj = get_object(f); if (!obj) { COMPLAIN(_("could not get array element")); ok = 0; break; } list = PLAppendArrayElement(list, obj); PLRelease(obj); } if (ok) return list; else { PLRelease(list); return NULL; } } static proplist_t get_dictionary(FILE *f) { int c; int ok = 1; proplist_t dict, key, value; dict = PLMakeDictionaryFromEntries(NULL, NULL); while (1) { c = get_non_space_char(f); if (c==EOF) { COMPLAIN(_("unterminated dictionary")); ok = 0; break; } else if (c=='}') { break; } /* get the entry */ /* get key */ DPUT("getting dict key"); if (c=='"') key = get_qstring(f); else if (ISSTRINGABLE(c)) { ungetc(c, f); key = get_string(f); } else { if (c=='=') COMPLAIN(_("missing dictionary key")); else COMPLAIN(_("missing dictionary entry key or unterminated dictionary")); ok = 0; break; } if (!key) { COMPLAIN(_("error parsing dictionary key")); ok = 0; break; } DPUT("getting ="); /* get = */ c = get_non_space_char(f); if (c!='=') { PLRelease(key); COMPLAIN(_("missing = in dictionary entry")); ok = 0; break; } DPUT("getting dict entry data"); /* get data */ value = get_object(f); if (!value) { /* COMPLAIN(_("error parsing dictionary entry value")); */ ok = 0; PLRelease(key); break; } DPUT("getting ;"); /* get ; */ c = get_non_space_char(f); if (c!=';') { COMPLAIN(_("missing ; in dictionary entry")); ok = 0; PLRelease(key); PLRelease(value); break; } dict = PLInsertDictionaryEntry(dict, key, value); PLRelease(key); PLRelease(value); } if (!ok) { PLRelease(dict); return NULL; } else { return dict; } } static proplist_t get_data(FILE *f) { COMPLAIN("the data datatype is not still implemented"); return NULL; } static proplist_t get_object(FILE *f) { int c; proplist_t pl; c = get_non_space_char(f); switch (c) { /* END OF FILE */ case EOF: DPUT("EOF"); pl = NULL; break; /* dictionary */ case '{': DPUT("getting dictionary"); pl = get_dictionary(f); break; /* array */ case '(': DPUT("getting arrray"); pl = get_array(f); break; /* data */ case '<': DPUT("getting data"); pl = get_data(f); break; /* quoted string */ case '"': DPUT("getting qstring"); pl = get_qstring(f); break; /* string */ default: if (ISSTRINGABLE(c)) { DPUT("getting string"); /* put back */ ungetc(c, f); pl = get_string(f); } else { COMPLAIN(_("was expecting a string, dictionary, data or array. If it's a string, try enclosing it with \".")); if (c=='#' || c=='/') { wwarning(_("Comments are not allowed inside WindowMaker owned domain files.")); } pl = NULL; } break; } return pl; } proplist_t ReadProplistFromFile(char *file) { FILE *f; proplist_t pl = NULL; f = fopen(file, "r"); if (!f) { wsyserror(_("could not open domain file %s"), file); return NULL; } file_name = file; line_number = 1; buffer_size = INITIAL_BUFFER_SIZE; buffer = wmalloc(buffer_size); pl = get_object(f); /* check for illegal characters after EOF */ if (get_non_space_char(f)!=EOF && pl) { COMPLAIN(_("extra data after end of file")); /* * We can't just ignore garbage after the file because the "garbage" * could be the data and the real garbage be in the beginning of * the file (wich is now, inside pl) */ PLRelease(pl); pl = NULL; } free(buffer); fclose(f); if (pl) { proplist_t fpl; fpl = PLMakeString(file); PLSetFilename(pl, fpl); PLRelease(fpl); } return pl; }