htmlparse.y 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /// @file
  2. /// @ingroup common_utils
  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 v1.0
  7. * which accompanies this distribution, and is available at
  8. * https://www.eclipse.org/legal/epl-v10.html
  9. *
  10. * Contributors: Details at https://graphviz.org
  11. *************************************************************************/
  12. %require "3.0"
  13. /* By default, Bison emits a parser using symbols prefixed with "yy". Graphviz
  14. * contains multiple Bison-generated parsers, so we alter this prefix to avoid
  15. * symbol clashes.
  16. */
  17. %define api.prefix {html}
  18. /* Generate a reentrant parser with no global state */
  19. %define api.pure full
  20. %param { htmlscan_t *scanner }
  21. %code requires {
  22. #include <cgraph/list.h>
  23. #include <cgraph/strview.h>
  24. #include <common/htmllex.h>
  25. #include <common/htmltable.h>
  26. #include <common/textspan.h>
  27. #include <gvc/gvcext.h>
  28. #include <util/agxbuf.h>
  29. }
  30. %code provides {
  31. DEFINE_LIST(sfont, textfont_t *)
  32. static inline void free_ti(textspan_t item) {
  33. free(item.str);
  34. }
  35. DEFINE_LIST_WITH_DTOR(textspans, textspan_t, free_ti)
  36. static inline void free_hi(htextspan_t item) {
  37. for (size_t i = 0; i < item.nitems; i++) {
  38. free(item.items[i].str);
  39. }
  40. free(item.items);
  41. }
  42. DEFINE_LIST_WITH_DTOR(htextspans, htextspan_t, free_hi)
  43. struct htmlparserstate_s {
  44. htmllabel_t* lbl; /* Generated label */
  45. htmltbl_t* tblstack; /* Stack of tables maintained during parsing */
  46. textspans_t fitemList;
  47. htextspans_t fspanList;
  48. agxbuf* str; /* Buffer for text */
  49. sfont_t fontstack;
  50. GVC_t* gvc;
  51. };
  52. typedef struct {
  53. #ifdef HAVE_EXPAT
  54. struct XML_ParserStruct *parser;
  55. #endif
  56. char* ptr; // input source
  57. int tok; // token type
  58. agxbuf* xb; // buffer to gather T_string data
  59. agxbuf lb; // buffer for translating lexical data
  60. int warn; // set if warning given
  61. int error; // set if error given
  62. char inCell; // set if in TD to allow T_string
  63. char mode; // for handling artificial <HTML>..</HTML>
  64. strview_t currtok; // for error reporting
  65. strview_t prevtok; // for error reporting
  66. GVC_t *gvc; // current GraphViz context
  67. HTMLSTYPE *htmllval; // generated by htmlparse.y
  68. } htmllexstate_t;
  69. struct htmlscan_s {
  70. htmllexstate_t lexer;
  71. htmlparserstate_t parser;
  72. };
  73. }
  74. %{
  75. #include <common/render.h>
  76. #include <common/htmltable.h>
  77. #include <common/htmllex.h>
  78. #include <stdbool.h>
  79. #include <util/alloc.h>
  80. /// Clean up cell if error in parsing.
  81. static void cleanCell(htmlcell_t *cp);
  82. /// Clean up table if error in parsing.
  83. static void cleanTbl(htmltbl_t *tp) {
  84. rows_t *rows = &tp->u.p.rows;
  85. for (size_t r = 0; r < rows_size(rows); ++r) {
  86. row_t *rp = rows_get(rows, r);
  87. for (size_t c = 0; c < cells_size(&rp->rp); ++c) {
  88. cleanCell(cells_get(&rp->rp, c));
  89. }
  90. }
  91. rows_free(rows);
  92. free_html_data(&tp->data);
  93. free(tp);
  94. }
  95. /// Clean up cell if error in parsing.
  96. static void
  97. cleanCell (htmlcell_t* cp)
  98. {
  99. if (cp->child.kind == HTML_TBL) cleanTbl (cp->child.u.tbl);
  100. else if (cp->child.kind == HTML_TEXT) free_html_text (cp->child.u.txt);
  101. free_html_data (&cp->data);
  102. free (cp);
  103. }
  104. /// Append a new text span to the list.
  105. static void
  106. appendFItemList (htmlparserstate_t *html_state, agxbuf *ag);
  107. static void
  108. appendFLineList (htmlparserstate_t *html_state, int v);
  109. static htmltxt_t*
  110. mkText(htmlparserstate_t *html_state);
  111. static row_t *lastRow(htmlparserstate_t *html_state);
  112. /// Add new cell row to current table.
  113. static void addRow(htmlparserstate_t *html_state);
  114. /// Set cell body and type and attach to row
  115. static void setCell(htmlparserstate_t *html_state, htmlcell_t *cp, void *obj, label_type_t kind);
  116. /// Create label, given body and type.
  117. static htmllabel_t *mkLabel(void *obj, label_type_t kind) {
  118. htmllabel_t* lp = gv_alloc(sizeof(htmllabel_t));
  119. lp->kind = kind;
  120. if (kind == HTML_TEXT)
  121. lp->u.txt = obj;
  122. else
  123. lp->u.tbl = obj;
  124. return lp;
  125. }
  126. /* Called on error. Frees resources allocated during parsing.
  127. * This includes a label, plus a walk down the stack of
  128. * tables. Note that `cleanTbl` frees the contained cells.
  129. */
  130. static void cleanup (htmlparserstate_t *html_state);
  131. /// Return 1 if s contains a non-space character.
  132. static bool nonSpace(const char *s) {
  133. char c;
  134. while ((c = *s++)) {
  135. if (c != ' ') return true;
  136. }
  137. return false;
  138. }
  139. /// Fonts are allocated in the lexer.
  140. static void
  141. pushFont (htmlparserstate_t *html_state, textfont_t *fp);
  142. static void
  143. popFont (htmlparserstate_t *html_state);
  144. %}
  145. %union {
  146. int i;
  147. htmltxt_t* txt;
  148. htmlcell_t* cell;
  149. htmltbl_t* tbl;
  150. textfont_t* font;
  151. htmlimg_t* img;
  152. row_t *p;
  153. }
  154. %token T_end_br T_end_img T_row T_end_row T_html T_end_html
  155. %token T_end_table T_end_cell T_end_font T_string T_error
  156. %token T_n_italic T_n_bold T_n_underline T_n_overline T_n_sup T_n_sub T_n_s
  157. %token T_HR T_hr T_end_hr
  158. %token T_VR T_vr T_end_vr
  159. %token <i> T_BR T_br
  160. %token <img> T_IMG T_img
  161. %token <tbl> T_table
  162. %token <cell> T_cell
  163. %token <font> T_font T_italic T_bold T_underline T_overline T_sup T_sub T_s
  164. %type <txt> fonttext
  165. %type <cell> cell cells
  166. %type <i> br
  167. %type <tbl> table fonttable
  168. %type <img> image
  169. %type <p> row rows
  170. %start html
  171. %%
  172. html : T_html fonttext T_end_html { scanner->parser.lbl = mkLabel($2,HTML_TEXT); }
  173. | T_html fonttable T_end_html { scanner->parser.lbl = mkLabel($2,HTML_TBL); }
  174. | error { cleanup(&scanner->parser); YYABORT; }
  175. ;
  176. fonttext : text { $$ = mkText(&scanner->parser); }
  177. ;
  178. text : text textitem
  179. | textitem
  180. ;
  181. textitem : string { appendFItemList(&scanner->parser,scanner->parser.str);}
  182. | br {appendFLineList(&scanner->parser,$1);}
  183. | font text n_font
  184. | italic text n_italic
  185. | underline text n_underline
  186. | overline text n_overline
  187. | bold text n_bold
  188. | sup text n_sup
  189. | sub text n_sub
  190. | strike text n_strike
  191. ;
  192. font : T_font { pushFont (&scanner->parser,$1); }
  193. ;
  194. n_font : T_end_font { popFont (&scanner->parser); }
  195. ;
  196. italic : T_italic {pushFont(&scanner->parser,$1);}
  197. ;
  198. n_italic : T_n_italic {popFont(&scanner->parser);}
  199. ;
  200. bold : T_bold {pushFont(&scanner->parser,$1);}
  201. ;
  202. n_bold : T_n_bold {popFont(&scanner->parser);}
  203. ;
  204. strike : T_s {pushFont(&scanner->parser,$1);}
  205. ;
  206. n_strike : T_n_s {popFont(&scanner->parser);}
  207. ;
  208. underline : T_underline {pushFont(&scanner->parser,$1);}
  209. ;
  210. n_underline : T_n_underline {popFont(&scanner->parser);}
  211. ;
  212. overline : T_overline {pushFont(&scanner->parser,$1);}
  213. ;
  214. n_overline : T_n_overline {popFont(&scanner->parser);}
  215. ;
  216. sup : T_sup {pushFont(&scanner->parser,$1);}
  217. ;
  218. n_sup : T_n_sup {popFont(&scanner->parser);}
  219. ;
  220. sub : T_sub {pushFont(&scanner->parser,$1);}
  221. ;
  222. n_sub : T_n_sub {popFont(&scanner->parser);}
  223. ;
  224. br : T_br T_end_br { $$ = $1; }
  225. | T_BR { $$ = $1; }
  226. ;
  227. string : T_string
  228. | string T_string
  229. ;
  230. table : opt_space T_table {
  231. if (nonSpace(agxbuse(scanner->parser.str))) {
  232. htmlerror (scanner,"Syntax error: non-space string used before <TABLE>");
  233. cleanup(&scanner->parser); YYABORT;
  234. }
  235. $2->u.p.prev = scanner->parser.tblstack;
  236. $2->u.p.rows = (rows_t){0};
  237. scanner->parser.tblstack = $2;
  238. $2->font = *sfont_back(&scanner->parser.fontstack);
  239. $<tbl>$ = $2;
  240. }
  241. rows T_end_table opt_space {
  242. if (nonSpace(agxbuse(scanner->parser.str))) {
  243. htmlerror (scanner,"Syntax error: non-space string used after </TABLE>");
  244. cleanup(&scanner->parser); YYABORT;
  245. }
  246. $$ = scanner->parser.tblstack;
  247. scanner->parser.tblstack = scanner->parser.tblstack->u.p.prev;
  248. }
  249. ;
  250. fonttable : table { $$ = $1; }
  251. | font table n_font { $$=$2; }
  252. | italic table n_italic { $$=$2; }
  253. | underline table n_underline { $$=$2; }
  254. | overline table n_overline { $$=$2; }
  255. | bold table n_bold { $$=$2; }
  256. ;
  257. opt_space : string
  258. | /* empty*/
  259. ;
  260. rows : row { $$ = $1; }
  261. | rows row { $$ = $2; }
  262. | rows HR row { $1->ruled = true; $$ = $3; }
  263. ;
  264. row : T_row { addRow (&scanner->parser); } cells T_end_row { $$ = lastRow(&scanner->parser); }
  265. ;
  266. cells : cell { $$ = $1; }
  267. | cells cell { $$ = $2; }
  268. | cells VR cell { $1->vruled = true; $$ = $3; }
  269. ;
  270. cell : T_cell fonttable { setCell(&scanner->parser,$1,$2,HTML_TBL); } T_end_cell { $$ = $1; }
  271. | T_cell fonttext { setCell(&scanner->parser,$1,$2,HTML_TEXT); } T_end_cell { $$ = $1; }
  272. | T_cell image { setCell(&scanner->parser,$1,$2,HTML_IMAGE); } T_end_cell { $$ = $1; }
  273. | T_cell { setCell(&scanner->parser,$1,mkText(&scanner->parser),HTML_TEXT); } T_end_cell { $$ = $1; }
  274. ;
  275. image : T_img T_end_img { $$ = $1; }
  276. | T_IMG { $$ = $1; }
  277. ;
  278. HR : T_hr T_end_hr
  279. | T_HR
  280. ;
  281. VR : T_vr T_end_vr
  282. | T_VR
  283. ;
  284. %%
  285. static void
  286. appendFItemList (htmlparserstate_t *html_state, agxbuf *ag)
  287. {
  288. const textspan_t ti = {.str = agxbdisown(ag),
  289. .font = *sfont_back(&html_state->fontstack)};
  290. textspans_append(&html_state->fitemList, ti);
  291. }
  292. static void
  293. appendFLineList (htmlparserstate_t *html_state, int v)
  294. {
  295. htextspan_t lp = {0};
  296. textspans_t *ilist = &html_state->fitemList;
  297. size_t cnt = textspans_size(ilist);
  298. lp.just = v;
  299. if (cnt) {
  300. lp.nitems = cnt;
  301. lp.items = gv_calloc(cnt, sizeof(textspan_t));
  302. for (size_t i = 0; i < textspans_size(ilist); ++i) {
  303. // move this text span into the new list
  304. textspan_t *ti = textspans_at(ilist, i);
  305. lp.items[i] = *ti;
  306. *ti = (textspan_t){0};
  307. }
  308. }
  309. else {
  310. lp.items = gv_alloc(sizeof(textspan_t));
  311. lp.nitems = 1;
  312. lp.items[0].str = gv_strdup("");
  313. lp.items[0].font = *sfont_back(&html_state->fontstack);
  314. }
  315. textspans_clear(ilist);
  316. htextspans_append(&html_state->fspanList, lp);
  317. }
  318. static htmltxt_t*
  319. mkText(htmlparserstate_t *html_state)
  320. {
  321. htextspans_t *ispan = &html_state->fspanList;
  322. htmltxt_t *hft = gv_alloc(sizeof(htmltxt_t));
  323. if (!textspans_is_empty(&html_state->fitemList))
  324. appendFLineList (html_state, UNSET_ALIGN);
  325. size_t cnt = htextspans_size(ispan);
  326. hft->nspans = cnt;
  327. hft->spans = gv_calloc(cnt, sizeof(htextspan_t));
  328. for (size_t i = 0; i < htextspans_size(ispan); ++i) {
  329. // move this HTML text span into the new list
  330. htextspan_t *hi = htextspans_at(ispan, i);
  331. hft->spans[i] = *hi;
  332. *hi = (htextspan_t){0};
  333. }
  334. htextspans_clear(ispan);
  335. return hft;
  336. }
  337. static row_t *lastRow(htmlparserstate_t *html_state) {
  338. htmltbl_t* tbl = html_state->tblstack;
  339. row_t *sp = *rows_back(&tbl->u.p.rows);
  340. return sp;
  341. }
  342. static void addRow(htmlparserstate_t *html_state) {
  343. htmltbl_t* tbl = html_state->tblstack;
  344. row_t *sp = gv_alloc(sizeof(row_t));
  345. if (tbl->hrule)
  346. sp->ruled = true;
  347. rows_append(&tbl->u.p.rows, sp);
  348. }
  349. static void setCell(htmlparserstate_t *html_state, htmlcell_t *cp, void *obj, label_type_t kind) {
  350. htmltbl_t* tbl = html_state->tblstack;
  351. row_t *rp = *rows_back(&tbl->u.p.rows);
  352. cells_t *row = &rp->rp;
  353. cells_append(row, cp);
  354. cp->child.kind = kind;
  355. if (tbl->vrule) {
  356. cp->vruled = true;
  357. cp->hruled = false;
  358. }
  359. if(kind == HTML_TEXT)
  360. cp->child.u.txt = obj;
  361. else if (kind == HTML_IMAGE)
  362. cp->child.u.img = obj;
  363. else
  364. cp->child.u.tbl = obj;
  365. }
  366. static void cleanup (htmlparserstate_t *html_state)
  367. {
  368. htmltbl_t* tp = html_state->tblstack;
  369. htmltbl_t* next;
  370. if (html_state->lbl) {
  371. free_html_label (html_state->lbl,1);
  372. html_state->lbl = NULL;
  373. }
  374. while (tp) {
  375. next = tp->u.p.prev;
  376. cleanTbl (tp);
  377. tp = next;
  378. }
  379. textspans_clear(&html_state->fitemList);
  380. htextspans_clear(&html_state->fspanList);
  381. sfont_free(&html_state->fontstack);
  382. }
  383. static void
  384. pushFont (htmlparserstate_t *html_state, textfont_t *fp)
  385. {
  386. textfont_t* curfont = *sfont_back(&html_state->fontstack);
  387. textfont_t f = *fp;
  388. if (curfont) {
  389. if (!f.color && curfont->color)
  390. f.color = curfont->color;
  391. if ((f.size < 0.0) && (curfont->size >= 0.0))
  392. f.size = curfont->size;
  393. if (!f.name && curfont->name)
  394. f.name = curfont->name;
  395. if (curfont->flags)
  396. f.flags |= curfont->flags;
  397. }
  398. textfont_t *const ft = dtinsert(html_state->gvc->textfont_dt, &f);
  399. sfont_push_back(&html_state->fontstack, ft);
  400. }
  401. static void
  402. popFont (htmlparserstate_t *html_state)
  403. {
  404. (void)sfont_pop_back(&html_state->fontstack);
  405. }
  406. /* Return parsed label or NULL if failure.
  407. * Set warn to 0 on success; 1 for warning message; 2 if no expat; 3 for error
  408. * message.
  409. */
  410. htmllabel_t*
  411. parseHTML (char* txt, int* warn, htmlenv_t *env)
  412. {
  413. agxbuf str = {0};
  414. htmllabel_t* l = NULL;
  415. htmlscan_t scanner = {0};
  416. sfont_push_back(&scanner.parser.fontstack, NULL);
  417. scanner.parser.gvc = GD_gvc(env->g);
  418. scanner.parser.str = &str;
  419. if (initHTMLlexer (&scanner, txt, &str, env)) {/* failed: no libexpat - give up */
  420. *warn = 2;
  421. }
  422. else {
  423. htmlparse(&scanner);
  424. *warn = clearHTMLlexer (&scanner);
  425. l = scanner.parser.lbl;
  426. }
  427. textspans_free(&scanner.parser.fitemList);
  428. htextspans_free(&scanner.parser.fspanList);
  429. sfont_free(&scanner.parser.fontstack);
  430. agxbfree (&str);
  431. return l;
  432. }