#include #define usprintf(b, f, x) (void)sprintf((char *)(b), (f), (x)) #include #include #define ustrlen(x) strlen((char const *)(x)) #define ustrcmp(x,y) strcmp((char const *)(x), (char const *)(y)) #define ustrcpy(x,y) (void)strcpy((char *)(x), (char const *)(y)) #include "qh.h" /* All the counters for an element are linked together so that they look like a list of ROKXML_bindings. That way they can quickly be snapped onto the front of an element's existing attributes. There is .base.next link to next counter .base.name name of attribute to define .base.value points to .buffer .buffer holds the current formatted value .current the current value .initial the first value to be reported .increment the increment .format one of '1aAiIxX" */ struct Counter { struct ROKXML_binding base; wchar buffer[12]; int current; int initial; int increment; char format; }; /* Each element has a list of counters that apply to it. It also has a list of counters that it must reset. These are the "count kids" of the element. */ struct CountKid { struct CountKid *next; struct Counter *counter; }; /* An element has .name a name .first the beginning of its list of counters .last the last counter in its list .kids its list of "children" There is no .next, because we hold these in an array. If/when that changes, we might use a BST. */ struct Element { wchar const *name; struct ROKXML_binding *first; struct ROKXML_binding *last; struct CountKid *kids; }; #define MAX_ELEMENTS 40 static struct Element counted[MAX_ELEMENTS]; static int ncounted = 0; static wchar *xcat(wchar *d, char const *s) { (void)strcat((char *)d, s); return d + strlen((char const *)d); } static void do_format(wchar *buffer, int n, char format) { wchar *p = buffer; if (n < 0) *p++ = '-', n = -n; switch (format) { case 'x': usprintf(p, "%x", (unsigned)n); break; case 'X': usprintf(p, "%X", (unsigned)n); break; case 'i': case 'I': if (!(n < 4000 && n > -4000 && n != 0)) { usprintf(p, "%d", n); break; } *p = '\0'; switch (n/1000) { case 3: p = xcat(p, "mmm"); break; case 2: p = xcat(p, "mm"); break; case 1: p = xcat(p, "m"); break; case 0: break; } switch ((n/100)%10) { case 9: p = xcat(p, "cm"); break; case 8: p = xcat(p, "dccc"); break; case 7: p = xcat(p, "dcc"); break; case 6: p = xcat(p, "dc"); break; case 5: p = xcat(p, "d"); break; case 4: p = xcat(p, "cd"); break; case 3: p = xcat(p, "ccc"); break; case 2: p = xcat(p, "cc"); break; case 1: p = xcat(p, "c"); break; case 0: break; } switch ((n/10)%10) { case 9: p = xcat(p, "xc"); break; case 8: p = xcat(p, "lxxx"); break; case 7: p = xcat(p, "lxx"); break; case 6: p = xcat(p, "lx"); break; case 5: p = xcat(p, "l"); break; case 4: p = xcat(p, "xl"); break; case 3: p = xcat(p, "xxx"); break; case 2: p = xcat(p, "xx"); break; case 1: p = xcat(p, "x"); break; case 0: break; } switch (n%10) { case 9: p = xcat(p, "ix"); break; case 8: p = xcat(p, "viii"); break; case 7: p = xcat(p, "vii"); break; case 6: p = xcat(p, "vi"); break; case 5: p = xcat(p, "v"); break; case 4: p = xcat(p, "iv"); break; case 3: p = xcat(p, "iii"); break; case 2: p = xcat(p, "ii"); break; case 1: p = xcat(p, "i"); break; case 0: break; } break; case 'a': case 'A': if (!(n < 18279 && n > -18279 && n != 0)) { usprintf(p, "%d", n); break; } *p = '\0'; if (n <= 26) { n -= 1; *p++ = n + 'a'; } else if (n <= 26*26 + 26) { n -= 26 + 1; *p++ = n/26 + 'a'; *p++ = n%26 + 'a'; } else { n -= 26*26 + 26 + 1; *p++ = n/(26*26) + 'a'; *p++ = (n/26)%26 + 'a'; *p++ = n%26 + 'a'; } *p = '\0'; break; default: usprintf(p, "%d", n); break; } if (format >= 'A' && format <= 'Z') { wchar c; for (p = buffer; (c = *p) != '\0'; p++) { if (c >= 'a' && c <= 'z') *p = c-32; } } } static struct ROKXML_binding *add_counters( wchar const *tag, struct ROKXML_binding *atts ) { int i; struct CountKid *k; struct Counter *c; for (i = ncounted; --i >= 0; ) if (counted[i].name[0] == tag[0] && ustrcmp(counted[i].name+1, tag+1) == 0 ) break; if (i < 0) return atts; for (k = counted[i].kids; k != 0; k = k->next) { c = k->counter; c->current = c->initial - c->increment; } c = (struct Counter *)counted[i].first; if (c == 0) return atts; for (;;) { if (c->format != '=') { c->current += c->increment; do_format(c->buffer, c->current, c->format); } if (c == (struct Counter *)counted[i].last) break; c = (struct Counter *)c->base.next; } c->base.next = atts; return counted[i].first; } static struct ROKXML_handlers const *original; static struct ROKXML_handlers count_handlers; static void count_begin(wchar const *tag, struct ROKXML_binding *atts) { original->begin(tag, add_counters(tag, atts)); } static void count_empty(wchar const *tag, struct ROKXML_binding *atts) { original->empty(tag, add_counters(tag, atts)); } struct ROKXML_handlers const *install_counters( struct ROKXML_handlers const *outer_handlers ) { if (counted == 0) return outer_handlers; original = outer_handlers; count_handlers = *outer_handlers; count_handlers.begin = count_begin; count_handlers.empty = count_empty; return &count_handlers; } static void *xmalloc(size_t n) { void *r = malloc(n); if (r == 0) abort(); return r; } /* *p points here on entry |some-name another-name ....| *p points here on exit If b is null, a new string is allocated and filled in with "some-name"; otherwise "some-name" is copied to b[...] and that is returned. */ static wchar *find_name(wchar const **p, wchar *b) { wchar buffer[128]; wchar *d = b ? b : buffer; wchar const *s = *p; while ((*d = *s++) > ' ') d++; *d = '\0'; s--; while (*s <= ' ' && *s != '\0') s++; *p = s; if (b == 0) ustrcpy(b = xmalloc(d-buffer+1), buffer); return b; } static struct Element *find_element(wchar const **p) { wchar buffer[128]; struct Element *e; find_name(p, buffer); for (e = &counted[ncounted]; --e >= counted; ) if (e->name[0] == buffer[0] && ustrcmp(e->name + 1, buffer + 1) == 0 ) return e; if (ncounted == MAX_ELEMENTS) { fprintf(stderr, "More than %d elements involved in counting\n", MAX_ELEMENTS); exit(EXIT_FAILURE); } e = &counted[ncounted++]; ustrcpy(e->name = xmalloc(ustrlen(buffer) + 1), buffer); e->first = e->last = 0; e->kids = 0; return e; } /* new_counter(description) description may be s* '@' file name or s* name s+ name s+ nmtoken [s+ definition] s* where the first name is the element name, the second name is the attribute name, the nmtoken is 1, a, A, i, I, x, or X, and the definition is = s* text (fixed text), or [number [s+ number]] {s+ name}... where the first number is the initial value, the second number is the increment, and the names are the names of the elements that are parents of this counter. (Strictly speaking, counters should have counters as parents, but when any counter of an element is advanced, all of them are, so it doesn't matter.) */ void new_counter(char const *d) { wchar const *s = (wchar const *)d; wchar c; while ((c = *s) <= ' ' && c != '\0') c++; if (c == '@') { char buffer[256]; FILE *source = fopen((char const *)(s+1), "r"); if (source == 0) { perror((char const *)(s+1)); exit(EXIT_FAILURE); } while (fgets(buffer, sizeof buffer, source)) new_counter(buffer); fclose(source); } else if (c != '\0') { struct Counter *t = xmalloc(sizeof *t); struct Element *e = find_element(&s); struct CountKid *k; wchar *a = find_name(&s, 0); char *x; wchar format = *s; switch (format) { case '1': case 'a': case 'A': case 'i': case 'I': case 'x': case 'X': break; case '=': do c = *++s; while (c <= ' ' && c != '\0'); ustrcpy(t->base.value = xmalloc(ustrlen(s)), s); s = s + ustrlen(s); break; default: fprintf(stderr, "Counter %s %s, format must be 1aAiIxX not %c\n", e->name, a, format); exit(EXIT_FAILURE); } t->base.name = a, t->base.value = t->buffer, t->initial = t->increment = 1, t->format = format; do c = *++s; while (c <= ' ' && c != '\0'); if (c <= '9' && (c >= '0' || c == '-')) { t->initial = strtol((char const *)s, &x, 10); s = (wchar const *)x; while ((c = *s) <= ' ' && c != '\0') s++; if (c <= '9' && (c >= '0' || c == '-')) { t->increment = strtol((char const *)s, &x, 10); s = (wchar const *)x; while ((c = *s) <= ' ' && c != '\0') s++; } } t->current = t->initial - t->increment; if ((t->base.next = e->first) == 0) e->last = &t->base; e->first = &t->base; while (*s != '\0') { e = find_element(&s); k = xmalloc(sizeof *k); k->next = e->kids; k->counter = t; e->kids = k; } } }