Graphviz 16.1.0~dev.20260823.0643
Loading...
Searching...
No Matches
htmllex.c
Go to the documentation of this file.
1
3/*************************************************************************
4 * Copyright (c) 2011 AT&T Intellectual Property
5 * All rights reserved. This program and the accompanying materials
6 * are made available under the terms of the Eclipse Public License v2.0
7 * which accompanies this distribution, and is available at
8 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
9 *
10 * Contributors: Details at https://graphviz.org
11 *************************************************************************/
12
13#include "config.h"
14
15#include "htmlparse.h"
16#include <assert.h>
17#include <cdt/cdt.h>
18#include <common/htmllex.h>
19#include <common/htmltable.h>
20#include <common/render.h>
21#include <limits.h>
22#include <stdatomic.h>
23#include <stdbool.h>
24#include <stddef.h>
25#include <stdint.h>
26#include <util/alloc.h>
27#include <util/gv_ctype.h>
28#include <util/startswith.h>
29#include <util/strcasecmp.h>
30#include <util/strview.h>
31#include <util/tokenize.h>
32#include <util/unused.h>
33
34#ifdef HAVE_EXPAT
35#include <expat.h>
36#endif
37
38#ifndef XML_STATUS_ERROR
39#define XML_STATUS_ERROR 0
40#endif
41
42static unsigned long htmllineno_ctx(htmllexstate_t *ctx);
43
44/* error_context:
45 * Print the last 2 "token"s seen.
46 */
47static void error_context(htmllexstate_t *ctx) {
48 agerr(AGPREV, "... %.*s%.*s ...\n", (int)ctx->prevtok.size, ctx->prevtok.data,
49 (int)ctx->currtok.size, ctx->currtok.data);
50}
51
52/* htmlerror:
53 * yyerror - called by yacc output
54 */
55void htmlerror(htmlscan_t *scanner, const char *msg) {
56 htmllexstate_t *ctx = &scanner->lexer;
57 if (ctx->error)
58 return;
59 ctx->error = 1;
60 agerrorf("%s in line %lu \n", msg, htmllineno(scanner));
61 error_context(&scanner->lexer);
62}
63
64#ifdef HAVE_EXPAT
65/* lexerror:
66 * called by lexer when unknown <..> is found.
67 */
68static void lexerror(htmllexstate_t *ctx, const char *name) {
69 ctx->tok = T_error;
70 ctx->error = 1;
71 agerrorf("Unknown HTML element <%s> on line %lu \n", name,
72 htmllineno_ctx(ctx));
73}
74
76static int icmp(const void *name, const void *item) {
77 const char *const *const j = item;
78 return strcasecmp(name, *j);
79}
80
81static int bgcolorfn(htmldata_t *p, const char *v) {
82 p->bgcolor = strdup(v);
83 return 0;
84}
85
86static int pencolorfn(htmldata_t *p, const char *v) {
87 p->pencolor = strdup(v);
88 return 0;
89}
90
91static int hreffn(htmldata_t *p, const char *v) {
92 p->href = strdup(v);
93 return 0;
94}
95
96static int sidesfn(htmldata_t *p, const char *v) {
97 unsigned short flags = 0;
98 char c;
99
100 while ((c = *v++)) {
101 switch (gv_tolower(c)) {
102 case 'l':
104 break;
105 case 't':
106 flags |= BORDER_TOP;
107 break;
108 case 'r':
110 break;
111 case 'b':
113 break;
114 default:
115 agwarningf("Unrecognized character '%c' (%d) in sides attribute\n", c, c);
116 break;
117 }
118 }
119 if (flags != BORDER_MASK)
120 p->flags |= flags;
121 return 0;
122}
123
124static int titlefn(htmldata_t *p, const char *v) {
125 p->title = strdup(v);
126 return 0;
127}
128
129static int portfn(htmldata_t *p, const char *v) {
130 p->port = strdup(v);
131 return 0;
132}
133
134#define DELIM " ,"
135
136static int stylefn(htmldata_t *p, const char *v) {
137 int rv = 0;
138 for (tok_t t = tok(v, DELIM); !tok_end(&t); tok_next(&t)) {
139 strview_t tk = tok_get(&t);
140 if (strview_case_str_eq(tk, "ROUNDED"))
141 p->style.rounded = true;
142 else if (strview_case_str_eq(tk, "RADIAL"))
143 p->style.radial = true;
144 else if (strview_case_str_eq(tk, "SOLID")) {
145 p->style.dotted = false;
146 p->style.dashed = false;
147 } else if (strview_case_str_eq(tk, "INVISIBLE") ||
148 strview_case_str_eq(tk, "INVIS"))
149 p->style.invisible = true;
150 else if (strview_case_str_eq(tk, "DOTTED"))
151 p->style.dotted = true;
152 else if (strview_case_str_eq(tk, "DASHED"))
153 p->style.dashed = true;
154 else {
155 agwarningf("Illegal value %.*s for STYLE - ignored\n", (int)tk.size,
156 tk.data);
157 rv = 1;
158 }
159 }
160 return rv;
161}
162
163static int targetfn(htmldata_t *p, const char *v) {
164 p->target = strdup(v);
165 return 0;
166}
167
168static int idfn(htmldata_t *p, const char *v) {
169 p->id = strdup(v);
170 return 0;
171}
172
173/* doInt:
174 * Scan v for integral value. Check that
175 * the value is >= min and <= max. Return value in ul.
176 * String s is name of value.
177 * Return 0 if okay; 1 otherwise.
178 */
179static int doInt(const char *v, char *s, int min, int max, long *ul) {
180 int rv = 0;
181 char *ep;
182 long b = strtol(v, &ep, 10);
183
184 if (ep == v) {
185 agwarningf("Improper %s value %s - ignored", s, v);
186 rv = 1;
187 } else if (b > max) {
188 agwarningf("%s value %s > %d - too large - ignored", s, v, max);
189 rv = 1;
190 } else if (b < min) {
191 agwarningf("%s value %s < %d - too small - ignored", s, v, min);
192 rv = 1;
193 } else
194 *ul = b;
195 return rv;
196}
197
198static int gradientanglefn(htmldata_t *p, const char *v) {
199 long u;
200
201 if (doInt(v, "GRADIENTANGLE", 0, 360, &u))
202 return 1;
203 p->gradientangle = (unsigned short)u;
204 return 0;
205}
206
207static int borderfn(htmldata_t *p, const char *v) {
208 long u;
209
210 if (doInt(v, "BORDER", 0, UCHAR_MAX, &u))
211 return 1;
212 p->border = (unsigned char)u;
213 p->flags |= BORDER_SET;
214 return 0;
215}
216
217static int cellpaddingfn(htmldata_t *p, const char *v) {
218 long u;
219
220 if (doInt(v, "CELLPADDING", 0, UCHAR_MAX, &u))
221 return 1;
222 p->pad = (unsigned char)u;
223 p->flags |= PAD_SET;
224 return 0;
225}
226
227static int cellspacingfn(htmldata_t *p, const char *v) {
228 long u;
229
230 if (doInt(v, "CELLSPACING", SCHAR_MIN, SCHAR_MAX, &u))
231 return 1;
232 p->space = (signed char)u;
233 p->flags |= SPACE_SET;
234 return 0;
235}
236
237static int cellborderfn(htmldata_t *data, const char *v) {
238 long u;
239 htmltbl_t *const p =
240 (htmltbl_t *)((uintptr_t)data - offsetof(htmltbl_t, data));
241
242 if (doInt(v, "CELLBORDER", 0, INT8_MAX, &u))
243 return 1;
244 p->cellborder = (int8_t)u;
245 return 0;
246}
247
248static int columnsfn(htmldata_t *data, const char *v) {
249 htmltbl_t *const p =
250 (htmltbl_t *)((uintptr_t)data - offsetof(htmltbl_t, data));
251 if (*v != '*') {
252 agwarningf("Unknown value %s for COLUMNS - ignored\n", v);
253 return 1;
254 }
255 p->vrule = true;
256 return 0;
257}
258
259static int rowsfn(htmldata_t *data, const char *v) {
260 htmltbl_t *const p =
261 (htmltbl_t *)((uintptr_t)data - offsetof(htmltbl_t, data));
262 if (*v != '*') {
263 agwarningf("Unknown value %s for ROWS - ignored\n", v);
264 return 1;
265 }
266 p->hrule = true;
267 return 0;
268}
269
270static int fixedsizefn(htmldata_t *p, const char *v) {
271 int rv = 0;
272 if (!strcasecmp(v, "TRUE"))
273 p->flags |= FIXED_FLAG;
274 else if (strcasecmp(v, "FALSE")) {
275 agwarningf("Illegal value %s for FIXEDSIZE - ignored\n", v);
276 rv = 1;
277 }
278 return rv;
279}
280
281static int valignfn(htmldata_t *p, const char *v) {
282 int rv = 0;
283 if (!strcasecmp(v, "BOTTOM"))
284 p->flags |= VALIGN_BOTTOM;
285 else if (!strcasecmp(v, "TOP"))
286 p->flags |= VALIGN_TOP;
287 else if (strcasecmp(v, "MIDDLE")) {
288 agwarningf("Illegal value %s for VALIGN - ignored\n", v);
289 rv = 1;
290 }
291 return rv;
292}
293
294static int halignfn(htmldata_t *p, const char *v) {
295 int rv = 0;
296 if (!strcasecmp(v, "LEFT"))
297 p->flags |= HALIGN_LEFT;
298 else if (!strcasecmp(v, "RIGHT"))
299 p->flags |= HALIGN_RIGHT;
300 else if (strcasecmp(v, "CENTER")) {
301 agwarningf("Illegal value %s for ALIGN - ignored\n", v);
302 rv = 1;
303 }
304 return rv;
305}
306
307static int cell_halignfn(htmldata_t *p, const char *v) {
308 int rv = 0;
309 if (!strcasecmp(v, "LEFT"))
310 p->flags |= HALIGN_LEFT;
311 else if (!strcasecmp(v, "RIGHT"))
312 p->flags |= HALIGN_RIGHT;
313 else if (!strcasecmp(v, "TEXT"))
314 p->flags |= HALIGN_TEXT;
315 else if (strcasecmp(v, "CENTER"))
316 rv = 1;
317 if (rv)
318 agwarningf("Illegal value %s for ALIGN in TD - ignored\n", v);
319 return rv;
320}
321
322static int balignfn(htmldata_t *p, const char *v) {
323 int rv = 0;
324 if (!strcasecmp(v, "LEFT"))
325 p->flags |= BALIGN_LEFT;
326 else if (!strcasecmp(v, "RIGHT"))
327 p->flags |= BALIGN_RIGHT;
328 else if (strcasecmp(v, "CENTER"))
329 rv = 1;
330 if (rv)
331 agwarningf("Illegal value %s for BALIGN in TD - ignored\n", v);
332 return rv;
333}
334
335static int heightfn(htmldata_t *p, const char *v) {
336 long u;
337
338 if (doInt(v, "HEIGHT", 0, USHRT_MAX, &u))
339 return 1;
340 p->height = (unsigned short)u;
341 return 0;
342}
343
344static int widthfn(htmldata_t *p, const char *v) {
345 long u;
346
347 if (doInt(v, "WIDTH", 0, USHRT_MAX, &u))
348 return 1;
349 p->width = (unsigned short)u;
350 return 0;
351}
352
353static int rowspanfn(htmldata_t *data, const char *v) {
354 long u;
355 htmlcell_t *const p =
356 (htmlcell_t *)((uintptr_t)data - offsetof(htmlcell_t, data));
357
358 if (doInt(v, "ROWSPAN", 0, UINT16_MAX, &u))
359 return 1;
360 if (u == 0) {
361 agwarningf("ROWSPAN value cannot be 0 - ignored\n");
362 return 1;
363 }
364 p->rowspan = (uint16_t)u;
365 return 0;
366}
367
368static int colspanfn(htmldata_t *data, const char *v) {
369 long u;
370 htmlcell_t *const p =
371 (htmlcell_t *)((uintptr_t)data - offsetof(htmlcell_t, data));
372
373 if (doInt(v, "COLSPAN", 0, UINT16_MAX, &u))
374 return 1;
375 if (u == 0) {
376 agwarningf("COLSPAN value cannot be 0 - ignored\n");
377 return 1;
378 }
379 p->colspan = (uint16_t)u;
380 return 0;
381}
382
383static int fontcolorfn(textfont_t *p, const char *v) {
384 // no strdup because HTML font usage is contained
385 p->color = (char *)v;
386 return 0;
387}
388
389static int facefn(textfont_t *p, const char *v) {
390 // no strdup because HTML font usage is contained
391 p->name = (char *)v;
392 return 0;
393}
394
395static int ptsizefn(textfont_t *p, const char *v) {
396 long u;
397
398 if (doInt(v, "POINT-SIZE", 0, UCHAR_MAX, &u))
399 return 1;
400 p->size = (double)u;
401 return 0;
402}
403
404static int srcfn(htmlimg_t *p, const char *v) {
405 p->src = strdup(v);
406 return 0;
407}
408
409static int scalefn(htmlimg_t *p, const char *v) {
410 p->scale = strdup(v);
411 return 0;
412}
413
414static int alignfn(int *p, const char *v) {
415 int rv = 0;
416 if (!strcasecmp(v, "RIGHT"))
417 *p = 'r';
418 else if (!strcasecmp(v, "LEFT"))
419 *p = 'l';
420 else if (!strcasecmp(v, "CENTER"))
421 *p = 'n';
422 else {
423 agwarningf("Illegal value %s for ALIGN - ignored\n", v);
424 rv = 1;
425 }
426 return rv;
427}
428
429typedef struct {
430 char *name;
431 int (*action)(htmldata_t *,
432 const char *);
433} html_item_t;
434
435/* Tables used in binary search; MUST be alphabetized */
436static const html_item_t tbl_items[] = {
437 {"align", halignfn},
438 {"bgcolor", bgcolorfn},
439 {"border", borderfn},
440 {"cellborder", cellborderfn},
441 {"cellpadding", cellpaddingfn},
442 {"cellspacing", cellspacingfn},
443 {"color", pencolorfn},
444 {"columns", columnsfn},
445 {"fixedsize", fixedsizefn},
446 {"gradientangle", gradientanglefn},
447 {"height", heightfn},
448 {"href", hreffn},
449 {"id", idfn},
450 {"port", portfn},
451 {"rows", rowsfn},
452 {"sides", sidesfn},
453 {"style", stylefn},
454 {"target", targetfn},
455 {"title", titlefn},
456 {"tooltip", titlefn},
457 {"valign", valignfn},
458 {"width", widthfn},
459};
460
461static const html_item_t cell_items[] = {
462 {"align", cell_halignfn},
463 {"balign", balignfn},
464 {"bgcolor", bgcolorfn},
465 {"border", borderfn},
466 {"cellpadding", cellpaddingfn},
467 {"cellspacing", cellspacingfn},
468 {"color", pencolorfn},
469 {"colspan", colspanfn},
470 {"fixedsize", fixedsizefn},
471 {"gradientangle", gradientanglefn},
472 {"height", heightfn},
473 {"href", hreffn},
474 {"id", idfn},
475 {"port", portfn},
476 {"rowspan", rowspanfn},
477 {"sides", sidesfn},
478 {"style", stylefn},
479 {"target", targetfn},
480 {"title", titlefn},
481 {"tooltip", titlefn},
482 {"valign", valignfn},
483 {"width", widthfn},
484};
485
486typedef struct {
487 char *name;
488 int (*action)(textfont_t *,
489 const char *);
490} font_item_t;
491
492static const font_item_t font_items[] = {
493 {"color", fontcolorfn},
494 {"face", facefn},
495 {"point-size", ptsizefn},
496};
497
498typedef struct {
499 char *name;
500 int (*action)(htmlimg_t *,
501 const char *);
502} img_item_t;
503
504static const img_item_t img_items[] = {
505 {"scale", scalefn},
506 {"src", srcfn},
507};
508
509typedef struct {
510 char *name;
511 int (*action)(int *, const char *);
512} br_item_t;
513
514static const br_item_t br_items[] = {
515 {"align", alignfn},
516};
517
522#define CALL_ACTION(list, elem, tp, val) \
523 (_Generic((list), \
524 const html_item_t *: (const html_item_t *)(elem), \
525 const font_item_t *: (const font_item_t *)(elem), \
526 const img_item_t *: (const img_item_t *)(elem), \
527 const br_item_t *: (const br_item_t *)(elem)) \
528 ->action((tp), (val)))
529
530/* doAttrs:
531 * General function for processing list of name/value attributes.
532 * Do binary search on items table. If match found, invoke action
533 * passing it tp and attribute value.
534 * Table size is given by nel
535 * Name/value pairs are in array atts, which is null terminated.
536 * s is the name of the HTML element being processed.
537 */
538#define doAttrs(ctx, tp, items, nel, atts, s) \
539 do { \
540 const char *name; \
541 \
542 while ((name = *(atts)++) != NULL) { \
543 const char *val = *(atts)++; \
544 void *const ip = \
545 bsearch(name, (items), (nel), sizeof((items)[0]), icmp); \
546 if (ip) \
547 (ctx)->warn |= CALL_ACTION((items), ip, (tp), val); \
548 else { \
549 agwarningf("Illegal attribute %s in %s - ignored\n", name, (s)); \
550 (ctx)->warn = 1; \
551 } \
552 } \
553 } while (0)
554
555static void mkBR(htmllexstate_t *ctx, const char **atts) {
556 ctx->htmllval->i = UNSET_ALIGN;
557 doAttrs(ctx, &ctx->htmllval->i, br_items,
558 sizeof(br_items) / sizeof(br_items[0]), atts, "<BR>");
559}
560
561static htmlimg_t *mkImg(htmllexstate_t *ctx, const char **atts) {
562 htmlimg_t *img = gv_alloc(sizeof(htmlimg_t));
563
564 doAttrs(ctx, img, img_items, sizeof(img_items) / sizeof(img_items[0]), atts,
565 "<IMG>");
566
567 return img;
568}
569
570static textfont_t *mkFont(htmllexstate_t *ctx, const char **atts,
571 unsigned char flags) {
572 textfont_t tf = {NULL, NULL, NULL, 0.0, 0, 0};
573
574 tf.size = -1.0; /* unassigned */
575 enum { FLAGS_MAX = (1 << GV_TEXTFONT_FLAGS_WIDTH) - 1 };
576 assert(flags <= FLAGS_MAX);
577 tf.flags = (unsigned char)(flags & FLAGS_MAX);
578 if (atts)
579 doAttrs(ctx, &tf, font_items, sizeof(font_items) / sizeof(font_items[0]),
580 atts, "<FONT>");
581
582 return dtinsert(ctx->gvc->textfont_dt, &tf);
583}
584
585static htmlcell_t *mkCell(htmllexstate_t *ctx, const char **atts) {
587
588 cell->colspan = 1;
589 cell->rowspan = 1;
590 doAttrs(ctx, &cell->data, cell_items,
591 sizeof(cell_items) / sizeof(cell_items[0]), atts, "<TD>");
592
593 return cell;
594}
595
596static htmltbl_t *mkTbl(htmllexstate_t *ctx, const char **atts) {
597 htmltbl_t *tbl = gv_alloc(sizeof(htmltbl_t));
598
599 tbl->row_count = SIZE_MAX; // flag that table is a raw, parsed table
600 tbl->rows = (rows_t){.dtor = free_ritem};
601 tbl->cellborder = -1; // unset cell border attribute
602 doAttrs(ctx, &tbl->data, tbl_items, sizeof(tbl_items) / sizeof(tbl_items[0]),
603 atts, "<TABLE>");
604
605 return tbl;
606}
607
608static void startElement(void *user, const char *name, const char **atts) {
609 htmllexstate_t *ctx = user;
610
611 if (strcasecmp(name, "TABLE") == 0) {
612 ctx->htmllval->tbl = mkTbl(ctx, atts);
613 ctx->inCell = 0;
614 ctx->tok = T_table;
615 } else if (strcasecmp(name, "TR") == 0 || strcasecmp(name, "TH") == 0) {
616 ctx->inCell = 0;
617 ctx->tok = T_row;
618 } else if (strcasecmp(name, "TD") == 0) {
619 ctx->inCell = 1;
620 ctx->htmllval->cell = mkCell(ctx, atts);
621 ctx->tok = T_cell;
622 } else if (strcasecmp(name, "FONT") == 0) {
623 ctx->htmllval->font = mkFont(ctx, atts, 0);
624 ctx->tok = T_font;
625 } else if (strcasecmp(name, "B") == 0) {
626 ctx->htmllval->font = mkFont(ctx, 0, HTML_BF);
627 ctx->tok = T_bold;
628 } else if (strcasecmp(name, "S") == 0) {
629 ctx->htmllval->font = mkFont(ctx, 0, HTML_S);
630 ctx->tok = T_s;
631 } else if (strcasecmp(name, "U") == 0) {
632 ctx->htmllval->font = mkFont(ctx, 0, HTML_UL);
633 ctx->tok = T_underline;
634 } else if (strcasecmp(name, "O") == 0) {
635 ctx->htmllval->font = mkFont(ctx, 0, HTML_OL);
636 ctx->tok = T_overline;
637 } else if (strcasecmp(name, "I") == 0) {
638 ctx->htmllval->font = mkFont(ctx, 0, HTML_IF);
639 ctx->tok = T_italic;
640 } else if (strcasecmp(name, "SUP") == 0) {
641 ctx->htmllval->font = mkFont(ctx, 0, HTML_SUP);
642 ctx->tok = T_sup;
643 } else if (strcasecmp(name, "SUB") == 0) {
644 ctx->htmllval->font = mkFont(ctx, 0, HTML_SUB);
645 ctx->tok = T_sub;
646 } else if (strcasecmp(name, "BR") == 0) {
647 mkBR(ctx, atts);
648 ctx->tok = T_br;
649 } else if (strcasecmp(name, "HR") == 0) {
650 ctx->tok = T_hr;
651 } else if (strcasecmp(name, "VR") == 0) {
652 ctx->tok = T_vr;
653 } else if (strcasecmp(name, "IMG") == 0) {
654 ctx->htmllval->img = mkImg(ctx, atts);
655 ctx->tok = T_img;
656 } else if (strcasecmp(name, "HTML") == 0) {
657 ctx->tok = T_html;
658 } else {
659 lexerror(ctx, name);
660 }
661}
662
663static void endElement(void *user, const char *name) {
664 htmllexstate_t *ctx = user;
665
666 if (strcasecmp(name, "TABLE") == 0) {
667 ctx->tok = T_end_table;
668 ctx->inCell = 1;
669 } else if (strcasecmp(name, "TR") == 0 || strcasecmp(name, "TH") == 0) {
670 ctx->tok = T_end_row;
671 } else if (strcasecmp(name, "TD") == 0) {
672 ctx->tok = T_end_cell;
673 ctx->inCell = 0;
674 } else if (strcasecmp(name, "HTML") == 0) {
675 ctx->tok = T_end_html;
676 } else if (strcasecmp(name, "FONT") == 0) {
677 ctx->tok = T_end_font;
678 } else if (strcasecmp(name, "B") == 0) {
679 ctx->tok = T_n_bold;
680 } else if (strcasecmp(name, "U") == 0) {
681 ctx->tok = T_n_underline;
682 } else if (strcasecmp(name, "O") == 0) {
683 ctx->tok = T_n_overline;
684 } else if (strcasecmp(name, "I") == 0) {
685 ctx->tok = T_n_italic;
686 } else if (strcasecmp(name, "SUP") == 0) {
687 ctx->tok = T_n_sup;
688 } else if (strcasecmp(name, "SUB") == 0) {
689 ctx->tok = T_n_sub;
690 } else if (strcasecmp(name, "S") == 0) {
691 ctx->tok = T_n_s;
692 } else if (strcasecmp(name, "BR") == 0) {
693 if (ctx->tok == T_br)
694 ctx->tok = T_BR;
695 else
696 ctx->tok = T_end_br;
697 } else if (strcasecmp(name, "HR") == 0) {
698 if (ctx->tok == T_hr)
699 ctx->tok = T_HR;
700 else
701 ctx->tok = T_end_hr;
702 } else if (strcasecmp(name, "VR") == 0) {
703 if (ctx->tok == T_vr)
704 ctx->tok = T_VR;
705 else
706 ctx->tok = T_end_vr;
707 } else if (strcasecmp(name, "IMG") == 0) {
708 if (ctx->tok == T_img)
709 ctx->tok = T_IMG;
710 else
711 ctx->tok = T_end_img;
712 } else {
713 lexerror(ctx, name);
714 }
715}
716
717/* characterData:
718 * Generate T_string token. Do this only when immediately in
719 * <TD>..</TD> or <HTML>..</HTML>, i.e., when inCell is true.
720 * Strip out formatting characters but keep spaces.
721 * Distinguish between all whitespace vs. strings with non-whitespace
722 * characters.
723 */
724static void characterData(void *user, const char *s, int length) {
725 htmllexstate_t *ctx = user;
726
727 int i, cnt = 0;
728 unsigned char c;
729
730 if (ctx->inCell) {
731 for (i = length; i; i--) {
732 c = *s++;
733 if (c >= ' ') {
734 cnt++;
735 agxbputc(ctx->xb, (char)c);
736 }
737 }
738 if (cnt)
739 ctx->tok = T_string;
740 }
741}
742#endif
743
744int initHTMLlexer(htmlscan_t *scanner, char *src, agxbuf *xb, htmlenv_t *env) {
745#ifdef HAVE_EXPAT
746 htmllexstate_t *ctx = &scanner->lexer;
747
748 ctx->xb = xb;
749 ctx->lb = (agxbuf){0};
750 ctx->ptr = src;
751 ctx->mode = 0;
752 ctx->warn = 0;
753 ctx->error = 0;
754 ctx->currtok = (strview_t){0};
755 ctx->prevtok = (strview_t){0};
756 ctx->inCell = 1;
757 ctx->parser = XML_ParserCreate(charsetToStr(GD_charset(env->g)));
758 ctx->gvc = GD_gvc(env->g);
759 XML_SetUserData(ctx->parser, ctx);
760 XML_SetElementHandler(ctx->parser, startElement, endElement);
761 XML_SetCharacterDataHandler(ctx->parser, characterData);
762 return 0;
763#else
764 (void)scanner;
765 (void)src;
766 (void)xb;
767 (void)env;
768
769 static atomic_flag first;
770 if (!atomic_flag_test_and_set(&first)) {
771 agwarningf("Not built with libexpat. Table formatting is not available.\n");
772 }
773 return 1;
774#endif
775}
776
778#ifdef HAVE_EXPAT
779 htmllexstate_t *ctx = &scanner->lexer;
780 int rv = ctx->error ? 3 : ctx->warn;
781 XML_ParserFree(ctx->parser);
782 agxbfree(&ctx->lb);
783 return rv;
784#else
785 (void)scanner;
786
787 return 1;
788#endif
789}
790
792static UNUSED void agxbput_move(agxbuf *dst, const char *src) {
793 // we cannot call `agxbput` itself because it calls `memcpy`, thereby
794 // implicitly assuming that source and destination do not overlap
795 char *src_copy = gv_strdup(src);
796 agxbput(dst, src_copy);
797 free(src_copy);
798}
799
800#ifdef HAVE_EXPAT
801/* eatComment:
802 * Given first character after open comment, eat characters
803 * up to comment close, returning pointer to closing > if it exists,
804 * or null character otherwise.
805 * We rely on HTML strings having matched nested <>.
806 */
807static char *eatComment(htmllexstate_t *ctx, char *p) {
808 int depth = 1;
809 char *s = p;
810 char c;
811
812 while (depth && (c = *s++)) {
813 if (c == '<')
814 depth++;
815 else if (c == '>')
816 depth--;
817 }
818 s--; /* move back to '\0' or '>' */
819 if (*s) {
820 char *t = s - 2;
821 if (t < p || !startswith(t, "--")) {
822 agwarningf("Unclosed comment\n");
823 ctx->warn = 1;
824 }
825 }
826 return s;
827}
828
829/* findNext:
830 * Return next XML unit. This is either <..>, an HTML
831 * comment <!-- ... -->, or characters up to next <.
832 */
833static char *findNext(htmllexstate_t *ctx, char *s, agxbuf *xb) {
834 char *t = s + 1;
835 char c;
836
837 if (*s == '<') {
838 if (startswith(t, "!--"))
839 t = eatComment(ctx, t + 3);
840 else
841 while (*t && *t != '>')
842 t++;
843 if (*t != '>') {
844 agwarningf("Label closed before end of HTML element\n");
845 ctx->warn = 1;
846 } else
847 t++;
848 } else {
849 t = s;
850 while ((c = *t) && c != '<') {
851 if (c == '&' && *(t + 1) != '#') {
852 t = scanEntity(t + 1, xb);
853 } else {
854 agxbputc(xb, c);
855 t++;
856 }
857 }
858 }
859 return t;
860}
861
878static void protect_rsqb(agxbuf *xb) {
879
880 // if the buffer is empty, we have nothing to do
881 if (agxblen(xb) == 0) {
882 return;
883 }
884
885 // check the last character and if it is not ], we have nothing to do
886 char *data = agxbuse(xb);
887 size_t size = strlen(data);
888 assert(size > 0);
889 if (data[size - 1] != ']') {
890 agxbput_move(xb, data);
891 return;
892 }
893
894 // truncate ] and write back the remaining prefix
895 data[size - 1] = '\0';
896 agxbput_move(xb, data);
897
898 // write an XML-escaped version of ] as a replacement
899 agxbput(xb, "&#93;");
900}
901#endif
902
904 return htmllineno_ctx(&scanner->lexer);
905}
906
907static unsigned long htmllineno_ctx(htmllexstate_t *ctx) {
908#ifdef HAVE_EXPAT
909 return XML_GetCurrentLineNumber(ctx->parser);
910#else
911 (void)ctx;
912
913 return 0;
914#endif
915}
916
917#ifdef DEBUG
918static void printTok(htmllexstate_t *ctx, int tok) {
919 char *s;
920
921 switch (tok) {
922 case T_end_br:
923 s = "T_end_br";
924 break;
925 case T_end_img:
926 s = "T_end_img";
927 break;
928 case T_row:
929 s = "T_row";
930 break;
931 case T_end_row:
932 s = "T_end_row";
933 break;
934 case T_html:
935 s = "T_html";
936 break;
937 case T_end_html:
938 s = "T_end_html";
939 break;
940 case T_end_table:
941 s = "T_end_table";
942 break;
943 case T_end_cell:
944 s = "T_end_cell";
945 break;
946 case T_end_font:
947 s = "T_end_font";
948 break;
949 case T_string:
950 s = "T_string";
951 break;
952 case T_error:
953 s = "T_error";
954 break;
955 case T_n_italic:
956 s = "T_n_italic";
957 break;
958 case T_n_bold:
959 s = "T_n_bold";
960 break;
961 case T_n_underline:
962 s = "T_n_underline";
963 break;
964 case T_n_overline:
965 s = "T_n_overline";
966 break;
967 case T_n_sup:
968 s = "T_n_sup";
969 break;
970 case T_n_sub:
971 s = "T_n_sub";
972 break;
973 case T_n_s:
974 s = "T_n_s";
975 break;
976 case T_HR:
977 s = "T_HR";
978 break;
979 case T_hr:
980 s = "T_hr";
981 break;
982 case T_end_hr:
983 s = "T_end_hr";
984 break;
985 case T_VR:
986 s = "T_VR";
987 break;
988 case T_vr:
989 s = "T_vr";
990 break;
991 case T_end_vr:
992 s = "T_end_vr";
993 break;
994 case T_BR:
995 s = "T_BR";
996 break;
997 case T_br:
998 s = "T_br";
999 break;
1000 case T_IMG:
1001 s = "T_IMG";
1002 break;
1003 case T_img:
1004 s = "T_img";
1005 break;
1006 case T_table:
1007 s = "T_table";
1008 break;
1009 case T_cell:
1010 s = "T_cell";
1011 break;
1012 case T_font:
1013 s = "T_font";
1014 break;
1015 case T_italic:
1016 s = "T_italic";
1017 break;
1018 case T_bold:
1019 s = "T_bold";
1020 break;
1021 case T_underline:
1022 s = "T_underline";
1023 break;
1024 case T_overline:
1025 s = "T_overline";
1026 break;
1027 case T_sup:
1028 s = "T_sup";
1029 break;
1030 case T_sub:
1031 s = "T_sub";
1032 break;
1033 case T_s:
1034 s = "T_s";
1035 break;
1036 default:
1037 s = "<unknown>";
1038 }
1039 if (tok == T_string) {
1040 const char *token_text = agxbuse(ctx->xb);
1041 fprintf(stderr, "%s \"%s\"\n", s, token_text);
1042 agxbput_move(ctx->xb, token_text);
1043 } else
1044 fprintf(stderr, "%s\n", s);
1045}
1046
1047#endif
1048
1049int htmllex(union HTMLSTYPE *htmllval, htmlscan_t *scanner) {
1050#ifdef HAVE_EXPAT
1051 static char *begin_html = "<HTML>";
1052 static char *end_html = "</HTML>";
1053
1054 char *s;
1055 char *endp = 0;
1056 size_t len, llen;
1057 int rv;
1058 htmllexstate_t *ctx = &scanner->lexer;
1059
1060 ctx->htmllval = htmllval;
1061 ctx->tok = 0;
1062 do {
1063 if (ctx->mode == 2)
1064 return EOF;
1065 if (ctx->mode == 0) {
1066 ctx->mode = 1;
1067 s = begin_html;
1068 len = strlen(s);
1069 endp = 0;
1070 } else {
1071 s = ctx->ptr;
1072 if (*s == '\0') {
1073 ctx->mode = 2;
1074 s = end_html;
1075 len = strlen(s);
1076 } else {
1077 endp = findNext(ctx, s, &ctx->lb);
1078 len = (size_t)(endp - s);
1079 }
1080 }
1081
1082 protect_rsqb(&ctx->lb);
1083
1084 ctx->prevtok = ctx->currtok;
1085 ctx->currtok = (strview_t){.data = s, .size = len};
1086 if ((llen = agxblen(&ctx->lb))) {
1087 assert(llen <= INT_MAX && "XML token too long for expat API");
1088 rv = XML_Parse(ctx->parser, agxbuse(&ctx->lb), (int)llen, 0);
1089 } else {
1090 assert(len <= INT_MAX && "XML token too long for expat API");
1091 rv = XML_Parse(ctx->parser, s, (int)len, len ? 0 : 1);
1092 }
1093 if (rv == XML_STATUS_ERROR) {
1094 if (!ctx->error) {
1095 agerrorf("%s in line %lu \n",
1096 XML_ErrorString(XML_GetErrorCode(ctx->parser)),
1098 error_context(ctx);
1099 ctx->error = 1;
1100 ctx->tok = T_error;
1101 }
1102 }
1103 if (endp)
1104 ctx->ptr = endp;
1105 } while (ctx->tok == 0);
1106#ifdef DEBUG
1107 printTok(ctx, ctx->tok);
1108#endif
1109 return ctx->tok;
1110#else
1111 (void)htmllval;
1112 (void)scanner;
1113
1114 return EOF;
1115#endif
1116}
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
static size_t agxblen(const agxbuf *xb)
return number of characters currently stored
Definition agxbuf.h:108
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:295
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static void * gv_alloc(size_t size)
Definition alloc.h:47
container data types API
#define dtinsert(d, o)
Definition cdt.h:186
char * scanEntity(char *t, agxbuf *xb)
Definition utils.c:1082
static Extype_t length(Exid_t *rhs, Exdisc_t *disc)
Definition compile.c:1615
static int flags
Definition gc.c:63
static double len(glCompPoint p)
Definition glutils.c:138
void free(void *)
#define SIZE_MAX
Definition gmlscan.c:347
#define UINT16_MAX
Definition gmlscan.c:340
#define INT8_MAX
Definition gmlscan.c:328
node NULL
Definition grammar.y:181
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:204
void agwarningf(const char *fmt,...)
Definition agerror.c:175
void agerrorf(const char *fmt,...)
Definition agerror.c:167
int agerr(agerrlevel_t level, const char *fmt,...)
Definition agerror.c:157
@ AGPREV
Definition cgraph.h:951
#define GD_charset(g)
Definition types.h:367
#define GD_gvc(g)
Definition types.h:355
replacements for ctype.h functions
static char gv_tolower(int c)
Definition gv_ctype.h:81
agxbput(xb, staging)
int htmllex(union HTMLSTYPE *htmllval, htmlscan_t *scanner)
Definition htmllex.c:1049
static UNUSED void agxbput_move(agxbuf *dst, const char *src)
agxbput, but assume that source and destination may overlap
Definition htmllex.c:792
unsigned long htmllineno(htmlscan_t *scanner)
Definition htmllex.c:903
#define XML_STATUS_ERROR
Definition htmllex.c:39
static void error_context(htmllexstate_t *ctx)
Definition htmllex.c:47
int clearHTMLlexer(htmlscan_t *scanner)
Definition htmllex.c:777
int initHTMLlexer(htmlscan_t *scanner, char *src, agxbuf *xb, htmlenv_t *env)
Definition htmllex.c:744
void htmlerror(htmlscan_t *scanner, const char *msg)
Definition htmllex.c:55
static unsigned long htmllineno_ctx(htmllexstate_t *ctx)
Definition htmllex.c:907
#define T_n_sup
Definition htmlparse.h:138
#define T_end_row
Definition htmlparse.h:126
#define T_end_table
Definition htmlparse.h:129
#define T_br
Definition htmlparse.h:148
#define T_vr
Definition htmlparse.h:145
#define T_error
Definition htmlparse.h:133
#define T_n_s
Definition htmlparse.h:140
#define T_end_cell
Definition htmlparse.h:130
#define T_n_sub
Definition htmlparse.h:139
#define T_n_bold
Definition htmlparse.h:135
#define T_html
Definition htmlparse.h:127
#define T_BR
Definition htmlparse.h:147
#define T_underline
Definition htmlparse.h:156
#define T_sup
Definition htmlparse.h:158
#define T_row
Definition htmlparse.h:125
#define T_table
Definition htmlparse.h:151
#define T_end_vr
Definition htmlparse.h:146
#define T_end_html
Definition htmlparse.h:128
#define T_IMG
Definition htmlparse.h:149
#define T_VR
Definition htmlparse.h:144
#define T_bold
Definition htmlparse.h:155
#define T_end_img
Definition htmlparse.h:124
#define T_sub
Definition htmlparse.h:159
#define T_s
Definition htmlparse.h:160
#define T_n_italic
Definition htmlparse.h:134
#define T_end_font
Definition htmlparse.h:131
#define T_overline
Definition htmlparse.h:157
#define T_hr
Definition htmlparse.h:142
#define T_font
Definition htmlparse.h:153
#define T_italic
Definition htmlparse.h:154
#define T_end_br
Definition htmlparse.h:123
#define T_n_underline
Definition htmlparse.h:136
#define T_cell
Definition htmlparse.h:152
#define T_end_hr
Definition htmlparse.h:143
#define T_string
Definition htmlparse.h:132
#define T_img
Definition htmlparse.h:150
#define T_HR
Definition htmlparse.h:141
#define T_n_overline
Definition htmlparse.h:137
cleanup & scanner
Definition htmlparse.y:289
#define PAD_SET
Definition htmltable.h:35
#define BORDER_RIGHT
Definition htmltable.h:42
#define BORDER_TOP
Definition htmltable.h:41
#define HALIGN_TEXT
Definition htmltable.h:30
#define UNSET_ALIGN
Definition htmltable.h:46
#define HALIGN_LEFT
Definition htmltable.h:28
#define VALIGN_BOTTOM
Definition htmltable.h:32
#define BALIGN_RIGHT
Definition htmltable.h:37
#define BALIGN_LEFT
Definition htmltable.h:38
#define BORDER_BOTTOM
Definition htmltable.h:43
static void free_ritem(row_t *p)
Free row. This closes and frees row’s list, then the item itself is freed.
Definition htmltable.h:119
#define SPACE_SET
Definition htmltable.h:36
#define BORDER_SET
Definition htmltable.h:34
#define BORDER_LEFT
Definition htmltable.h:40
#define BORDER_MASK
Definition htmltable.h:44
#define HALIGN_RIGHT
Definition htmltable.h:27
#define VALIGN_TOP
Definition htmltable.h:31
#define FIXED_FLAG
Definition htmltable.h:26
char * charsetToStr(int c)
Given an internal charset value, return a canonical string representation.
Definition input.c:807
static bool startswith(const char *s, const char *prefix)
does the string s begin with the string prefix?
Definition startswith.h:11
platform abstraction for case-insensitive string functions
Dt_t * textfont_dt
Definition gvcint.h:108
result of partitioning available space, part of maze
Definition grid.h:33
uint16_t rowspan
Definition htmltable.h:160
uint16_t colspan
Definition htmltable.h:159
char * bgcolor
Definition htmltable.h:89
unsigned char border
Definition htmltable.h:93
char * target
Definition htmltable.h:86
char * id
Definition htmltable.h:88
signed char space
Definition htmltable.h:92
unsigned short width
Definition htmltable.h:97
unsigned short height
Definition htmltable.h:98
int gradientangle
Definition htmltable.h:91
char * port
Definition htmltable.h:85
unsigned short flags
Definition htmltable.h:96
char * href
Definition htmltable.h:84
unsigned char pad
Definition htmltable.h:94
char * pencolor
Definition htmltable.h:90
htmlstyle_t style
Definition htmltable.h:99
char * title
Definition htmltable.h:87
graph_t * g
Definition htmltable.h:173
char * scale
Definition htmltable.h:72
char * src
Definition htmltable.h:71
HTMLSTYPE * htmllval
Definition htmlparse.h:229
agxbuf * xb
Definition htmlparse.h:220
strview_t prevtok
Definition htmlparse.h:227
strview_t currtok
Definition htmlparse.h:226
bool dashed
Definition htmltable.h:80
bool dotted
Definition htmltable.h:79
bool rounded
Definition htmltable.h:77
bool radial
Definition htmltable.h:76
bool invisible
Definition htmltable.h:78
bool vrule
vertical rule
Definition htmltable.h:145
size_t row_count
number of rows
Definition htmltable.h:141
bool hrule
horizontal rule
Definition htmltable.h:144
htmldata_t data
Definition htmltable.h:127
rows_t rows
cells
Definition htmltable.h:135
int8_t cellborder
Definition htmltable.h:138
Definition utils.c:752
a non-owning string reference
Definition strview.h:20
const char * data
start of the pointed to string
Definition strview.h:21
size_t size
extent of the string in bytes
Definition strview.h:22
char * color
Definition textspan.h:57
char * name
Definition textspan.h:56
unsigned int flags
Definition textspan.h:61
double size
Definition textspan.h:59
state for an in-progress string tokenization
Definition tokenize.h:36
Non-owning string references.
static bool strview_case_str_eq(strview_t a, const char *b)
compare a string reference to a string for case insensitive equality
Definition strview.h:62
#define HTML_OL
Definition textspan.h:37
#define HTML_IF
Definition textspan.h:32
#define HTML_UL
Definition textspan.h:33
#define HTML_BF
Definition textspan.h:31
#define HTML_SUP
Definition textspan.h:34
#define HTML_S
Definition textspan.h:36
#define GV_TEXTFONT_FLAGS_WIDTH
Definition textspan.h:26
#define HTML_SUB
Definition textspan.h:35
String tokenization.
static strview_t tok_get(const tok_t *t)
get the current token
Definition tokenize.h:76
static tok_t tok(const char *input, const char *separators)
begin tokenization of a new string
Definition tokenize.h:43
static bool tok_end(const tok_t *t)
is this tokenizer exhausted?
Definition tokenize.h:68
static void tok_next(tok_t *t)
advance to the next token in the string being scanned
Definition tokenize.h:85
htmltbl_t * tbl
Definition htmlparse.h:171
htmlcell_t * cell
Definition htmlparse.h:170
textfont_t * font
Definition htmlparse.h:172
htmlimg_t * img
Definition htmlparse.h:173
Definition grammar.c:90
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25