diff options
author | June McEnroe <june@causal.agency> | 2020-07-16 17:46:19 -0400 |
---|---|---|
committer | June McEnroe <june@causal.agency> | 2020-07-16 17:46:19 -0400 |
commit | 6500baa1ffc720ec4c0f256dd99dab478225122b (patch) | |
tree | 8afb6e817f3fd21c93a42a6fd65de931b262be05 /html.c | |
parent | Remove viewport meta (diff) | |
download | scooper-6500baa1ffc720ec4c0f256dd99dab478225122b.tar.gz scooper-6500baa1ffc720ec4c0f256dd99dab478225122b.zip |
Big refactor, remove struct Scope
Diffstat (limited to 'html.c')
-rw-r--r-- | html.c | 559 |
1 files changed, 266 insertions, 293 deletions
diff --git a/html.c b/html.c index 2d3ce2f..a28d84b 100644 --- a/html.c +++ b/html.c @@ -47,32 +47,19 @@ enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) { || khtml_closeelem(html, 1); } -enum kcgi_err htmlScopeFields(struct khtmlreq *html, struct Scope scope) { - if (scope.network) { - enum kcgi_err error = khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "hidden", - KATTR_NAME, Keys[Network].name, - KATTR_VALUE, scope.network, - KATTR__MAX - ); - if (error) return error; - } - if (scope.context) { - enum kcgi_err error = khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "hidden", - KATTR_NAME, Keys[Context].name, - KATTR_VALUE, scope.context, - KATTR__MAX - ); - if (error) return error; - } - return KCGI_OK; +enum kcgi_err +htmlHidden(struct khtmlreq *html, struct kreq *req, enum Key key) { + if (!req->fieldmap[key]) return KCGI_OK; + return khtml_attr( + html, KELEM_INPUT, + KATTR_TYPE, "hidden", + KATTR_NAME, Keys[key].name, + KATTR_VALUE, req->fieldmap[key]->val, + KATTR__MAX + ); } -enum kcgi_err -htmlNav(struct khtmlreq *html, struct Scope scope) { +enum kcgi_err htmlNav(struct khtmlreq *html, struct kreq *req) { enum kcgi_err error = 0 || khtml_elem(html, KELEM_NAV) || khtml_elem(html, KELEM_OL) @@ -82,44 +69,53 @@ htmlNav(struct khtmlreq *html, struct Scope scope) { || khtml_closeelem(html, 2); if (error) return error; - if (scope.network) { + const char *network = NULL; + const char *context = NULL; + if (req->fieldmap[Network]) { + network = req->fieldmap[Network]->parsed.s; + if (req->fieldmap[Context]) { + context = req->fieldmap[Context]->parsed.s; + } + } + + if (network) { char *href = khttp_urlpart( NULL, NULL, Pages[Contexts], - Keys[Network].name, scope.network, + Keys[Network].name, network, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); error = 0 || khtml_elem(html, KELEM_LI) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_puts(html, scope.network) + || khtml_puts(html, network) || khtml_closeelem(html, 2); + free(href); if (error) return error; } - if (scope.network && scope.context) { + if (context) { + const char *context = req->fieldmap[Context]->parsed.s; char *href = khttp_urlpart( NULL, NULL, Pages[Events], - Keys[Network].name, scope.network, - Keys[Context].name, scope.context, + Keys[Network].name, network, + Keys[Context].name, context, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); error = 0 || khtml_elem(html, KELEM_LI) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_puts(html, scope.context) + || khtml_puts(html, context) || khtml_closeelem(html, 2); + free(href); if (error) return error; } - char label[256]; - snprintf( - label, sizeof(label), "Search%s%s", - (scope.network ? " " : ""), - (scope.context ? scope.context : scope.network ? scope.network : "") - ); - + const char *query = NULL; + if (req->fieldmap[Query]) { + query = req->fieldmap[Query]->parsed.s; + } return 0 || khtml_closeelem(html, 1) || khtml_attr( @@ -132,44 +128,124 @@ htmlNav(struct khtmlreq *html, struct Scope scope) { html, KELEM_INPUT, KATTR_TYPE, "search", KATTR_NAME, Keys[Query].name, - KATTR_VALUE, (scope.query ? scope.query : ""), + KATTR_VALUE, (query ? query : ""), KATTR__MAX ) - || htmlScopeFields(html, scope) - || khtml_attr( - html, KELEM_INPUT, - KATTR_TYPE, "submit", - KATTR_VALUE, label, - KATTR__MAX + || htmlHidden(html, req, Network) + || htmlHidden(html, req, Context) + || khtml_elem(html, KELEM_BUTTON) + || khtml_printf( + html, "Search %s", + (context ? context : network ? network : "") ) - || khtml_closeelem(html, 2); + || khtml_closeelem(html, 3); } -static const char *SourceURL = "https://git.causal.agency/scooper"; -static const char *SyntaxURL = { - "https://www.sqlite.org/fts5.html#full_text_query_syntax" -}; -static const char *Columns = { - "network, channel, query, nick, user, target, message" -}; +enum kcgi_err linkify(struct khtmlreq *html, const char *str, size_t len) { + static const char *Pattern = "https?://([^[:space:]>\"()]|[(][^)]*[)])+"; + static regex_t regex; + if (!regex.re_nsub) { + int error = regcomp(®ex, Pattern, REG_EXTENDED); + assert(!error); + } -enum kcgi_err htmlFooter(struct khtmlreq *html) { - return 0 - || khtml_closeto(html, 0) - || khtml_elem(html, KELEM_FOOTER) - || khtml_elem(html, KELEM_SPAN) - || khtml_attr(html, KELEM_A, KATTR_HREF, SourceURL, KATTR__MAX) - || khtml_puts(html, "scooper is AGPLv3+") - || khtml_closeelem(html, 2) - || khtml_putc(html, ' ') - || khtml_elem(html, KELEM_SPAN) - || khtml_attr(html, KELEM_A, KATTR_HREF, SyntaxURL, KATTR__MAX) - || khtml_puts(html, "Search syntax") - || khtml_closeelem(html, 1) - || khtml_putc(html, ' ') - || khtml_attr(html, KELEM_SPAN, KATTR_TITLE, Columns, KATTR__MAX) - || khtml_puts(html, "Columns") - || khtml_closeto(html, 0); + regmatch_t match = {0}; + while (!regexec(®ex, str, 1, &match, 0)) { + if ((size_t)match.rm_eo > len) break; + + char *href = strndup(&str[match.rm_so], match.rm_eo - match.rm_so); + if (!href) err(EX_OSERR, "strndup"); + + enum kcgi_err error = 0 + || khtml_write(str, match.rm_so, html) + || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) + || khtml_write(&str[match.rm_so], match.rm_eo - match.rm_so, html) + || khtml_closeelem(html, 1); + free(href); + if (error) return error; + + str += match.rm_eo; + len -= match.rm_eo; + } + if (len) return khtml_write(str, len, html); + return KCGI_OK; +} + +static const struct Style { + int fg, bg; + bool b, r, i, u; +} Default = { .fg = 99, .bg = 99 }; + +enum kcgi_err htmlStyle(struct khtmlreq *html, struct Style style) { + enum kcgi_err error = KCGI_OK; + char class[sizeof("fg99")]; + if (style.fg != Default.fg) { + snprintf(class, sizeof(class), "fg%02d", style.fg); + error = error || khtml_attr( + html, KELEM_SPAN, + KATTR_CLASS, class, + KATTR__MAX + ); + } + if (style.bg != Default.bg) { + snprintf(class, sizeof(class), "bg%02d", style.bg); + error = error || khtml_attr( + html, KELEM_SPAN, + KATTR_CLASS, class, + KATTR__MAX + ); + } + if (style.b) error = error || khtml_elem(html, KELEM_B); + if (style.r) error = error || khtml_elem(html, KELEM_MARK); + if (style.i) error = error || khtml_elem(html, KELEM_I); + if (style.u) error = error || khtml_elem(html, KELEM_U); + return error; +} + +enum kcgi_err htmlIRC(struct khtmlreq *html, const char *str) { + enum kcgi_err error = khtml_attr( + html, KELEM_SPAN, + KATTR_CLASS, "irc", + KATTR__MAX + ); + if (error) return error; + + size_t top = khtml_elemat(html); + struct Style style = Default; + for (;;) { + size_t len = strcspn(str, "\2\3\17\26\35\37"); + if (len) { + error = 0 + || khtml_closeto(html, top) + || htmlStyle(html, style) + || linkify(html, str, len); + if (error) return error; + } + + str += len; + if (!*str) break; + switch (*str++) { + break; case '\2': style.b ^= true; + break; case '\17': style = Default; + break; case '\26': style.r ^= true; + break; case '\35': style.i ^= true; + break; case '\37': style.u ^= true; + break; case '\3': { + if (!isdigit(*str)) { + style.fg = Default.fg; + style.bg = Default.bg; + break; + } + style.fg = *str++ - '0'; + if (isdigit(*str)) style.fg = style.fg * 10 + *str++ - '0'; + if (str[0] != ',' || !isdigit(str[1])) break; + str++; + style.bg = *str++ - '0'; + if (isdigit(*str)) style.bg = style.bg * 10 + *str++ - '0'; + } + } + } + return khtml_closeto(html, top) || khtml_closeelem(html, 1); } static const char *timestamp(time_t time) { @@ -178,74 +254,73 @@ static const char *timestamp(time_t time) { return stamp; } -static enum kcgi_err eventTime(struct khtmlreq *html, struct Event event) { - char *base = NULL; - if (event.network && event.context) { - base = khttp_urlpart( +static enum kcgi_err +eventTime(struct khtmlreq *html, const struct Event *event) { + char *page = NULL; + char *href = NULL; + if (event->network && event->context) { + page = khttp_urlpart( NULL, NULL, Pages[Events], - Keys[Network].name, event.network, - Keys[Context].name, event.context, - Keys[After].name, timestamp(event.time - eventsOverlap), + Keys[Network].name, event->network, + Keys[Context].name, event->context, + Keys[After].name, timestamp(event->time - eventsOverlap), NULL ); - if (!base) err(EX_OSERR, "khttp_urlpart"); + if (!page) err(EX_OSERR, "khttp_urlpart"); } - char *href = NULL; - asprintf(&href, "%s#%" PRId64, (base ? base : ""), event.event); + asprintf(&href, "%s#%" PRId64, (page ? page : ""), event->event); if (!href) err(EX_OSERR, "asprintf"); - if (base) free(base); + if (page) free(page); + const char *stamp = timestamp(event->time); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "time", KATTR__MAX) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) || khtml_attr( html, KELEM_TIME, - KATTR_DATETIME, timestamp(event.time), + KATTR_DATETIME, stamp, KATTR__MAX ) - || khtml_puts(html, timestamp(event.time)) + || khtml_puts(html, stamp) || khtml_closeelem(html, 3); - free(href); return error; } static enum kcgi_err -eventNetwork(struct khtmlreq *html, struct Scope scope, struct Event event) { - if (scope.network || !scope.query || !event.network) return KCGI_OK; +eventNetwork(struct khtmlreq *html, struct kreq *req, struct Event *event) { + if (!req->fieldmap[Query] || req->fieldmap[Network]) return KCGI_OK; char *href = khttp_urlpart( NULL, NULL, Pages[Search], - Keys[Network].name, event.network, - Keys[Query].name, scope.query, + Keys[Query].name, req->fieldmap[Query]->parsed.s, + Keys[Network].name, event->network, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "network", KATTR__MAX) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_puts(html, event.network) + || khtml_puts(html, event->network) || khtml_closeelem(html, 2); free(href); return error; } static enum kcgi_err -eventContext(struct khtmlreq *html, struct Scope scope, struct Event event) { - if (scope.context || !scope.query || !event.network || !event.context) { - return KCGI_OK; - } +eventContext(struct khtmlreq *html, struct kreq *req, struct Event *event) { + if (!req->fieldmap[Query] || req->fieldmap[Context]) return KCGI_OK; char *href = khttp_urlpart( NULL, NULL, Pages[Search], - Keys[Network].name, event.network, - Keys[Context].name, event.context, - Keys[Query].name, scope.query, + Keys[Query].name, req->fieldmap[Query]->parsed.s, + Keys[Network].name, event->network, + Keys[Context].name, event->context, NULL ); if (!href) err(EX_OSERR, "khttp_urlpart"); enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "context", KATTR__MAX) || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_puts(html, event.context) + || khtml_puts(html, event->context) || khtml_closeelem(html, 2); free(href); return error; @@ -262,115 +337,93 @@ static int hash(const char *str) { return 2 + hash % 74; } -static const char *colorClass(int color) { - static char class[sizeof("fg99")]; - snprintf(class, sizeof(class), "fg%02d", color); - return class; -} - -static enum kcgi_err eventNick(struct khtmlreq *html, struct Event event) { +static enum kcgi_err +eventNick(struct khtmlreq *html, const struct Event *event) { char *mask = NULL; - asprintf(&mask, "%s!%s@%s", event.nick, event.user, event.host); + char class[sizeof("fg99")]; + snprintf(class, sizeof(class), "fg%02d", hash(event->user)); + asprintf(&mask, "%s!%s@%s", event->nick, event->user, event->host); if (!mask) err(EX_OSERR, "asprintf"); - + const char *format = "%s"; + switch (event->type) { + break; case Privmsg: format = "<%s>"; + break; case Notice: format = "-%s-"; + break; case Action: format = "* %s"; + break; default:; + } enum kcgi_err error = 0 || khtml_attr(html, KELEM_TD, KATTR_CLASS, "nick", KATTR__MAX) || khtml_attr( html, KELEM_SPAN, - KATTR_CLASS, colorClass(hash(event.user)), + KATTR_CLASS, class, KATTR_TITLE, mask, KATTR__MAX - ); + ) + || khtml_printf(html, format, event->nick) + || khtml_closeelem(html, 2); free(mask); - if (error) return error; - - switch (event.type) { - break; case Privmsg: error = khtml_printf(html, "<%s>", event.nick); - break; case Action: error = khtml_printf(html, "* %s", event.nick); - break; case Notice: error = khtml_printf(html, "-%s-", event.nick); - break; default: error = khtml_puts(html, event.nick); - } - return error || khtml_closeelem(html, 2); -} - -static enum kcgi_err typeJoin(struct khtmlreq *html, struct Event event) { - (void)event; - return khtml_puts(html, "joined"); -} - -static enum kcgi_err typePart(struct khtmlreq *html, struct Event event) { - if (!event.message) return khtml_puts(html, "left"); - return 0 - || khtml_puts(html, "left (") - || htmlIRC(html, event.message) - || khtml_puts(html, ")"); -} - -static enum kcgi_err typeQuit(struct khtmlreq *html, struct Event event) { - if (!event.message) return khtml_puts(html, "quit"); - return 0 - || khtml_puts(html, "quit (") - || htmlIRC(html, event.message) - || khtml_puts(html, ")"); + return error; } -static enum kcgi_err typeKick(struct khtmlreq *html, struct Event event) { - if (!event.target) return KCGI_OK; - if (!event.message) return khtml_printf(html, "kicked %s", event.target); +static enum kcgi_err +maybeMessage(struct khtmlreq *html, const struct Event *event) { + if (!event->message) return KCGI_OK; return 0 - || khtml_printf(html, "kicked %s (", event.target) - || htmlIRC(html, event.message) + || khtml_puts(html, " (") + || htmlIRC(html, event->message) || khtml_puts(html, ")"); } -static enum kcgi_err typeNick(struct khtmlreq *html, struct Event event) { - if (!event.target) return KCGI_OK; - return 0 - || khtml_puts(html, "changed nick to ") - || khtml_attr( - html, KELEM_SPAN, - KATTR_CLASS, colorClass(hash(event.user)), - KATTR__MAX - ) - || khtml_puts(html, event.target) - || khtml_closeelem(html, 1); -} - -static enum kcgi_err typeTopic(struct khtmlreq *html, struct Event event) { - if (!event.message) return KCGI_OK; - return 0 - || khtml_puts(html, "set the topic: ") - || htmlIRC(html, event.message); -} - -static enum kcgi_err typeBan(struct khtmlreq *html, struct Event event) { - if (!event.target) return KCGI_OK; - return khtml_printf(html, "banned %s", event.target); -} - -static enum kcgi_err typeUnban(struct khtmlreq *html, struct Event event) { - if (!event.target) return KCGI_OK; - return khtml_printf(html, "unbanned %s", event.target); -} - -static enum kcgi_err eventMessage(struct khtmlreq *html, struct Event event) { +static enum kcgi_err +eventMessage(struct khtmlreq *html, const struct Event *event) { enum kcgi_err error = khtml_attr( html, KELEM_TD, KATTR_CLASS, "message", KATTR__MAX ); if (error) return error; - switch (event.type) { - break; case Join: error = typeJoin(html, event); - break; case Part: error = typePart(html, event); - break; case Quit: error = typeQuit(html, event); - break; case Kick: error = typeKick(html, event); - break; case Nick: error = typeNick(html, event); - break; case Topic: error = typeTopic(html, event); - break; case Ban: error = typeBan(html, event); - break; case Unban: error = typeUnban(html, event); + switch (event->type) { + break; case Join: { + error = khtml_puts(html, "joined"); + } + break; case Part: { + error = khtml_puts(html, "left") || maybeMessage(html, event); + } + break; case Quit: { + error = khtml_puts(html, "quit") || maybeMessage(html, event); + } + break; case Kick: { + if (!event->target) break; + error = 0 + || khtml_printf(html, "kicked %s", event->target) + || maybeMessage(html, event); + } + break; case Nick: { + if (!event->target) break; + char class[sizeof("fg99")]; + snprintf(class, sizeof(class), "fg%02d", hash(event->user)); + error = 0 + || khtml_puts(html, "changed nick to ") + || khtml_attr(html, KELEM_SPAN, KATTR_CLASS, class, KATTR__MAX) + || khtml_puts(html, event->target) + || khtml_closeelem(html, 1); + } + break; case Topic: { + if (!event->message) break; + error = 0 + || khtml_puts(html, "set the topic: ") + || htmlIRC(html, event->message); + } + break; case Ban: { + if (!event->target) break; + error = khtml_printf(html, "banned %s", event->target); + } + break; case Unban: { + if (!event->target) break; + error = khtml_printf(html, "unbanned %s", event->target); + } break; default: { - if (event.message) error = htmlIRC(html, event.message); + if (event->message) error = htmlIRC(html, event->message); } } return error || khtml_closeelem(html, 1); @@ -383,126 +436,46 @@ static const char *Types[TypesLen] = { }; enum kcgi_err -htmlEvent(struct khtmlreq *html, struct Scope scope, struct Event event) { - const char *type = (event.type < TypesLen ? Types[event.type] : "unknown"); +htmlEvent(struct khtmlreq *html, struct kreq *req, struct Event *event) { + const char *type = (event->type < TypesLen ? Types[event->type] : "unknown"); return 0 || khtml_attrx( html, KELEM_TR, - KATTR_ID, KATTRX_INT, event.event, + KATTR_ID, KATTRX_INT, event->event, KATTR_CLASS, KATTRX_STRING, type, KATTR__MAX ) || eventTime(html, event) - || eventNetwork(html, scope, event) - || eventContext(html, scope, event) + || eventNetwork(html, req, event) + || eventContext(html, req, event) || eventNick(html, event) || eventMessage(html, event) || khtml_closeelem(html, 1); } -enum kcgi_err linkify(struct khtmlreq *html, const char *str, size_t len) { - static const char *Pattern = "https?://([^[:space:]>\"()]|[(][^)]*[)])+"; - static regex_t regex; - if (!regex.re_nsub) { - int error = regcomp(®ex, Pattern, REG_EXTENDED); - assert(!error); - } - - regmatch_t match = {0}; - while (!regexec(®ex, str, 1, &match, 0)) { - if ((size_t)match.rm_eo > len) break; - - char *href = strndup(&str[match.rm_so], match.rm_eo - match.rm_so); - if (!href) err(EX_OSERR, "strndup"); - - enum kcgi_err error = 0 - || khtml_write(str, match.rm_so, html) - || khtml_attr(html, KELEM_A, KATTR_HREF, href, KATTR__MAX) - || khtml_write(&str[match.rm_so], match.rm_eo - match.rm_so, html) - || khtml_closeelem(html, 1); - free(href); - if (error) return error; - - str += match.rm_eo; - len -= match.rm_eo; - } - if (len) return khtml_write(str, len, html); - return KCGI_OK; -} - -static const struct Style { - int fg, bg; - bool b, r, i, u; -} Default = { .fg = 99, .bg = 99 }; - -enum kcgi_err htmlStyle(struct khtmlreq *html, struct Style style) { - enum kcgi_err error = KCGI_OK; - char class[sizeof("fg99")]; - if (style.fg != Default.fg) { - snprintf(class, sizeof(class), "fg%02d", style.fg); - error = error || khtml_attr( - html, KELEM_SPAN, - KATTR_CLASS, class, - KATTR__MAX - ); - } - if (style.bg != Default.bg) { - snprintf(class, sizeof(class), "bg%02d", style.bg); - error = error || khtml_attr( - html, KELEM_SPAN, - KATTR_CLASS, class, - KATTR__MAX - ); - } - if (style.b) error = error || khtml_elem(html, KELEM_B); - if (style.r) error = error || khtml_elem(html, KELEM_MARK); - if (style.i) error = error || khtml_elem(html, KELEM_I); - if (style.u) error = error || khtml_elem(html, KELEM_U); - return error; -} - -enum kcgi_err htmlIRC(struct khtmlreq *html, const char *str) { - enum kcgi_err error = khtml_attr( - html, KELEM_SPAN, - KATTR_CLASS, "irc", - KATTR__MAX - ); - if (error) return error; - - size_t top = khtml_elemat(html); - struct Style style = Default; - for (;;) { - size_t len = strcspn(str, "\2\3\17\26\35\37"); - if (len) { - error = 0 - || khtml_closeto(html, top) - || htmlStyle(html, style) - || linkify(html, str, len); - if (error) return error; - } +static const char *SourceURL = "https://git.causal.agency/scooper"; +static const char *SyntaxURL = { + "https://www.sqlite.org/fts5.html#full_text_query_syntax" +}; +static const char *Columns = { + "network, channel, query, nick, user, target, message" +}; - str += len; - if (!*str) break; - switch (*str++) { - break; case '\2': style.b ^= true; - break; case '\17': style = Default; - break; case '\26': style.r ^= true; - break; case '\35': style.i ^= true; - break; case '\37': style.u ^= true; - break; case '\3': { - if (!isdigit(*str)) { - style.fg = Default.fg; - style.bg = Default.bg; - break; - } - style.fg = *str++ - '0'; - if (isdigit(*str)) style.fg = style.fg * 10 + *str++ - '0'; - if (str[0] != ',' || !isdigit(str[1])) break; - str++; - style.bg = *str++ - '0'; - if (isdigit(*str)) style.bg = style.bg * 10 + *str++ - '0'; - } - } - } - return khtml_closeto(html, top) || khtml_closeelem(html, 1); +enum kcgi_err htmlFooter(struct khtmlreq *html) { + return 0 + || khtml_closeto(html, 0) + || khtml_elem(html, KELEM_FOOTER) + || khtml_elem(html, KELEM_SPAN) + || khtml_attr(html, KELEM_A, KATTR_HREF, SourceURL, KATTR__MAX) + || khtml_puts(html, "scooper is AGPLv3+") + || khtml_closeelem(html, 2) + || khtml_putc(html, ' ') + || khtml_elem(html, KELEM_SPAN) + || khtml_attr(html, KELEM_A, KATTR_HREF, SyntaxURL, KATTR__MAX) + || khtml_puts(html, "Search syntax") + || khtml_closeelem(html, 1) + || khtml_putc(html, ' ') + || khtml_attr(html, KELEM_SPAN, KATTR_TITLE, Columns, KATTR__MAX) + || khtml_puts(html, "Columns") + || khtml_closeto(html, 0); } |