1 /*
2 * This file is part of Hubbub.
3 * Licensed under the MIT License,
4 * http://www.opensource.org/licenses/mit-license.php
5 * Copyright 2008 John-Mark Bell <jmb@netsurf-browser.org>
6 */
7
8 #include <assert.h>
9 #include <string.h>
10 #include <strings.h>
11
12 #include <stdio.h>
13
14 #include "treebuilder/modes.h"
15 #include "treebuilder/internal.h"
16 #include "treebuilder/treebuilder.h"
17 #include "utils/utils.h"
18 #include "utils/string.h"
19
20
21 #define S(x) x, SLEN(x)
22
23 static const struct {
24 const char *name;
25 size_t len;
26 element_type type;
27 } name_type_map[] = {
28 { S("address"), ADDRESS },
29 { S("area"), AREA },
30 { S("article"), ARTICLE },
31 { S("aside"), ASIDE },
32 { S("base"), BASE },
33 { S("basefont"), BASEFONT },
34 { S("bgsound"), BGSOUND },
35 { S("blockquote"), BLOCKQUOTE },
36 { S("body"), BODY },
37 { S("br"), BR },
38 { S("center"), CENTER },
39 { S("col"), COL },
40 { S("colgroup"), COLGROUP },
41 { S("command"), COMMAND },
42 { S("dd"), DD },
43 { S("details"), DETAILS },
44 { S("dialog"), DIALOG },
45 { S("dir"), DIR },
46 { S("div"), DIV },
47 { S("dl"), DL },
48 { S("dt"), DT },
49 { S("embed"), EMBED },
50 { S("fieldset"), FIELDSET },
51 { S("figcaption"), FIGCAPTION },
52 { S("figure"), FIGURE },
53 { S("footer"), FOOTER },
54 { S("form"), FORM },
55 { S("frame"), FRAME },
56 { S("frameset"), FRAMESET },
57 { S("h1"), H1 },
58 { S("h2"), H2 },
59 { S("h3"), H3 },
60 { S("h4"), H4 },
61 { S("h5"), H5 },
62 { S("h6"), H6 },
63 { S("head"), HEAD },
64 { S("hr"), HR },
65 { S("iframe"), IFRAME },
66 { S("image"), IMAGE },
67 { S("img"), IMG },
68 { S("input"), INPUT },
69 { S("isindex"), ISINDEX },
70 { S("li"), LI },
71 { S("link"), LINK },
72 { S("listing"), LISTING },
73 { S("menu"), MENU },
74 { S("meta"), META },
75 { S("noembed"), NOEMBED },
76 { S("noframes"), NOFRAMES },
77 { S("noscript"), NOSCRIPT },
78 { S("ol"), OL },
79 { S("optgroup"), OPTGROUP },
80 { S("option"), OPTION },
81 { S("output"), OUTPUT },
82 { S("p"), P },
83 { S("param"), PARAM },
84 { S("plaintext"), PLAINTEXT },
85 { S("pre"), PRE },
86 { S("script"), SCRIPT },
87 { S("select"), SELECT },
88 { S("spacer"), SPACER },
89 { S("style"), STYLE },
90 { S("summary"), SUMMARY },
91 { S("tbody"), TBODY },
92 { S("textarea"), TEXTAREA },
93 { S("tfoot"), TFOOT },
94 { S("thead"), THEAD },
95 { S("title"), TITLE },
96 { S("tr"), TR },
97 { S("ul"), UL },
98 { S("wbr"), WBR },
99
100 { S("applet"), APPLET },
101 { S("button"), BUTTON },
102 { S("caption"), CAPTION },
103 { S("html"), HTML },
104 { S("marquee"), MARQUEE },
105 { S("object"), OBJECT },
106 { S("table"), TABLE },
107 { S("td"), TD },
108 { S("th"), TH },
109
110 { S("a"), A },
111 { S("b"), B },
112 { S("big"), BIG },
113 { S("em"), EM },
114 { S("font"), FONT },
115 { S("i"), I },
116 { S("nobr"), NOBR },
117 { S("s"), S },
118 { S("small"), SMALL },
119 { S("strike"), STRIKE },
120 { S("strong"), STRONG },
121 { S("tt"), TT },
122 { S("u"), U },
123
124 { S("xmp"), XMP },
125
126 { S("math"), MATH },
127 { S("mglyph"), MGLYPH },
128 { S("malignmark"), MALIGNMARK },
129 { S("mi"), MI },
130 { S("mo"), MO },
131 { S("mn"), MN },
132 { S("ms"), MS },
133 { S("mtext"), MTEXT },
134 { S("annotation-xml"), ANNOTATION_XML },
135
136 { S("svg"), SVG },
137 { S("desc"), DESC },
138 { S("foreignobject"), FOREIGNOBJECT },
139 };
140
141 static bool is_form_associated(element_type type);
142
143 /**
144 * Create a hubbub treebuilder
145 *
146 * \param tokeniser Underlying tokeniser instance
147 * \param treebuilder Pointer to location to receive treebuilder instance
148 * \return HUBBUB_OK on success,
149 * HUBBUB_BADPARM on bad parameters
150 * HUBBUB_NOMEM on memory exhaustion
151 */
hubbub_treebuilder_create(hubbub_tokeniser * tokeniser,hubbub_treebuilder ** treebuilder)152 hubbub_error hubbub_treebuilder_create(hubbub_tokeniser *tokeniser,
153 hubbub_treebuilder **treebuilder)
154 {
155 hubbub_error error;
156 hubbub_treebuilder *tb;
157 hubbub_tokeniser_optparams tokparams;
158
159 if (tokeniser == NULL || treebuilder == NULL)
160 return HUBBUB_BADPARM;
161
162 tb = malloc(sizeof(hubbub_treebuilder));
163 if (tb == NULL)
164 return HUBBUB_NOMEM;
165
166 tb->tokeniser = tokeniser;
167
168 tb->tree_handler = NULL;
169
170 memset(&tb->context, 0, sizeof(hubbub_treebuilder_context));
171 tb->context.mode = INITIAL;
172
173 tb->context.element_stack = malloc(
174 ELEMENT_STACK_CHUNK * sizeof(element_context));
175 if (tb->context.element_stack == NULL) {
176 free(tb);
177 return HUBBUB_NOMEM;
178 }
179 tb->context.stack_alloc = ELEMENT_STACK_CHUNK;
180 /* We rely on HTML not being equal to zero to determine
181 * if the first item in the stack is in use. Assert this here. */
182 assert(HTML != 0);
183 tb->context.element_stack[0].type = (element_type) 0;
184
185 tb->context.strip_leading_lr = false;
186 tb->context.frameset_ok = true;
187
188 tb->error_handler = NULL;
189 tb->error_pw = NULL;
190
191 tokparams.token_handler.handler = hubbub_treebuilder_token_handler;
192 tokparams.token_handler.pw = tb;
193
194 error = hubbub_tokeniser_setopt(tokeniser,
195 HUBBUB_TOKENISER_TOKEN_HANDLER, &tokparams);
196 if (error != HUBBUB_OK) {
197 free(tb->context.element_stack);
198 free(tb);
199 return error;
200 }
201
202 *treebuilder = tb;
203
204 return HUBBUB_OK;
205 }
206
207 /**
208 * Destroy a hubbub treebuilder
209 *
210 * \param treebuilder The treebuilder instance to destroy
211 * \return HUBBUB_OK on success, appropriate error otherwise
212 */
hubbub_treebuilder_destroy(hubbub_treebuilder * treebuilder)213 hubbub_error hubbub_treebuilder_destroy(hubbub_treebuilder *treebuilder)
214 {
215 formatting_list_entry *entry, *next;
216 hubbub_tokeniser_optparams tokparams;
217
218 if (treebuilder == NULL)
219 return HUBBUB_BADPARM;
220
221 tokparams.token_handler.handler = NULL;
222 tokparams.token_handler.pw = NULL;
223
224 hubbub_tokeniser_setopt(treebuilder->tokeniser,
225 HUBBUB_TOKENISER_TOKEN_HANDLER, &tokparams);
226
227 /* Clean up context */
228 if (treebuilder->tree_handler != NULL) {
229 uint32_t n;
230
231 if (treebuilder->context.head_element != NULL) {
232 treebuilder->tree_handler->unref_node(
233 treebuilder->tree_handler->ctx,
234 treebuilder->context.head_element);
235 }
236
237 if (treebuilder->context.form_element != NULL) {
238 treebuilder->tree_handler->unref_node(
239 treebuilder->tree_handler->ctx,
240 treebuilder->context.form_element);
241 }
242
243 if (treebuilder->context.document != NULL) {
244 treebuilder->tree_handler->unref_node(
245 treebuilder->tree_handler->ctx,
246 treebuilder->context.document);
247 }
248
249 for (n = treebuilder->context.current_node;
250 n > 0; n--) {
251 treebuilder->tree_handler->unref_node(
252 treebuilder->tree_handler->ctx,
253 treebuilder->context.element_stack[n].node);
254 }
255 if (treebuilder->context.element_stack[0].type == HTML) {
256 treebuilder->tree_handler->unref_node(
257 treebuilder->tree_handler->ctx,
258 treebuilder->context.element_stack[0].node);
259 }
260 }
261 free(treebuilder->context.element_stack);
262 treebuilder->context.element_stack = NULL;
263
264 for (entry = treebuilder->context.formatting_list; entry != NULL;
265 entry = next) {
266 next = entry->next;
267
268 if (treebuilder->tree_handler != NULL) {
269 treebuilder->tree_handler->unref_node(
270 treebuilder->tree_handler->ctx,
271 entry->details.node);
272 }
273
274 free(entry);
275 }
276
277 free(treebuilder);
278
279 return HUBBUB_OK;
280 }
281
282 /**
283 * Configure a hubbub treebuilder
284 *
285 * \param treebuilder The treebuilder instance to configure
286 * \param type The option type to configure
287 * \param params Pointer to option-specific parameters
288 * \return HUBBUB_OK on success, appropriate error otherwise.
289 */
hubbub_treebuilder_setopt(hubbub_treebuilder * treebuilder,hubbub_treebuilder_opttype type,hubbub_treebuilder_optparams * params)290 hubbub_error hubbub_treebuilder_setopt(hubbub_treebuilder *treebuilder,
291 hubbub_treebuilder_opttype type,
292 hubbub_treebuilder_optparams *params)
293 {
294 if (treebuilder == NULL || params == NULL)
295 return HUBBUB_BADPARM;
296
297 switch (type) {
298 case HUBBUB_TREEBUILDER_ERROR_HANDLER:
299 treebuilder->error_handler = params->error_handler.handler;
300 treebuilder->error_pw = params->error_handler.pw;
301 break;
302 case HUBBUB_TREEBUILDER_TREE_HANDLER:
303 treebuilder->tree_handler = params->tree_handler;
304 break;
305 case HUBBUB_TREEBUILDER_DOCUMENT_NODE:
306 treebuilder->context.document = params->document_node;
307 break;
308 case HUBBUB_TREEBUILDER_ENABLE_SCRIPTING:
309 treebuilder->context.enable_scripting =
310 params->enable_scripting;
311 break;
312 }
313
314 return HUBBUB_OK;
315 }
316
317 /**
318 * Handle tokeniser emitting a token
319 *
320 * \param token The emitted token
321 * \param pw Pointer to treebuilder instance
322 */
hubbub_treebuilder_token_handler(const hubbub_token * token,void * pw)323 hubbub_error hubbub_treebuilder_token_handler(const hubbub_token *token,
324 void *pw)
325 {
326 hubbub_treebuilder *treebuilder = (hubbub_treebuilder *) pw;
327 hubbub_error err = HUBBUB_REPROCESS;
328
329 /* Do nothing if we have no document node or there's no tree handler */
330 if (treebuilder->context.document == NULL ||
331 treebuilder->tree_handler == NULL)
332 return HUBBUB_OK;
333
334 assert((signed) treebuilder->context.current_node >= 0);
335
336 /* A slightly nasty debugging hook, but very useful */
337 #ifdef NDEBUG
338 # define mode(x) \
339 case x:
340 #else
341 # define mode(x) \
342 case x: \
343 printf( #x "\n");
344 #endif
345
346 while (err == HUBBUB_REPROCESS) {
347 switch (treebuilder->context.mode) {
348 mode(INITIAL)
349 err = handle_initial(treebuilder, token);
350 break;
351 mode(BEFORE_HTML)
352 err = handle_before_html(treebuilder, token);
353 break;
354 mode(BEFORE_HEAD)
355 err = handle_before_head(treebuilder, token);
356 break;
357 mode(IN_HEAD)
358 err = handle_in_head(treebuilder, token);
359 break;
360 mode(IN_HEAD_NOSCRIPT)
361 err = handle_in_head_noscript(treebuilder, token);
362 break;
363 mode(AFTER_HEAD)
364 err = handle_after_head(treebuilder, token);
365 break;
366 mode(IN_BODY)
367 err = handle_in_body(treebuilder, token);
368 break;
369 mode(IN_TABLE)
370 err = handle_in_table(treebuilder, token);
371 break;
372 mode(IN_CAPTION)
373 err = handle_in_caption(treebuilder, token);
374 break;
375 mode(IN_COLUMN_GROUP)
376 err = handle_in_column_group(treebuilder, token);
377 break;
378 mode(IN_TABLE_BODY)
379 err = handle_in_table_body(treebuilder, token);
380 break;
381 mode(IN_ROW)
382 err = handle_in_row(treebuilder, token);
383 break;
384 mode(IN_CELL)
385 err = handle_in_cell(treebuilder, token);
386 break;
387 mode(IN_SELECT)
388 err = handle_in_select(treebuilder, token);
389 break;
390 mode(IN_SELECT_IN_TABLE)
391 err = handle_in_select_in_table(treebuilder, token);
392 break;
393 mode(IN_FOREIGN_CONTENT)
394 err = handle_in_foreign_content(treebuilder, token);
395 break;
396 mode(AFTER_BODY)
397 err = handle_after_body(treebuilder, token);
398 break;
399 mode(IN_FRAMESET)
400 err = handle_in_frameset(treebuilder, token);
401 break;
402 mode(AFTER_FRAMESET)
403 err = handle_after_frameset(treebuilder, token);
404 break;
405 mode(AFTER_AFTER_BODY)
406 err = handle_after_after_body(treebuilder, token);
407 break;
408 mode(AFTER_AFTER_FRAMESET)
409 err = handle_after_after_frameset(treebuilder, token);
410 break;
411 mode(GENERIC_RCDATA)
412 err = handle_generic_rcdata(treebuilder, token);
413 break;
414 }
415 }
416
417 return err;
418 }
419
420
421 /**
422 * Process a character token in cases where we expect only whitespace
423 *
424 * \param treebuilder The treebuilder instance
425 * \param token The character token
426 * \param insert_into_current_node Whether to insert whitespace into
427 * current node
428 * \return HUBBUB_REPROCESS if the token needs reprocessing
429 * (token data updated to skip any leading whitespace),
430 * HUBBUB_OK if it contained only whitespace,
431 * appropriate error otherwise
432 */
process_characters_expect_whitespace(hubbub_treebuilder * treebuilder,const hubbub_token * token,bool insert_into_current_node)433 hubbub_error process_characters_expect_whitespace(
434 hubbub_treebuilder *treebuilder,
435 const hubbub_token *token, bool insert_into_current_node)
436 {
437 const uint8_t *data = token->data.character.ptr;
438 size_t len = token->data.character.len;
439 size_t c;
440
441 for (c = 0; c < len; c++) {
442 if (data[c] != 0x09 && data[c] != 0x0A &&
443 data[c] != 0x0C && data[c] != 0x20)
444 break;
445 }
446
447 if (c > 0 && insert_into_current_node) {
448 hubbub_error error;
449 hubbub_string temp;
450
451 temp.ptr = data;
452 temp.len = c;
453
454 error = append_text(treebuilder, &temp);
455 if (error != HUBBUB_OK)
456 return error;
457 }
458
459 /* Non-whitespace characters in token, so reprocess */
460 if (c != len) {
461 /* Update token data to strip leading whitespace */
462 ((hubbub_token *) token)->data.character.ptr += c;
463 ((hubbub_token *) token)->data.character.len -= c;
464
465 return HUBBUB_REPROCESS;
466 }
467
468 return HUBBUB_OK;
469 }
470
471 /**
472 * Process a comment token, appending it to the given parent
473 *
474 * \param treebuilder The treebuilder instance
475 * \param token The comment token
476 * \param parent The node to append to
477 * \return HUBBUB_OK on success, appropriate error otherwise
478 */
process_comment_append(hubbub_treebuilder * treebuilder,const hubbub_token * token,void * parent)479 hubbub_error process_comment_append(hubbub_treebuilder *treebuilder,
480 const hubbub_token *token, void *parent)
481 {
482 hubbub_error error = HUBBUB_OK;
483 element_type type = current_node(treebuilder);
484 void *comment, *appended;
485
486 error = treebuilder->tree_handler->create_comment(
487 treebuilder->tree_handler->ctx,
488 &token->data.comment, &comment);
489 if (error != HUBBUB_OK)
490 return error;
491
492 if (treebuilder->context.in_table_foster &&
493 (type == TABLE || type == TBODY || type == TFOOT ||
494 type == THEAD || type == TR)) {
495 error = aa_insert_into_foster_parent(treebuilder, comment,
496 &appended);
497 } else {
498 error = treebuilder->tree_handler->append_child(
499 treebuilder->tree_handler->ctx,
500 parent, comment, &appended);
501 }
502
503 if (error == HUBBUB_OK) {
504 treebuilder->tree_handler->unref_node(
505 treebuilder->tree_handler->ctx, appended);
506 }
507
508 treebuilder->tree_handler->unref_node(
509 treebuilder->tree_handler->ctx, comment);
510
511 return error;
512 }
513
514 /**
515 * Trigger parsing of generic (R)CDATA
516 *
517 * \param treebuilder The treebuilder instance
518 * \param token The current token
519 * \param rcdata True for RCDATA, false for CDATA
520 * \return HUBBUB_OK on success, appropriate error otherwise
521 */
parse_generic_rcdata(hubbub_treebuilder * treebuilder,const hubbub_token * token,bool rcdata)522 hubbub_error parse_generic_rcdata(hubbub_treebuilder *treebuilder,
523 const hubbub_token *token, bool rcdata)
524 {
525 hubbub_error error;
526 element_type type;
527 hubbub_tokeniser_optparams params;
528
529 type = element_type_from_name(treebuilder, &token->data.tag.name);
530
531 error = insert_element(treebuilder, &token->data.tag, true);
532 if (error != HUBBUB_OK)
533 return error;
534
535 params.content_model.model = rcdata ? HUBBUB_CONTENT_MODEL_RCDATA
536 : HUBBUB_CONTENT_MODEL_CDATA;
537 error = hubbub_tokeniser_setopt(treebuilder->tokeniser,
538 HUBBUB_TOKENISER_CONTENT_MODEL, ¶ms);
539 /* There is no way that setopt can fail. Ensure this. */
540 assert(error == HUBBUB_OK);
541
542 treebuilder->context.collect.mode = treebuilder->context.mode;
543 treebuilder->context.collect.type = type;
544
545 treebuilder->context.mode = GENERIC_RCDATA;
546
547 return HUBBUB_OK;
548 }
549
550 /**
551 * Determine if an element is in (table) scope
552 *
553 * \param treebuilder Treebuilder to look in
554 * \param type Element type to find
555 * \param in_table Whether we're looking in table scope
556 * \return Element stack index, or 0 if not in scope
557 */
element_in_scope(hubbub_treebuilder * treebuilder,element_type type,bool in_table)558 uint32_t element_in_scope(hubbub_treebuilder *treebuilder,
559 element_type type, bool in_table)
560 {
561 uint32_t node;
562
563 if (treebuilder->context.element_stack == NULL)
564 return 0;
565
566 assert((signed) treebuilder->context.current_node >= 0);
567
568 for (node = treebuilder->context.current_node; node > 0; node--) {
569 hubbub_ns node_ns =
570 treebuilder->context.element_stack[node].ns;
571 element_type node_type =
572 treebuilder->context.element_stack[node].type;
573
574 if (node_type == type)
575 return node;
576
577 if (node_type == TABLE)
578 break;
579
580 /* The list of element types given in the spec here are the
581 * scoping elements excluding TABLE and HTML. TABLE is handled
582 * in the previous conditional and HTML should only occur
583 * as the first node in the stack, which is never processed
584 * in this loop. */
585 if (!in_table && (is_scoping_element(node_type) ||
586 (node_type == FOREIGNOBJECT &&
587 node_ns == HUBBUB_NS_SVG))) {
588 break;
589 }
590 }
591
592 return 0;
593 }
594
595 /**
596 * Reconstruct the list of active formatting elements
597 *
598 * \param treebuilder Treebuilder instance containing list
599 * \return HUBBUB_OK on success, appropriate error otherwise.
600 */
reconstruct_active_formatting_list(hubbub_treebuilder * treebuilder)601 hubbub_error reconstruct_active_formatting_list(hubbub_treebuilder *treebuilder)
602 {
603 hubbub_error error = HUBBUB_OK;
604 formatting_list_entry *entry, *initial_entry;
605 uint32_t sp = treebuilder->context.current_node;
606
607 if (treebuilder->context.formatting_list == NULL)
608 return HUBBUB_OK;
609
610 entry = treebuilder->context.formatting_list_end;
611
612 /* Assumption: HTML and TABLE elements are not inserted into the list */
613 if (is_scoping_element(entry->details.type) || entry->stack_index != 0)
614 return HUBBUB_OK;
615
616 while (entry->prev != NULL) {
617 entry = entry->prev;
618
619 if (is_scoping_element(entry->details.type) ||
620 entry->stack_index != 0) {
621 entry = entry->next;
622 break;
623 }
624 }
625
626 /* Save initial entry for later */
627 initial_entry = entry;
628
629 /* Process formatting list entries, cloning nodes and
630 * inserting them into the DOM and element stack */
631 while (entry != NULL) {
632 void *clone, *appended;
633 bool foster;
634 element_type type = current_node(treebuilder);
635
636 error = treebuilder->tree_handler->clone_node(
637 treebuilder->tree_handler->ctx,
638 entry->details.node,
639 false,
640 &clone);
641 if (error != HUBBUB_OK)
642 goto cleanup;
643
644 foster = treebuilder->context.in_table_foster &&
645 (type == TABLE || type == TBODY ||
646 type == TFOOT || type == THEAD ||
647 type == TR);
648
649 if (foster) {
650 error = aa_insert_into_foster_parent(treebuilder,
651 clone, &appended);
652 } else {
653 error = treebuilder->tree_handler->append_child(
654 treebuilder->tree_handler->ctx,
655 treebuilder->context.element_stack[
656 treebuilder->context.current_node].node,
657 clone,
658 &appended);
659 }
660
661 /* No longer interested in clone */
662 treebuilder->tree_handler->unref_node(
663 treebuilder->tree_handler->ctx,
664 clone);
665
666 if (error != HUBBUB_OK)
667 goto cleanup;
668
669 error = element_stack_push(treebuilder, entry->details.ns,
670 entry->details.type, appended);
671 if (error != HUBBUB_OK) {
672 remove_node_from_dom(treebuilder, appended);
673
674 treebuilder->tree_handler->unref_node(
675 treebuilder->tree_handler->ctx,
676 appended);
677
678 goto cleanup;
679 }
680
681 entry = entry->next;
682 }
683
684 /* Now, replace the formatting list entries */
685 for (entry = initial_entry; entry != NULL; entry = entry->next) {
686 void *node;
687 hubbub_ns prev_ns;
688 element_type prev_type;
689 void *prev_node;
690 uint32_t prev_stack_index;
691
692 node = treebuilder->context.element_stack[++sp].node;
693
694 treebuilder->tree_handler->ref_node(
695 treebuilder->tree_handler->ctx, node);
696
697 error = formatting_list_replace(treebuilder, entry,
698 entry->details.ns, entry->details.type,
699 node, sp,
700 &prev_ns, &prev_type, &prev_node,
701 &prev_stack_index);
702 /* Cannot fail. Ensure this. */
703 assert(error == HUBBUB_OK);
704
705 treebuilder->tree_handler->unref_node(
706 treebuilder->tree_handler->ctx,
707 prev_node);
708 }
709
710 return HUBBUB_OK;
711
712 cleanup:
713 /* An error occurred while cloning nodes and inserting them.
714 * We must restore the state on entry here. */
715 while (treebuilder->context.current_node > sp) {
716 hubbub_ns ns;
717 element_type type;
718 void *node;
719
720 element_stack_pop(treebuilder, &ns, &type, &node);
721
722 remove_node_from_dom(treebuilder, node);
723
724 treebuilder->tree_handler->unref_node(
725 treebuilder->tree_handler->ctx,
726 node);
727 }
728
729 return error;
730 }
731
732 /**
733 * Remove a node from the DOM
734 *
735 * \param treebuilder Treebuilder instance
736 * \param node Node to remove
737 * \return HUBBUB_OK on success, appropriate error otherwise.
738 */
remove_node_from_dom(hubbub_treebuilder * treebuilder,void * node)739 hubbub_error remove_node_from_dom(hubbub_treebuilder *treebuilder, void *node)
740 {
741 hubbub_error err;
742 void *parent = NULL;
743 void *removed;
744
745 err = treebuilder->tree_handler->get_parent(
746 treebuilder->tree_handler->ctx,
747 node, false, &parent);
748 if (err != HUBBUB_OK)
749 return err;
750
751 if (parent != NULL) {
752 err = treebuilder->tree_handler->remove_child(
753 treebuilder->tree_handler->ctx,
754 parent, node, &removed);
755 if (err != HUBBUB_OK)
756 return err;
757
758 treebuilder->tree_handler->unref_node(
759 treebuilder->tree_handler->ctx,
760 parent);
761
762 treebuilder->tree_handler->unref_node(
763 treebuilder->tree_handler->ctx,
764 removed);
765 }
766
767 return HUBBUB_OK;
768 }
769
770 /**
771 * Clear the list of active formatting elements up to the last marker
772 *
773 * \param treebuilder The treebuilder instance containing the list
774 */
clear_active_formatting_list_to_marker(hubbub_treebuilder * treebuilder)775 void clear_active_formatting_list_to_marker(hubbub_treebuilder *treebuilder)
776 {
777 formatting_list_entry *entry;
778 bool done = false;
779
780 while ((entry = treebuilder->context.formatting_list_end) != NULL) {
781 hubbub_ns ns;
782 element_type type;
783 void *node;
784 uint32_t stack_index;
785
786 if (is_scoping_element(entry->details.type))
787 done = true;
788
789 formatting_list_remove(treebuilder, entry,
790 &ns, &type, &node, &stack_index);
791
792 treebuilder->tree_handler->unref_node(
793 treebuilder->tree_handler->ctx,
794 node);
795
796 if (done)
797 break;
798 }
799 }
800
801 /**
802 * Create element and insert it into the DOM,
803 * potentially pushing it on the stack
804 *
805 * \param treebuilder The treebuilder instance
806 * \param tag The element to insert
807 * \param push Whether to push the element onto the stack
808 * \return HUBBUB_OK on success, appropriate error otherwise.
809 */
insert_element(hubbub_treebuilder * treebuilder,const hubbub_tag * tag,bool push)810 hubbub_error insert_element(hubbub_treebuilder *treebuilder,
811 const hubbub_tag *tag, bool push)
812 {
813 element_type type = current_node(treebuilder);
814 hubbub_error error;
815 void *node, *appended;
816
817 error = treebuilder->tree_handler->create_element(
818 treebuilder->tree_handler->ctx, tag, &node);
819 if (error != HUBBUB_OK)
820 return error;
821
822 if (treebuilder->context.in_table_foster &&
823 (type == TABLE || type == TBODY || type == TFOOT ||
824 type == THEAD || type == TR)) {
825 error = aa_insert_into_foster_parent(treebuilder, node,
826 &appended);
827 } else {
828 error = treebuilder->tree_handler->append_child(
829 treebuilder->tree_handler->ctx,
830 treebuilder->context.element_stack[
831 treebuilder->context.current_node].node,
832 node, &appended);
833 }
834
835 /* No longer interested in node */
836 treebuilder->tree_handler->unref_node(
837 treebuilder->tree_handler->ctx, node);
838
839 if (error != HUBBUB_OK)
840 return error;
841
842 type = element_type_from_name(treebuilder, &tag->name);
843 if (treebuilder->context.form_element != NULL &&
844 is_form_associated(type)) {
845 /* Consideration of @form is left to the client */
846 error = treebuilder->tree_handler->form_associate(
847 treebuilder->tree_handler->ctx,
848 treebuilder->context.form_element,
849 appended);
850 if (error != HUBBUB_OK) {
851 remove_node_from_dom(treebuilder, appended);
852
853 treebuilder->tree_handler->unref_node(
854 treebuilder->tree_handler->ctx,
855 appended);
856
857 return error;
858 }
859 }
860
861 if (push) {
862 error = element_stack_push(treebuilder,
863 tag->ns, type, appended);
864 if (error != HUBBUB_OK) {
865 remove_node_from_dom(treebuilder, appended);
866
867 treebuilder->tree_handler->unref_node(
868 treebuilder->tree_handler->ctx,
869 appended);
870 return error;
871 }
872 } else {
873 treebuilder->tree_handler->unref_node(
874 treebuilder->tree_handler->ctx, appended);
875 }
876
877 return HUBBUB_OK;
878 }
879
880 /**
881 * Close implied end tags
882 *
883 * \param treebuilder The treebuilder instance
884 * \param except Tag type to exclude from processing [DD,DT,LI,OPTION,
885 * OPTGROUP,P,RP,RT], UNKNOWN to exclude nothing
886 */
close_implied_end_tags(hubbub_treebuilder * treebuilder,element_type except)887 void close_implied_end_tags(hubbub_treebuilder *treebuilder,
888 element_type except)
889 {
890 element_type type;
891
892 type = treebuilder->context.element_stack[
893 treebuilder->context.current_node].type;
894
895 while (type == DD || type == DT || type == LI || type == OPTION ||
896 type == OPTGROUP || type == P || type == RP ||
897 type == RT) {
898 hubbub_ns ns;
899 element_type otype;
900 void *node;
901
902 if (except != UNKNOWN && type == except)
903 break;
904
905 element_stack_pop(treebuilder, &ns, &otype, &node);
906
907 treebuilder->tree_handler->unref_node(
908 treebuilder->tree_handler->ctx,
909 node);
910
911 type = treebuilder->context.element_stack[
912 treebuilder->context.current_node].type;
913 }
914 }
915
916 /**
917 * Reset the insertion mode
918 *
919 * \param treebuilder The treebuilder to reset
920 */
reset_insertion_mode(hubbub_treebuilder * treebuilder)921 void reset_insertion_mode(hubbub_treebuilder *treebuilder)
922 {
923 uint32_t node;
924 element_context *stack = treebuilder->context.element_stack;
925
926 /** \todo fragment parsing algorithm */
927
928 for (node = treebuilder->context.current_node; node > 0; node--) {
929 if (stack[node].ns != HUBBUB_NS_HTML) {
930 treebuilder->context.mode = IN_FOREIGN_CONTENT;
931 treebuilder->context.second_mode = IN_BODY;
932 break;
933 }
934
935 switch (stack[node].type) {
936 case SELECT:
937 /* fragment case */
938 break;
939 case TD:
940 case TH:
941 treebuilder->context.mode = IN_CELL;
942 return;
943 case TR:
944 treebuilder->context.mode = IN_ROW;
945 return;
946 case TBODY:
947 case TFOOT:
948 case THEAD:
949 treebuilder->context.mode = IN_TABLE_BODY;
950 return;
951 case CAPTION:
952 treebuilder->context.mode = IN_CAPTION;
953 return;
954 case COLGROUP:
955 /* fragment case */
956 break;
957 case TABLE:
958 treebuilder->context.mode = IN_TABLE;
959 return;
960 case HEAD:
961 /* fragment case */
962 break;
963 case BODY:
964 treebuilder->context.mode = IN_BODY;
965 return;
966 case FRAMESET:
967 /* fragment case */
968 break;
969 case HTML:
970 /* fragment case */
971 break;
972 default:
973 break;
974 }
975 }
976 }
977
978 /**
979 * Script processing and execution
980 *
981 * \param treebuilder The treebuilder instance
982 * \return HUBBUB_OK on success, appropriate error otherwise
983 */
complete_script(hubbub_treebuilder * treebuilder)984 hubbub_error complete_script(hubbub_treebuilder *treebuilder)
985 {
986 hubbub_error error = HUBBUB_OK;
987 error = treebuilder->tree_handler->complete_script(
988 treebuilder->tree_handler->ctx,
989 treebuilder->context.element_stack[
990 treebuilder->context.current_node].node);
991 return error;
992 }
993
994 /**
995 * Append text to the current node, inserting into the last child of the
996 * current node, iff it's a Text node.
997 *
998 * \param treebuilder The treebuilder instance
999 * \param string The string to append
1000 * \return HUBBUB_OK on success, appropriate error otherwise
1001 */
append_text(hubbub_treebuilder * treebuilder,const hubbub_string * string)1002 hubbub_error append_text(hubbub_treebuilder *treebuilder,
1003 const hubbub_string *string)
1004 {
1005 element_type type = current_node(treebuilder);
1006 hubbub_error error = HUBBUB_OK;
1007 void *text, *appended;
1008
1009 error = treebuilder->tree_handler->create_text(
1010 treebuilder->tree_handler->ctx, string, &text);
1011 if (error != HUBBUB_OK)
1012 return error;
1013
1014 if (treebuilder->context.in_table_foster &&
1015 (type == TABLE || type == TBODY || type == TFOOT ||
1016 type == THEAD || type == TR)) {
1017 error = aa_insert_into_foster_parent(treebuilder, text,
1018 &appended);
1019 } else {
1020 error = treebuilder->tree_handler->append_child(
1021 treebuilder->tree_handler->ctx,
1022 treebuilder->context.element_stack[
1023 treebuilder->context.current_node].node,
1024 text, &appended);
1025 }
1026
1027 if (error == HUBBUB_OK) {
1028 treebuilder->tree_handler->unref_node(
1029 treebuilder->tree_handler->ctx, appended);
1030 }
1031
1032 treebuilder->tree_handler->unref_node(
1033 treebuilder->tree_handler->ctx, text);
1034
1035 return error;
1036 }
1037
1038 /**
1039 * Convert an element name into an element type
1040 *
1041 * \param treebuilder The treebuilder instance
1042 * \param tag_name The tag name to consider
1043 * \return The corresponding element type
1044 */
element_type_from_name(hubbub_treebuilder * treebuilder,const hubbub_string * tag_name)1045 element_type element_type_from_name(hubbub_treebuilder *treebuilder,
1046 const hubbub_string *tag_name)
1047 {
1048 const uint8_t *name = tag_name->ptr;
1049 size_t len = tag_name->len;
1050 uint32_t i;
1051
1052 UNUSED(treebuilder);
1053
1054 /** \todo optimise this */
1055
1056 for (i = 0; i < N_ELEMENTS(name_type_map); i++) {
1057 if (name_type_map[i].len != len)
1058 continue;
1059
1060 if (strncasecmp(name_type_map[i].name,
1061 (const char *) name, len) == 0)
1062 return name_type_map[i].type;
1063 }
1064
1065 return UNKNOWN;
1066 }
1067
1068 /**
1069 * Determine if a node is a special element
1070 *
1071 * \param type Node type to consider
1072 * \return True iff node is a special element
1073 */
is_special_element(element_type type)1074 bool is_special_element(element_type type)
1075 {
1076 return (type <= WBR);
1077 }
1078
1079 /**
1080 * Determine if a node is a scoping element
1081 *
1082 * \param type Node type to consider
1083 * \return True iff node is a scoping element
1084 */
is_scoping_element(element_type type)1085 bool is_scoping_element(element_type type)
1086 {
1087 return (type >= APPLET && type <= TH);
1088 }
1089
1090 /**
1091 * Determine if a node is a formatting element
1092 *
1093 * \param type Node type to consider
1094 * \return True iff node is a formatting element
1095 */
is_formatting_element(element_type type)1096 bool is_formatting_element(element_type type)
1097 {
1098 return (type >= A && type <= U);
1099 }
1100
1101 /**
1102 * Determine if a node is a phrasing element
1103 *
1104 * \param type Node type to consider
1105 * \return True iff node is a phrasing element
1106 */
is_phrasing_element(element_type type)1107 bool is_phrasing_element(element_type type)
1108 {
1109 return (type > U);
1110 }
1111
1112 /**
1113 * Determine if a node is form associated
1114 *
1115 * \param type Node type to consider
1116 * \return True iff node is form associated
1117 */
is_form_associated(element_type type)1118 bool is_form_associated(element_type type)
1119 {
1120 return type == FIELDSET || type == LABEL || type == INPUT ||
1121 type == BUTTON || type == SELECT || type == TEXTAREA ||
1122 type == OUTPUT;
1123 }
1124
1125 /**
1126 * Push an element onto the stack of open elements
1127 *
1128 * \param treebuilder The treebuilder instance containing the stack
1129 * \param ns The namespace of element being pushed
1130 * \param type The type of element being pushed
1131 * \param node The node to push
1132 * \return HUBBUB_OK on success, appropriate error otherwise.
1133 */
element_stack_push(hubbub_treebuilder * treebuilder,hubbub_ns ns,element_type type,void * node)1134 hubbub_error element_stack_push(hubbub_treebuilder *treebuilder,
1135 hubbub_ns ns, element_type type, void *node)
1136 {
1137 uint32_t slot = treebuilder->context.current_node + 1;
1138
1139 if (slot >= treebuilder->context.stack_alloc) {
1140 element_context *temp = realloc(
1141 treebuilder->context.element_stack,
1142 (treebuilder->context.stack_alloc +
1143 ELEMENT_STACK_CHUNK) *
1144 sizeof(element_context));
1145
1146 if (temp == NULL)
1147 return HUBBUB_NOMEM;
1148
1149 treebuilder->context.element_stack = temp;
1150 treebuilder->context.stack_alloc += ELEMENT_STACK_CHUNK;
1151 }
1152
1153 treebuilder->context.element_stack[slot].ns = ns;
1154 treebuilder->context.element_stack[slot].type = type;
1155 treebuilder->context.element_stack[slot].node = node;
1156
1157 treebuilder->context.current_node = slot;
1158
1159 return HUBBUB_OK;
1160 }
1161
1162 /**
1163 * Pop an element off the stack of open elements
1164 *
1165 * \param treebuilder The treebuilder instance containing the stack
1166 * \param ns Pointer to location to receive element namespace
1167 * \param type Pointer to location to receive element type
1168 * \param node Pointer to location to receive node
1169 * \return HUBBUB_OK on success, appropriate error otherwise.
1170 */
element_stack_pop(hubbub_treebuilder * treebuilder,hubbub_ns * ns,element_type * type,void ** node)1171 hubbub_error element_stack_pop(hubbub_treebuilder *treebuilder,
1172 hubbub_ns *ns, element_type *type, void **node)
1173 {
1174 element_context *stack = treebuilder->context.element_stack;
1175 uint32_t slot = treebuilder->context.current_node;
1176 formatting_list_entry *entry;
1177
1178 /* We're popping a table, find previous */
1179 if (stack[slot].type == TABLE) {
1180 uint32_t t;
1181 for (t = slot - 1; t > 0; t--) {
1182 if (stack[t].type == TABLE)
1183 break;
1184 }
1185 }
1186
1187 if (is_formatting_element(stack[slot].type) ||
1188 (is_scoping_element(stack[slot].type) &&
1189 stack[slot].type != HTML &&
1190 stack[slot].type != TABLE)) {
1191 /* Find occurrences of the node we're about to pop in the list
1192 * of active formatting elements. We need to invalidate their
1193 * stack index information. */
1194 for (entry = treebuilder->context.formatting_list_end;
1195 entry != NULL; entry = entry->prev) {
1196 /** \todo Can we optimise this?
1197 * (i.e. by not traversing the entire list) */
1198 if (entry->stack_index == slot)
1199 entry->stack_index = 0;
1200 }
1201 }
1202
1203 *ns = stack[slot].ns;
1204 *type = stack[slot].type;
1205 *node = stack[slot].node;
1206
1207 /** \todo reduce allocated stack size once there's enough free */
1208
1209 treebuilder->context.current_node = slot - 1;
1210 assert((signed) treebuilder->context.current_node >= 0);
1211
1212 return HUBBUB_OK;
1213 }
1214
1215 /**
1216 * Pop elements until an element of type "element" has been popped.
1217 *
1218 * \return HUBBUB_OK on success, appropriate error otherwise.
1219 */
element_stack_pop_until(hubbub_treebuilder * treebuilder,element_type type)1220 hubbub_error element_stack_pop_until(hubbub_treebuilder *treebuilder,
1221 element_type type)
1222 {
1223 element_type otype = UNKNOWN;
1224 void *node;
1225 hubbub_ns ns;
1226
1227 while (otype != type) {
1228 element_stack_pop(treebuilder, &ns, &otype, &node);
1229
1230 treebuilder->tree_handler->unref_node(
1231 treebuilder->tree_handler->ctx, node);
1232
1233 assert((signed) treebuilder->context.current_node >= 0);
1234 }
1235
1236 return HUBBUB_OK;
1237 }
1238
1239 /**
1240 * Remove a node from the stack of open elements
1241 *
1242 * \param treebuilder The treebuilder instance
1243 * \param index The index of the node to remove
1244 * \param ns Pointer to location to receive namespace
1245 * \param type Pointer to location to receive type
1246 * \param removed Pointer to location to receive removed node
1247 * \return HUBBUB_OK on success, appropriate error otherwise.
1248 */
element_stack_remove(hubbub_treebuilder * treebuilder,uint32_t index,hubbub_ns * ns,element_type * type,void ** removed)1249 hubbub_error element_stack_remove(hubbub_treebuilder *treebuilder,
1250 uint32_t index, hubbub_ns *ns, element_type *type,
1251 void **removed)
1252 {
1253 element_context *stack = treebuilder->context.element_stack;
1254 uint32_t n;
1255
1256 assert(index <= treebuilder->context.current_node);
1257
1258 /* Scan over subsequent entries in the stack,
1259 * searching for them in the list of active formatting
1260 * entries. If found, update the corresponding
1261 * formatting list entry's stack index to match the
1262 * new stack location */
1263 for (n = index + 1; n <= treebuilder->context.current_node; n++) {
1264 if (is_formatting_element(stack[n].type) ||
1265 (is_scoping_element(stack[n].type) &&
1266 stack[n].type != HTML &&
1267 stack[n].type != TABLE)) {
1268 formatting_list_entry *e;
1269
1270 for (e = treebuilder->context.formatting_list_end;
1271 e != NULL; e = e->prev) {
1272 if (e->stack_index == n)
1273 e->stack_index--;
1274 }
1275 }
1276 }
1277
1278 *ns = stack[index].ns;
1279 *type = stack[index].type;
1280 *removed = stack[index].node;
1281
1282 /* Now, shuffle the stack up one, removing node in the process */
1283 if (index < treebuilder->context.current_node) {
1284 memmove(&stack[index], &stack[index + 1],
1285 (treebuilder->context.current_node - index) *
1286 sizeof(element_context));
1287 }
1288
1289 treebuilder->context.current_node--;
1290
1291 return HUBBUB_OK;
1292 }
1293
1294 /**
1295 * Find the stack index of the current table.
1296 */
current_table(hubbub_treebuilder * treebuilder)1297 uint32_t current_table(hubbub_treebuilder *treebuilder)
1298 {
1299 element_context *stack = treebuilder->context.element_stack;
1300 size_t t;
1301
1302 for (t = treebuilder->context.current_node; t != 0; t--) {
1303 if (stack[t].type == TABLE)
1304 return t;
1305 }
1306
1307 /* fragment case */
1308 return 0;
1309 }
1310
1311 /**
1312 * Peek at the top element of the element stack.
1313 *
1314 * \param treebuilder Treebuilder instance
1315 * \return Element type on the top of the stack
1316 */
current_node(hubbub_treebuilder * treebuilder)1317 element_type current_node(hubbub_treebuilder *treebuilder)
1318 {
1319 return treebuilder->context.element_stack
1320 [treebuilder->context.current_node].type;
1321 }
1322
1323 /**
1324 * Peek at the element below the top of the element stack.
1325 *
1326 * \param treebuilder Treebuilder instance
1327 * \return Element type of the element one below the top of the stack
1328 */
prev_node(hubbub_treebuilder * treebuilder)1329 element_type prev_node(hubbub_treebuilder *treebuilder)
1330 {
1331 if (treebuilder->context.current_node == 0)
1332 return UNKNOWN;
1333
1334 return treebuilder->context.element_stack
1335 [treebuilder->context.current_node - 1].type;
1336 }
1337
1338
1339
1340 /**
1341 * Append an element to the end of the list of active formatting elements
1342 *
1343 * \param treebuilder Treebuilder instance containing list
1344 * \param ns Namespace of node being inserted
1345 * \param type Type of node being inserted
1346 * \param node Node being inserted
1347 * \param stack_index Index into stack of open elements
1348 * \return HUBBUB_OK on success, appropriate error otherwise
1349 */
formatting_list_append(hubbub_treebuilder * treebuilder,hubbub_ns ns,element_type type,void * node,uint32_t stack_index)1350 hubbub_error formatting_list_append(hubbub_treebuilder *treebuilder,
1351 hubbub_ns ns, element_type type, void *node,
1352 uint32_t stack_index)
1353 {
1354 formatting_list_entry *entry;
1355
1356 entry = malloc(sizeof(formatting_list_entry));
1357 if (entry == NULL)
1358 return HUBBUB_NOMEM;
1359
1360 entry->details.ns = ns;
1361 entry->details.type = type;
1362 entry->details.node = node;
1363 entry->stack_index = stack_index;
1364
1365 entry->prev = treebuilder->context.formatting_list_end;
1366 entry->next = NULL;
1367
1368 if (entry->prev != NULL)
1369 entry->prev->next = entry;
1370 else
1371 treebuilder->context.formatting_list = entry;
1372
1373 treebuilder->context.formatting_list_end = entry;
1374
1375 return HUBBUB_OK;
1376 }
1377
1378 /**
1379 * Insert an element into the list of active formatting elements
1380 *
1381 * \param treebuilder Treebuilder instance containing list
1382 * \param prev Previous entry
1383 * \param next Next entry
1384 * \param ns Namespace of node being inserted
1385 * \param type Type of node being inserted
1386 * \param node Node being inserted
1387 * \param stack_index Index into stack of open elements
1388 * \return HUBBUB_OK on success, appropriate error otherwise
1389 */
formatting_list_insert(hubbub_treebuilder * treebuilder,formatting_list_entry * prev,formatting_list_entry * next,hubbub_ns ns,element_type type,void * node,uint32_t stack_index)1390 hubbub_error formatting_list_insert(hubbub_treebuilder *treebuilder,
1391 formatting_list_entry *prev, formatting_list_entry *next,
1392 hubbub_ns ns, element_type type, void *node,
1393 uint32_t stack_index)
1394 {
1395 formatting_list_entry *entry;
1396
1397 if (prev != NULL) {
1398 assert(prev->next == next);
1399 }
1400
1401 if (next != NULL) {
1402 assert(next->prev == prev);
1403 }
1404
1405 entry = malloc(sizeof(formatting_list_entry));
1406 if (entry == NULL)
1407 return HUBBUB_NOMEM;
1408
1409 entry->details.ns = ns;
1410 entry->details.type = type;
1411 entry->details.node = node;
1412 entry->stack_index = stack_index;
1413
1414 entry->prev = prev;
1415 entry->next = next;
1416
1417 if (entry->prev != NULL)
1418 entry->prev->next = entry;
1419 else
1420 treebuilder->context.formatting_list = entry;
1421
1422 if (entry->next != NULL)
1423 entry->next->prev = entry;
1424 else
1425 treebuilder->context.formatting_list_end = entry;
1426
1427 return HUBBUB_OK;
1428 }
1429
1430
1431 /**
1432 * Remove an element from the list of active formatting elements
1433 *
1434 * \param treebuilder Treebuilder instance containing list
1435 * \param entry The item to remove
1436 * \param ns Pointer to location to receive namespace of node
1437 * \param type Pointer to location to receive type of node
1438 * \param node Pointer to location to receive node
1439 * \param stack_index Pointer to location to receive stack index
1440 * \return HUBBUB_OK on success, appropriate error otherwise.
1441 */
formatting_list_remove(hubbub_treebuilder * treebuilder,formatting_list_entry * entry,hubbub_ns * ns,element_type * type,void ** node,uint32_t * stack_index)1442 hubbub_error formatting_list_remove(hubbub_treebuilder *treebuilder,
1443 formatting_list_entry *entry,
1444 hubbub_ns *ns, element_type *type, void **node,
1445 uint32_t *stack_index)
1446 {
1447 *ns = entry->details.ns;
1448 *type = entry->details.type;
1449 *node = entry->details.node;
1450 *stack_index = entry->stack_index;
1451
1452 if (entry->prev == NULL) {
1453 assert(treebuilder->context.formatting_list == entry);
1454 treebuilder->context.formatting_list = entry->next;
1455 } else {
1456 assert(treebuilder->context.formatting_list != entry);
1457 entry->prev->next = entry->next;
1458 }
1459
1460 if (entry->next == NULL) {
1461 assert(treebuilder->context.formatting_list_end == entry);
1462 treebuilder->context.formatting_list_end = entry->prev;
1463 } else {
1464 assert(treebuilder->context.formatting_list_end != entry);
1465 entry->next->prev = entry->prev;
1466 }
1467
1468 free(entry);
1469
1470 return HUBBUB_OK;
1471 }
1472
1473 /**
1474 * Remove an element from the list of active formatting elements
1475 *
1476 * \param treebuilder Treebuilder instance containing list
1477 * \param entry The item to replace
1478 * \param ns Replacement node namespace
1479 * \param type Replacement node type
1480 * \param node Replacement node
1481 * \param stack_index Replacement stack index
1482 * \param ons Pointer to location to receive old namespace
1483 * \param otype Pointer to location to receive old type
1484 * \param onode Pointer to location to receive old node
1485 * \param ostack_index Pointer to location to receive old stack index
1486 * \return HUBBUB_OK on success, appropriate error otherwise
1487 */
formatting_list_replace(hubbub_treebuilder * treebuilder,formatting_list_entry * entry,hubbub_ns ns,element_type type,void * node,uint32_t stack_index,hubbub_ns * ons,element_type * otype,void ** onode,uint32_t * ostack_index)1488 hubbub_error formatting_list_replace(hubbub_treebuilder *treebuilder,
1489 formatting_list_entry *entry,
1490 hubbub_ns ns, element_type type, void *node,
1491 uint32_t stack_index,
1492 hubbub_ns *ons, element_type *otype, void **onode,
1493 uint32_t *ostack_index)
1494 {
1495 UNUSED(treebuilder);
1496
1497 *ons = entry->details.ns;
1498 *otype = entry->details.type;
1499 *onode = entry->details.node;
1500 *ostack_index = entry->stack_index;
1501
1502 entry->details.ns = ns;
1503 entry->details.type = type;
1504 entry->details.node = node;
1505 entry->stack_index = stack_index;
1506
1507 return HUBBUB_OK;
1508 }
1509
1510
1511
1512 #ifndef NDEBUG
1513
1514 /**
1515 * Dump an element stack to the given file pointer
1516 *
1517 * \param treebuilder The treebuilder instance
1518 * \param fp The file to dump to
1519 */
element_stack_dump(hubbub_treebuilder * treebuilder,FILE * fp)1520 void element_stack_dump(hubbub_treebuilder *treebuilder, FILE *fp)
1521 {
1522 element_context *stack = treebuilder->context.element_stack;
1523 uint32_t i;
1524
1525 for (i = 0; i <= treebuilder->context.current_node; i++) {
1526 fprintf(fp, "%u: %s %p\n",
1527 i,
1528 element_type_to_name(stack[i].type),
1529 stack[i].node);
1530 }
1531 }
1532
1533 /**
1534 * Dump a formatting list to the given file pointer
1535 *
1536 * \param treebuilder The treebuilder instance
1537 * \param fp The file to dump to
1538 */
formatting_list_dump(hubbub_treebuilder * treebuilder,FILE * fp)1539 void formatting_list_dump(hubbub_treebuilder *treebuilder, FILE *fp)
1540 {
1541 formatting_list_entry *entry;
1542
1543 for (entry = treebuilder->context.formatting_list; entry != NULL;
1544 entry = entry->next) {
1545 fprintf(fp, "%s %p %u\n",
1546 element_type_to_name(entry->details.type),
1547 entry->details.node, entry->stack_index);
1548 }
1549 }
1550
1551 /**
1552 * Convert an element type to a name
1553 *
1554 * \param type The element type
1555 * \return Pointer to name
1556 */
element_type_to_name(element_type type)1557 const char *element_type_to_name(element_type type)
1558 {
1559 size_t i;
1560
1561 for (i = 0;
1562 i < sizeof(name_type_map) / sizeof(name_type_map[0]);
1563 i++) {
1564 if (name_type_map[i].type == type)
1565 return name_type_map[i].name;
1566 }
1567
1568 return "UNKNOWN";
1569 }
1570 #endif
1571
1572