/***************************************************************************** * XML lexer functions. * Copyright (C) 2004 Joe Wingbermuehle *****************************************************************************/ #include "jwm.h" #include "lex.h" #include "error.h" #include "misc.h" static const int BLOCK_SIZE = 16; /* Order is important! The order must match the order in lex.h */ static const char *TOKEN_MAP[] = { "[invalid]", "ActiveBackground", "ActiveForeground", "Background", "BorderStyle", "Class", "Clock", "ClockStyle", "Close", "Desktops", "Dock", "DoubleClickSpeed", "DoubleClickDelta", "Exit", "FocusModel", "Font", "Foreground", "Group", "Height", "IconPath", "Include", "JWM", "Key", "Kill", "Layer", "Maximize", "Menu", "MenuStyle", "Minimize", "Mouse", "Move", "MoveMode", "Name", "Option", "Outline", "Pager", "PagerStyle", "Popup", "PopupStyle", "Program", "Resize", "ResizeMode", "Restart", "RestartCommand", "RootMenu", "SendTo", "Separator", "Shade", "ShutdownCommand", "SnapMode", "StartupCommand", "Stick", "Swallow", "TaskListStyle", "TaskList", "Theme", "ThemePath", "Tray", "TrayButton", "TrayButtonStyle", "TrayStyle", "Width" }; static TokenNode *head, *current; static TokenNode *CreateNode(TokenNode *parent, const char *file, int line); static AttributeNode *CreateAttribute(TokenNode *np); static int IsElementEnd(char ch); static int IsValueEnd(char ch); static int IsAttributeEnd(char ch); static int IsSpace(char ch, int *lineNumber); static char *ReadElementName(const char *line); static char *ReadElementValue(const char *line, const char *file, int *lineNumber); static char *ReadAttributeValue(const char *line, const char *file, int *lineNumber); static int ParseEntity(const char *entity, char *ch, const char *file, int line); static TokenType LookupType(const char *name, TokenNode *np); /***************************************************************************** *****************************************************************************/ TokenNode * Tokenize(const char *line, const char *fileName) { TokenNode *np; AttributeNode *ap; char *temp; int inElement; int x; int found; int lineNumber; head = NULL; current = NULL; inElement = 0; lineNumber = 1; x = 0; /* Skip any initial white space */ while(IsSpace(line[x], &lineNumber)) ++x; /* Skip any XML stuff */ if(!strncmp(line + x, "", 2)) { x += 2; break; } ++x; } } while(line[x]) { do { while(IsSpace(line[x], &lineNumber)) ++x; /* Skip comments */ found = 0; if(!strncmp(line + x, "", 3)) { x += 3; found = 1; break; } ++x; } } } while(found); switch(line[x]) { case '<': ++x; if(line[x] == '/') { ++x; temp = ReadElementName(line + x); if(current) { if(temp) { if(current->type != LookupType(temp, NULL)) { Warning("%s[%d]: close tag \"%s\" does not " "match open tag \"%s\"", fileName, lineNumber, temp, GetTokenName(current)); } } else { Warning("%s[%d]: unexpected and invalid close tag", fileName, lineNumber); } current = current->parent; } else { if(temp) { Warning("%s[%d]: close tag \"%s\" without open " "tag", fileName, lineNumber, temp); } else { Warning("%s[%d]: invalid close tag", fileName, lineNumber); } } if(temp) { x += strlen(temp); Release(temp); } } else { np = current; current = NULL; np = CreateNode(np, fileName, lineNumber); temp = ReadElementName(line + x); if(temp) { x += strlen(temp); LookupType(temp, np); Release(temp); } else { Warning("%s[%d]: invalid open tag", fileName, lineNumber); } } inElement = 1; break; case '/': if(inElement) { ++x; if(line[x] == '>' && current) { ++x; current = current->parent; inElement = 0; } else { Warning("%s[%d]: invalid tag", fileName, lineNumber); } } else { goto ReadDefault; } break; case '>': ++x; inElement = 0; break; default: ReadDefault: if(inElement) { ap = CreateAttribute(current); ap->name = ReadElementName(line + x); if(ap->name) { x += strlen(ap->name); if(line[x] == '=') { ++x; } if(line[x] == '\"') { ++x; } ap->value = ReadAttributeValue(line + x, fileName, &lineNumber); if(ap->value) { x += strlen(ap->value); } if(line[x] == '\"') { ++x; } } } else { temp = ReadElementValue(line + x, fileName, &lineNumber); if(temp) { x += strlen(temp); if(current) { if(current->value) { current->value = Reallocate(current->value, strlen(current->value) + strlen(temp) + 1); strcat(current->value, temp); Release(temp); } else { current->value = temp; } } else { if(temp[0]) { Warning("%s[%d]: unexpected text: \"%s\"", fileName, lineNumber, temp); } Release(temp); } } } break; } } return head; } /***************************************************************************** * Parse an entity reference. * The entity value is returned in ch and the length of the entity * is returned as the value of the function. *****************************************************************************/ int ParseEntity(const char *entity, char *ch, const char *file, int line) { char *temp; int x; if(!strncmp(""", entity, 6)) { *ch = '\"'; return 6; } else if(!strncmp("<", entity, 4)) { *ch = '<'; return 4; } else if(!strncmp(">", entity, 4)) { *ch = '>'; return 4; } else if(!strncmp("&", entity, 5)) { *ch = '&'; return 5; } else if(!strncmp("'", entity, 6)) { *ch = '\''; return 6; } else { for(x = 0; entity[x]; x++) { if(entity[x] == ';') { break; } } temp = AllocateStack(x + 2); strncpy(temp, entity, x + 1); temp[x + 1] = 0; Warning("%s[%d]: invalid entity: \"%.8s\"", file, line, temp); ReleaseStack(temp); *ch = '&'; return 1; } } /***************************************************************************** *****************************************************************************/ int IsElementEnd(char ch) { switch(ch) { case ' ': case '\t': case '\n': case '\r': case '\"': case '>': case '<': case '/': case '=': case 0: return 1; default: return 0; } } /***************************************************************************** *****************************************************************************/ int IsAttributeEnd(char ch) { switch(ch) { case 0: case '\"': return 1; default: return 0; } } /***************************************************************************** *****************************************************************************/ int IsValueEnd(char ch) { switch(ch) { case 0: case '<': return 1; default: return 0; } } /***************************************************************************** *****************************************************************************/ int IsSpace(char ch, int *lineNumber) { switch(ch) { case ' ': case '\t': case '\r': return 1; case '\n': ++*lineNumber; return 1; default: return 0; } } /***************************************************************************** *****************************************************************************/ char * ReadElementName(const char *line) { char *buffer; int len, max; int x; len = 0; max = BLOCK_SIZE; buffer = Allocate(max + 1); for(x = 0; !IsElementEnd(line[x]); x++) { buffer[len++] = line[x]; if(len >= max) { max += BLOCK_SIZE; buffer = Reallocate(buffer, max + 1); } } buffer[len] = 0; return buffer; } /***************************************************************************** *****************************************************************************/ char * ReadElementValue(const char *line, const char *file, int *lineNumber) { char *buffer; char ch; int len, max; int x; len = 0; max = BLOCK_SIZE; buffer = Allocate(max + 1); for(x = 0; !IsValueEnd(line[x]); x++) { if(line[x] == '&') { x += ParseEntity(line + x, &ch, file, *lineNumber) - 1; if(ch) { buffer[len] = ch; } else { buffer[len] = line[x]; } } else { if(line[x] == '\n') { ++*lineNumber; } buffer[len] = line[x]; } ++len; if(len >= max) { max += BLOCK_SIZE; buffer = Reallocate(buffer, max + 1); } } buffer[len] = 0; Trim(buffer); return buffer; } /***************************************************************************** *****************************************************************************/ char *ReadAttributeValue(const char *line, const char *file, int *lineNumber) { char *buffer; char ch; int len, max; int x; len = 0; max = BLOCK_SIZE; buffer = Allocate(max + 1); for(x = 0; !IsAttributeEnd(line[x]); x++) { if(line[x] == '&') { x += ParseEntity(line + x, &ch, file, *lineNumber) - 1; if(ch) { buffer[len] = ch; } else { buffer[len] = line[x]; } } else { if(line[x] == '\n') { ++*lineNumber; } buffer[len] = line[x]; } ++len; if(len >= max) { max += BLOCK_SIZE; buffer = Reallocate(buffer, max + 1); } } buffer[len] = 0; return buffer; } /***************************************************************************** *****************************************************************************/ TokenType LookupType(const char *name, TokenNode *np) { unsigned int x; Assert(name); for(x = 0; x < sizeof(TOKEN_MAP) / sizeof(char*); x++) { if(!strcmp(name, TOKEN_MAP[x])) { if(np) { np->type = x; } return x; } } if(np) { np->type = TOK_INVALID; np->invalidName = CopyString(name); } return TOK_INVALID; } /***************************************************************************** *****************************************************************************/ const char * GetTokenName(const TokenNode *tp) { if(tp->invalidName) { return tp->invalidName; } else if(tp->type >= sizeof(TOKEN_MAP) / sizeof(const char*)) { return "[invalid]"; } else { return TOKEN_MAP[tp->type]; } } /***************************************************************************** *****************************************************************************/ const char * GetTokenTypeName(TokenType type) { return TOKEN_MAP[type]; } /***************************************************************************** *****************************************************************************/ TokenNode * CreateNode(TokenNode *parent, const char *file, int line) { TokenNode *np; np = Allocate(sizeof(TokenNode)); np->type = TOK_INVALID; np->value = NULL; np->attributes = NULL; np->subnodeHead = NULL; np->subnodeTail = NULL; np->parent = parent; np->next = NULL; np->fileName = Allocate(strlen(file) + 1); strcpy(np->fileName, file); np->line = line; np->invalidName = NULL; if(!head) { head = np; } if(parent) { if(parent->subnodeHead) { parent->subnodeTail->next = np; } else { parent->subnodeHead = np; } parent->subnodeTail = np; } else if(current) { current->next = np; } current = np; return np; } /***************************************************************************** *****************************************************************************/ AttributeNode * CreateAttribute(TokenNode *np) { AttributeNode *ap; ap = Allocate(sizeof(AttributeNode)); ap->name = NULL; ap->value = NULL; ap->next = np->attributes; np->attributes = ap; return ap; }