/* * css.c * * Copyright (c) 1998, John Kilburg * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "port_before.h" #include #include #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #include "port_after.h" #include "common.h" #include "Chimera.h" #include "css.h" struct CSSPropertyP { CSSRule cr; char *name; char *value; }; struct CSSSelectorP { char *tag; char *id; char *class; char *pclass; }; struct CSSRuleP { GList selectors; /* list of lists of CSSSelector's */ GList properties; /* list of CSSProperty's */ }; /* * Used to keep track of which buffer to take input from. Since new * text can be inserted at any time have to be able to store and restore * buffer context. */ struct CSSInputP { char *b; size_t blen; char *cp; char *ep; }; struct CSSContextP { MemPool mp; ChimeraContext cs; /* parser bookkeeping */ GList inputs; /* The Result: a list of rules and properties */ GList rules; GList props; /* callback information */ CSSProc proc; void *closure; ChimeraTask task; }; typedef struct CSSInputP *CSSInput; static char *ParseSpace _ArgProto((char *, char *)); static CSSSelector ParseSelector2 _ArgProto((MemPool, char *, size_t)); static GList ParseSelector1 _ArgProto((MemPool, char *, size_t)); static GList ParseSelector _ArgProto((MemPool, char *, size_t)); static CSSProperty ParseProperty _ArgProto((MemPool, char *, size_t)); static GList ParseProperties _ArgProto((MemPool, char *, size_t)); static bool ParseRule _ArgProto((CSSContext, CSSInput)); static bool ParseAt _ArgProto((CSSContext, CSSInput)); static void ParseCSS _ArgProto((CSSContext)); static int scompare _ArgProto((char *, char *)); static bool SelectorListMatch _ArgProto((GList, GList)); static char *ParseToChar _ArgProto((char *, char *, int, bool)); /* * ParseToChar * * Search for an expected character. Deals with escaped characters, * quoted strings, and pairs of braces/parens/brackets. */ static char * ParseToChar(cp, ep, c, chkws) char *cp, *ep; int c; bool chkws; { bool sq, dq, esc; sq = false; /* in single quote? */ dq = false; /* in double quote? */ esc = false; /* escape mode? */ while (cp < ep) { if (esc) esc = false; else if (*cp == '\\') esc = true; else if (dq && *cp != '"') dq = false; else if (sq && *cp != '\'') sq = false; else if (*cp == '"') dq = true; else if (*cp == '\'') sq = true; else if (*cp == c) return(cp); else if (chkws && isspace8(*cp)) return(cp); else if (*cp == '{') { if ((cp = ParseToChar(cp + 1, ep, '}', false)) == NULL) return(NULL); } else if (*cp == '(') { if ((cp = ParseToChar(cp + 1, ep, ')', false)) == NULL) return(NULL); } else if (*cp == '[') { if ((cp = ParseToChar(cp + 1, ep, ']', false)) == NULL) return(NULL); } cp++; } return(NULL); } /* * ParseAt * * This is lame. Handle @ statements later. * * Return false if @import occured. Return true if not. */ static bool ParseAt(css, ci) CSSContext css; CSSInput ci; { while (ci->cp < ci->ep) { if (*ci->cp == '{') { if ((ci->cp = ParseToChar(ci->cp + 1, ci->ep, '}', false)) == NULL) { ci->cp = ci->ep; } return(true); } else if (*ci->cp == ';') { ci->cp++; return(false); } ci->cp++; } return(true); } /* * ParseSpace * * Look for non-space and deal with comments. */ static char * ParseSpace(s, e) char *s, *e; { bool sc, ec, ic; sc = false; /* possible start comment? */ ec = false; /* possible end comment? */ ic = false; /* in comment? */ while (s < e) { if (ic) { if (ec && *s == '/') ic = false; else if (*s == '*') ec = true; else ec = false; } else if (sc && *s == '*') ic = true; else if (*s == '/') sc = true; else if (!isspace8(*s)) return(s); else sc = false; s++; } return(NULL); } static CSSProperty ParseProperty(mp, s, slen) MemPool mp; char *s; size_t slen; { char *ep, *colon, *x; CSSProperty prop; ep = s + slen; /* get rid of space in front of the declaration */ if ((s = ParseSpace(s, ep)) == NULL) return(NULL); /* search for the required colon or white space */ if ((colon = ParseToChar(s, ep, ':', true)) == NULL) return(NULL); prop = (CSSProperty)MPCGet(mp, sizeof(struct CSSPropertyP)); prop->name = (char *)MPGet(mp, colon - s + 1); strncpy(prop->name, s, colon - s); prop->name[colon - s] = '\0'; /* May have only reached white space before */ if (*colon != ':') { /* search for the required colon */ if ((colon = ParseToChar(colon, ep, ':', false)) == NULL) return(NULL); } /* remove space in front of the value */ if ((s = ParseSpace(colon + 1, ep)) == NULL) return(NULL); prop->value = (char *)MPGet(mp, ep - s + 1); /* Copy the value until end of input or whitespace is seen */ x = prop->value; while (s < ep) { if (isspace8(*s)) break; *x++ = *s++; } *x = '\0'; if (strlen(prop->name) == 0 || strlen(prop->value) == 0) return(NULL); return(prop); } static GList ParseProperties(mp, s, slen) MemPool mp; char *s; size_t slen; { char *last; char *ep; GList properties; CSSProperty prop; properties = GListCreateX(mp); ep = s + slen; while (s < ep) { last = s; s = ParseToChar(s, ep, ';', false); if (s == NULL) s = ep; if (last < s && (prop = ParseProperty(mp, last, s - last)) != NULL) { GListAddTail(properties, prop); } s++; } if (GListGetHead(properties) == NULL) return(NULL); return(properties); } static CSSSelector ParseSelector2(mp, s, slen) MemPool mp; char *s; size_t slen; { CSSSelector cs; char *tag = NULL; char *id = NULL; char *class = NULL; char *pclass = NULL; char *ep; char *t; cs = (CSSSelector)MPCGet(mp, sizeof(struct CSSSelectorP)); t = MPGet(mp, slen + 1); strncpy(t, s, slen); t[slen] = '\0'; s = t; if (*s == '#') id = s + 1; else if (*s == '.') class = s + 1; else if (*s == ':') pclass = s + 1; else { tag = s; ep = s + slen; while (s < ep) { if (*s == '#') { id = s + 1; *s = '\0'; break; } else if (*s == '.') { class = s + 1; *s = '\0'; break; } else if (*s == ':') { pclass = s + 1; *s = '\0'; break; } else s++; } } cs->tag = tag; cs->id = id; cs->class = class; cs->pclass = pclass; return(cs); } static GList ParseSelector1(mp, s, slen) MemPool mp; char *s; size_t slen; { char *ep = s + slen; char *cp; char *last; CSSSelector cs; GList selectors; if ((s = ParseSpace(s, ep)) == NULL) return(NULL); selectors = GListCreateX(mp); last = s; cp = s; while (cp < ep) { if (isspace8(*cp)) { if ((cs = ParseSelector2(mp, last, cp - last)) != NULL) { GListAddTail(selectors, cs); } if ((cp = ParseSpace(cp, ep)) == NULL) break; last = cp; } else cp++; } if (last < cp) { if ((cs = ParseSelector2(mp, last, cp - last)) != NULL) { GListAddTail(selectors, cs); } } if (GListGetHead(selectors) == NULL) return(NULL); return(selectors); } /* * ParseSelector */ static GList ParseSelector(mp, s, slen) MemPool mp; char *s; size_t slen; { GList slist, xlist; char *ep; char *last; slist = GListCreateX(mp); ep = s + slen; while (s < ep) { last = s; s = ParseToChar(s, ep, ',', false); if (s == NULL) s = ep; if (last < s && (xlist = ParseSelector1(mp, last, s - last)) != NULL) { GListAddTail(slist, xlist); } s++; } if (GListGetHead(slist) == NULL) return(NULL); return(slist); } /* * scompare * * do the sort of string comparison that we need here */ static int scompare(s1, s2) char *s1, *s2; { if (s1 == s2) return(0); if (s1 == NULL || s2 == NULL) return(-1); if (strlen(s1) != strlen(s2)) return(-1); return(strcasecmp(s1, s2)); } /* * ParseRule * * Parse a single rule. Return false if parsing error that leads to EOF. */ static bool ParseRule(css, ci) CSSContext css; CSSInput ci; { char *last; GList slist; GList plist; CSSRule cr; slist = GListCreateX(css->mp); last = ci->cp; if ((ci->cp = ParseToChar(ci->cp, ci->ep, '{', false)) == NULL) { ci->cp = ci->ep; return(false); } slist = ParseSelector(css->mp, last, ci->cp - last); ci->cp++; /* need to get past the '{' from above */ last = ci->cp; if ((ci->cp = ParseToChar(ci->cp, ci->ep, '}', false)) == NULL) { ci->cp = ci->ep; return(false); } plist = ParseProperties(css->mp, last, ci->cp - last); ci->cp++; /* * Return true because parse was successful. */ if (plist == NULL || GListGetHead(plist) == NULL || slist == NULL || GListGetHead(slist) == NULL) return(true); cr = (CSSRule)MPCGet(css->mp, sizeof(struct CSSRuleP)); cr->selectors = slist; cr->properties = plist; GListAddTail(css->rules, cr); return(true); } /* * ParseCSS * * Toplevel parser loop. */ static void ParseCSS(css) CSSContext css; { CSSInput ci = (CSSInput)GListGetHead(css->inputs); if (ci == NULL) { if (css->proc == NULL) return; /* * Should call callback here because this means all CSS text including * remote text has been parsed. Callback must be made from a toplevel * task so create a task first. */ return; } ci->cp = ParseSpace(ci->cp, ci->ep); while (ci->cp != NULL && ci->cp < ci->ep) { if (*ci->cp == '@') { if (!ParseAt(css, ci)) return; } else if (!ParseRule(css, ci)) return; ci->cp = ParseSpace(ci->cp, ci->ep); } GListPop(css->inputs); ParseCSS(css); return; } CSSContext CSSParseBuffer(cs, b, blen, proc, closure) ChimeraContext cs; char *b; size_t blen; CSSProc proc; void *closure; { CSSContext css; MemPool mp; CSSInput ci; mp = MPCreate(); css = (CSSContext)MPCGet(mp, sizeof(struct CSSContextP)); css->mp = mp; css->cs = cs; css->rules = GListCreateX(mp); css->props = GListCreateX(mp); css->inputs = GListCreateX(mp); css->proc = proc; css->closure = closure; ci = (CSSInput)MPCGet(mp, sizeof(struct CSSInputP)); ci->b = b; ci->blen = blen; ci->cp = b; ci->ep = b + blen; GListAddHead(css->inputs, ci); ParseCSS(css); return(css); } /* * CSSDestroyContext */ void CSSDestroyContext(css) CSSContext css; { MPDestroy(css->mp); return; } static bool SelectorListMatch(slist1, slist2) GList slist1, slist2; { CSSSelector cs, ncs; /* * This code needs attention. It compares two selector lists. */ for (cs = (CSSSelector)GListGetHead(slist1), ncs = (CSSSelector)GListGetHead(slist2); cs != NULL && ncs != NULL; cs = (CSSSelector)GListGetNext(slist1), ncs = (CSSSelector)GListGetNext(slist2)) { if (scompare(ncs->tag, cs->tag) != 0 || scompare(ncs->id, cs->id) != 0 || scompare(ncs->class, cs->class) != 0 || scompare(ncs->pclass, cs->pclass) != 0) { return(false); } } if (ncs != NULL || cs != NULL) return(false); return(true); } /* * CSSCreateSelector */ CSSSelector CSSCreateSelector(mp) MemPool mp; { return((CSSSelector)MPCGet(mp, sizeof(struct CSSSelectorP))); } /* * CSSSetSelector */ void CSSSetSelector(cs, tag, id, class, pclass) CSSSelector cs; char *tag, *id, *class, *pclass; { cs->tag = tag; cs->id = id; cs->class = class; cs->pclass = pclass; return; } /* * CSSFindProperty */ char * CSSFindProperty(css, selectors, name) CSSContext css; GList selectors; char *name; { CSSProperty p; GList slist, xlist; CSSSelector h; if ((h = GListGetHead(selectors)) != GListGetTail(selectors)) { slist = GListCreate(); GListAddHead(slist, h); } else slist = NULL; for (p = (CSSProperty)GListGetHead(css->props); p != NULL; p = (CSSProperty)GListGetNext(css->props)) { if (scompare(p->name, name) == 0) { for (xlist = (GList)GListGetHead(p->cr->selectors); xlist != NULL; xlist = (GList)GListGetNext(p->cr->selectors)) { if ((slist != NULL && SelectorListMatch(xlist, slist)) || SelectorListMatch(xlist, selectors)) { if (slist != NULL) GListDestroy(slist); return(p->value); } } } } if (slist != NULL) GListDestroy(slist); return(NULL); } /* * CSSPrintSelectorList */ void CSSPrintSelectorList(slist) GList slist; { CSSSelector s; for (s = (CSSSelector)GListGetHead(slist); s != NULL; s = (CSSSelector)GListGetNext(slist)) { if (s->tag != NULL) fprintf (stderr, "%s ", s->tag); if (s->id != NULL) fprintf (stderr, "%s ", s->id); if (s->class != NULL) fprintf (stderr, "%s ", s->class); if (s->pclass != NULL) fprintf (stderr, "%s ", s->pclass); fprintf (stderr, "\n"); } return; } /* * CSSPrint */ void CSSPrint(css) CSSContext css; { CSSRule cr; CSSSelector cs; GList slist; CSSProperty prop; for (cr = (CSSRule)GListGetHead(css->rules); cr != NULL; cr = (CSSRule)GListGetNext(css->rules)) { for (slist = (GList)GListGetHead(cr->selectors); slist != NULL; slist = (GList)GListGetNext(cr->selectors)) { for (cs = (CSSSelector)GListGetHead(slist); cs != NULL; cs = (CSSSelector)GListGetNext(slist)) { printf ("%s ", cs->tag); } printf (", "); } printf ("{\n"); for (prop = (CSSProperty)GListGetHead(cr->properties); prop != NULL; prop = (CSSProperty)GListGetNext(cr->properties)) { printf ("\t%s : %s;\n", prop->name, prop->value); } printf ("}\n"); } } #ifdef CSSDEBUG #include #include main(argc, argv) int argc; char *argv[]; { CSSContext css; FILE *fp; char *b; char *name; struct stat st; GList slist; CSSSelector cs; MemPool mp; if (argc < 2) exit(1); if (stat(argv[1], &st) != 0) exit(1); if ((b = (char *)alloc_mem(st.st_size)) == NULL) exit(1); if ((fp = fopen(argv[1], "r")) == NULL) exit(1); fread(b, 1, st.st_size, fp); fclose(fp); css = CSSParseBuffer(NULL, b, st.st_size, NULL, NULL); mp = MPCreate(); slist = GListCreate(); cs = CSSCreateSelector(mp); CSSSetSelector(cs, "H5", NULL, NULL, NULL); GListAddHead(slist, cs); name = "white-space"; if ((b = CSSFindProperty(css, slist, name)) != NULL) { printf ("%s == %s\n", name, b); } else printf ("%s not found\n", name); CSSPrint(css); CSSDestroyContext(css); exit(0); } #endif