Coverage Report

Created: 2026-10-13 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/thirdparty/gumbo-parser/src/parser.c
Line
Count
Source
1
// Copyright 2010 Google Inc. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
//
15
// Author: jdtang@google.com (Jonathan Tang)
16
17
#include <assert.h>
18
#include <ctype.h>
19
#include <stdarg.h>
20
#include <stdlib.h>
21
#include <string.h>
22
#include <strings.h>
23
24
#include "attribute.h"
25
#include "error.h"
26
#include "gumbo.h"
27
#include "insertion_mode.h"
28
#include "parser.h"
29
#include "tokenizer.h"
30
#include "tokenizer_states.h"
31
#include "utf8.h"
32
#include "util.h"
33
#include "vector.h"
34
35
0
#define AVOID_UNUSED_VARIABLE_WARNING(i) (void)(i)
36
37
#define GUMBO_STRING(literal) \
38
  { literal, sizeof(literal) - 1 }
39
#define TERMINATOR \
40
  { "", 0 }
41
42
typedef char gumbo_tagset[GUMBO_TAG_LAST];
43
0
#define TAG(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_HTML)
44
0
#define TAG_SVG(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_SVG)
45
0
#define TAG_MATHML(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_MATHML)
46
47
#define TAGSET_INCLUDES(tagset, namespace, tag) \
48
0
  (tag < GUMBO_TAG_LAST && tagset[(int) tag] & (1 << (int) namespace))
49
50
#define SCOPE_TAGS            \
51
0
  TAG(APPLET),                \
52
0
  TAG(CAPTION),               \
53
0
  TAG(HTML),                  \
54
0
  TAG(TABLE),                 \
55
0
  TAG(TD),                    \
56
0
  TAG(TH),                    \
57
0
  TAG(MARQUEE),               \
58
0
  TAG(OBJECT),                \
59
0
  TAG(TEMPLATE),              \
60
0
  TAG_MATHML(MI),             \
61
0
  TAG_MATHML(MO),             \
62
0
  TAG_MATHML(MN),             \
63
0
  TAG_MATHML(MS),             \
64
0
  TAG_MATHML(MTEXT),          \
65
0
  TAG_MATHML(ANNOTATION_XML), \
66
0
  TAG_SVG(FOREIGNOBJECT),     \
67
0
  TAG_SVG(DESC),              \
68
0
  TAG_SVG(TITLE)
69
70
#define HEAD_TAGS \
71
0
  TAG(BASE),      \
72
0
  TAG(BASEFONT),  \
73
0
  TAG(BGSOUND),   \
74
0
  TAG(LINK),      \
75
0
  TAG(META),      \
76
0
  TAG(NOFRAMES),  \
77
0
  TAG(SCRIPT),    \
78
0
  TAG(STYLE),     \
79
0
  TAG(TEMPLATE),  \
80
0
  TAG(TITLE)
81
82
typedef enum {
83
  GUMBO_SELECTEDCONTENT_EMPTY,
84
  GUMBO_SELECTEDCONTENT_AUTOMATIC, // determined by first instance of option
85
  GUMBO_SELECTEDCONTENT_FORCED,    // determined by option with selected attribute
86
} GumboSelectedcontentState;
87
88
// selected forward declarations as it is getting hard to find
89
// an appropriate order
90
static bool node_html_tag_is(const GumboNode*, GumboTag);
91
static GumboInsertionMode get_current_template_insertion_mode(
92
    const GumboParser*);
93
static bool handle_in_template(GumboParser*, GumboToken*);
94
static void destroy_node(GumboParser*, GumboNode*);
95
96
0
static void* malloc_wrapper(void* unused, size_t size) { return malloc(size); }
97
98
0
static void free_wrapper(void* unused, void* ptr) { free(ptr); }
99
100
const GumboOptions kGumboDefaultOptions = {&malloc_wrapper, &free_wrapper, NULL,
101
    8, false, -1, GUMBO_TAG_LAST, GUMBO_NAMESPACE_HTML};
102
103
static const GumboStringPiece kDoctypeHtml = GUMBO_STRING("html");
104
static const GumboStringPiece kPublicIdHtml4_0 =
105
    GUMBO_STRING("-//W3C//DTD HTML 4.0//EN");
106
static const GumboStringPiece kPublicIdHtml4_01 =
107
    GUMBO_STRING("-//W3C//DTD HTML 4.01//EN");
108
static const GumboStringPiece kPublicIdXhtml1_0 =
109
    GUMBO_STRING("-//W3C//DTD XHTML 1.0 Strict//EN");
110
static const GumboStringPiece kPublicIdXhtml1_1 =
111
    GUMBO_STRING("-//W3C//DTD XHTML 1.1//EN");
112
static const GumboStringPiece kSystemIdRecHtml4_0 =
113
    GUMBO_STRING("http://www.w3.org/TR/REC-html40/strict.dtd");
114
static const GumboStringPiece kSystemIdHtml4 =
115
    GUMBO_STRING("http://www.w3.org/TR/html4/strict.dtd");
116
static const GumboStringPiece kSystemIdXhtmlStrict1_1 =
117
    GUMBO_STRING("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
118
static const GumboStringPiece kSystemIdXhtml1_1 =
119
    GUMBO_STRING("http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd");
120
static const GumboStringPiece kSystemIdLegacyCompat =
121
    GUMBO_STRING("about:legacy-compat");
122
123
// The doctype arrays have an explicit terminator because we want to pass them
124
// to a helper function, and passing them as a pointer discards sizeof
125
// information.  The SVG arrays are used only by one-off functions, and so loops
126
// over them use sizeof directly instead of a terminator.
127
128
static const GumboStringPiece kQuirksModePublicIdPrefixes[] = {
129
    GUMBO_STRING("+//Silmaril//dtd html Pro v0r11 19970101//"),
130
    GUMBO_STRING("-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//"),
131
    GUMBO_STRING("-//AS//DTD HTML 3.0 asWedit + extensions//"),
132
    GUMBO_STRING("-//IETF//DTD HTML 2.0 Level 1//"),
133
    GUMBO_STRING("-//IETF//DTD HTML 2.0 Level 2//"),
134
    GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict Level 1//"),
135
    GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict Level 2//"),
136
    GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict//"),
137
    GUMBO_STRING("-//IETF//DTD HTML 2.0//"),
138
    GUMBO_STRING("-//IETF//DTD HTML 2.1E//"),
139
    GUMBO_STRING("-//IETF//DTD HTML 3.0//"),
140
    GUMBO_STRING("-//IETF//DTD HTML 3.2 Final//"),
141
    GUMBO_STRING("-//IETF//DTD HTML 3.2//"),
142
    GUMBO_STRING("-//IETF//DTD HTML 3//"),
143
    GUMBO_STRING("-//IETF//DTD HTML Level 0//"),
144
    GUMBO_STRING("-//IETF//DTD HTML Level 1//"),
145
    GUMBO_STRING("-//IETF//DTD HTML Level 2//"),
146
    GUMBO_STRING("-//IETF//DTD HTML Level 3//"),
147
    GUMBO_STRING("-//IETF//DTD HTML Strict Level 0//"),
148
    GUMBO_STRING("-//IETF//DTD HTML Strict Level 1//"),
149
    GUMBO_STRING("-//IETF//DTD HTML Strict Level 2//"),
150
    GUMBO_STRING("-//IETF//DTD HTML Strict Level 3//"),
151
    GUMBO_STRING("-//IETF//DTD HTML Strict//"),
152
    GUMBO_STRING("-//IETF//DTD HTML//"),
153
    GUMBO_STRING("-//Metrius//DTD Metrius Presentational//"),
154
    GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//"),
155
    GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 HTML//"),
156
    GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 Tables//"),
157
    GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//"),
158
    GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 HTML//"),
159
    GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 Tables//"),
160
    GUMBO_STRING("-//Netscape Comm. Corp.//DTD HTML//"),
161
    GUMBO_STRING("-//Netscape Comm. Corp.//DTD Strict HTML//"),
162
    GUMBO_STRING("-//O'Reilly and Associates//DTD HTML 2.0//"),
163
    GUMBO_STRING("-//O'Reilly and Associates//DTD HTML Extended 1.0//"),
164
    GUMBO_STRING("-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//"),
165
    GUMBO_STRING(
166
        "-//SoftQuad Software//DTD HoTMetaL PRO 6.0::19990601::)"
167
        "extensions to HTML 4.0//"),
168
    GUMBO_STRING(
169
        "-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::"
170
        "extensions to HTML 4.0//"),
171
    GUMBO_STRING("-//Spyglass//DTD HTML 2.0 Extended//"),
172
    GUMBO_STRING("-//SQ//DTD HTML 2.0 HoTMetaL + extensions//"),
173
    GUMBO_STRING("-//Sun Microsystems Corp.//DTD HotJava HTML//"),
174
    GUMBO_STRING("-//Sun Microsystems Corp.//DTD HotJava Strict HTML//"),
175
    GUMBO_STRING("-//W3C//DTD HTML 3 1995-03-24//"),
176
    GUMBO_STRING("-//W3C//DTD HTML 3.2 Draft//"),
177
    GUMBO_STRING("-//W3C//DTD HTML 3.2 Final//"),
178
    GUMBO_STRING("-//W3C//DTD HTML 3.2//"),
179
    GUMBO_STRING("-//W3C//DTD HTML 3.2S Draft//"),
180
    GUMBO_STRING("-//W3C//DTD HTML 4.0 Frameset//"),
181
    GUMBO_STRING("-//W3C//DTD HTML 4.0 Transitional//"),
182
    GUMBO_STRING("-//W3C//DTD HTML Experimental 19960712//"),
183
    GUMBO_STRING("-//W3C//DTD HTML Experimental 970421//"),
184
    GUMBO_STRING("-//W3C//DTD W3 HTML//"),
185
    GUMBO_STRING("-//W3O//DTD W3 HTML 3.0//"),
186
    GUMBO_STRING("-//WebTechs//DTD Mozilla HTML 2.0//"),
187
    GUMBO_STRING("-//WebTechs//DTD Mozilla HTML//"), TERMINATOR};
188
189
static const GumboStringPiece kQuirksModePublicIdExactMatches[] = {
190
    GUMBO_STRING("-//W3O//DTD W3 HTML Strict 3.0//EN//"),
191
    GUMBO_STRING("-/W3C/DTD HTML 4.0 Transitional/EN"), GUMBO_STRING("HTML"),
192
    TERMINATOR};
193
194
static const GumboStringPiece kQuirksModeSystemIdExactMatches[] = {
195
    GUMBO_STRING("http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"),
196
    TERMINATOR};
197
198
static const GumboStringPiece kLimitedQuirksPublicIdPrefixes[] = {
199
    GUMBO_STRING("-//W3C//DTD XHTML 1.0 Frameset//"),
200
    GUMBO_STRING("-//W3C//DTD XHTML 1.0 Transitional//"), TERMINATOR};
201
202
static const GumboStringPiece kLimitedQuirksRequiresSystemIdPublicIdPrefixes[] =
203
    {GUMBO_STRING("-//W3C//DTD HTML 4.01 Frameset//"),
204
        GUMBO_STRING("-//W3C//DTD HTML 4.01 Transitional//"), TERMINATOR};
205
206
// Indexed by GumboNamespaceEnum; keep in sync with that.
207
static const char* kLegalXmlns[] = {"http://www.w3.org/1999/xhtml",
208
    "http://www.w3.org/2000/svg", "http://www.w3.org/1998/Math/MathML"};
209
210
typedef struct _ReplacementEntry {
211
  const GumboStringPiece from;
212
  const GumboStringPiece to;
213
} ReplacementEntry;
214
215
#define REPLACEMENT_ENTRY(from, to) \
216
  { GUMBO_STRING(from), GUMBO_STRING(to) }
217
218
// Static data for SVG attribute replacements.
219
// https://html.spec.whatwg.org/multipage/syntax.html#creating-and-inserting-nodes
220
static const ReplacementEntry kSvgAttributeReplacements[] = {
221
    REPLACEMENT_ENTRY("attributename", "attributeName"),
222
    REPLACEMENT_ENTRY("attributetype", "attributeType"),
223
    REPLACEMENT_ENTRY("basefrequency", "baseFrequency"),
224
    REPLACEMENT_ENTRY("baseprofile", "baseProfile"),
225
    REPLACEMENT_ENTRY("calcmode", "calcMode"),
226
    REPLACEMENT_ENTRY("clippathunits", "clipPathUnits"),
227
    // REPLACEMENT_ENTRY("contentscripttype", "contentScriptType"),
228
    // REPLACEMENT_ENTRY("contentstyletype", "contentStyleType"),
229
    REPLACEMENT_ENTRY("diffuseconstant", "diffuseConstant"),
230
    REPLACEMENT_ENTRY("edgemode", "edgeMode"),
231
    // REPLACEMENT_ENTRY("externalresourcesrequired",
232
    // "externalResourcesRequired"),
233
    // REPLACEMENT_ENTRY("filterres", "filterRes"),
234
    REPLACEMENT_ENTRY("filterunits", "filterUnits"),
235
    REPLACEMENT_ENTRY("glyphref", "glyphRef"),
236
    REPLACEMENT_ENTRY("gradienttransform", "gradientTransform"),
237
    REPLACEMENT_ENTRY("gradientunits", "gradientUnits"),
238
    REPLACEMENT_ENTRY("kernelmatrix", "kernelMatrix"),
239
    REPLACEMENT_ENTRY("kernelunitlength", "kernelUnitLength"),
240
    REPLACEMENT_ENTRY("keypoints", "keyPoints"),
241
    REPLACEMENT_ENTRY("keysplines", "keySplines"),
242
    REPLACEMENT_ENTRY("keytimes", "keyTimes"),
243
    REPLACEMENT_ENTRY("lengthadjust", "lengthAdjust"),
244
    REPLACEMENT_ENTRY("limitingconeangle", "limitingConeAngle"),
245
    REPLACEMENT_ENTRY("markerheight", "markerHeight"),
246
    REPLACEMENT_ENTRY("markerunits", "markerUnits"),
247
    REPLACEMENT_ENTRY("markerwidth", "markerWidth"),
248
    REPLACEMENT_ENTRY("maskcontentunits", "maskContentUnits"),
249
    REPLACEMENT_ENTRY("maskunits", "maskUnits"),
250
    REPLACEMENT_ENTRY("numoctaves", "numOctaves"),
251
    REPLACEMENT_ENTRY("pathlength", "pathLength"),
252
    REPLACEMENT_ENTRY("patterncontentunits", "patternContentUnits"),
253
    REPLACEMENT_ENTRY("patterntransform", "patternTransform"),
254
    REPLACEMENT_ENTRY("patternunits", "patternUnits"),
255
    REPLACEMENT_ENTRY("pointsatx", "pointsAtX"),
256
    REPLACEMENT_ENTRY("pointsaty", "pointsAtY"),
257
    REPLACEMENT_ENTRY("pointsatz", "pointsAtZ"),
258
    REPLACEMENT_ENTRY("preservealpha", "preserveAlpha"),
259
    REPLACEMENT_ENTRY("preserveaspectratio", "preserveAspectRatio"),
260
    REPLACEMENT_ENTRY("primitiveunits", "primitiveUnits"),
261
    REPLACEMENT_ENTRY("refx", "refX"), REPLACEMENT_ENTRY("refy", "refY"),
262
    REPLACEMENT_ENTRY("repeatcount", "repeatCount"),
263
    REPLACEMENT_ENTRY("repeatdur", "repeatDur"),
264
    REPLACEMENT_ENTRY("requiredextensions", "requiredExtensions"),
265
    REPLACEMENT_ENTRY("requiredfeatures", "requiredFeatures"),
266
    REPLACEMENT_ENTRY("specularconstant", "specularConstant"),
267
    REPLACEMENT_ENTRY("specularexponent", "specularExponent"),
268
    REPLACEMENT_ENTRY("spreadmethod", "spreadMethod"),
269
    REPLACEMENT_ENTRY("startoffset", "startOffset"),
270
    REPLACEMENT_ENTRY("stddeviation", "stdDeviation"),
271
    REPLACEMENT_ENTRY("stitchtiles", "stitchTiles"),
272
    REPLACEMENT_ENTRY("surfacescale", "surfaceScale"),
273
    REPLACEMENT_ENTRY("systemlanguage", "systemLanguage"),
274
    REPLACEMENT_ENTRY("tablevalues", "tableValues"),
275
    REPLACEMENT_ENTRY("targetx", "targetX"),
276
    REPLACEMENT_ENTRY("targety", "targetY"),
277
    REPLACEMENT_ENTRY("textlength", "textLength"),
278
    REPLACEMENT_ENTRY("viewbox", "viewBox"),
279
    REPLACEMENT_ENTRY("viewtarget", "viewTarget"),
280
    REPLACEMENT_ENTRY("xchannelselector", "xChannelSelector"),
281
    REPLACEMENT_ENTRY("ychannelselector", "yChannelSelector"),
282
    REPLACEMENT_ENTRY("zoomandpan", "zoomAndPan"),
283
};
284
285
static const ReplacementEntry kSvgTagReplacements[] = {
286
    REPLACEMENT_ENTRY("altglyph", "altGlyph"),
287
    REPLACEMENT_ENTRY("altglyphdef", "altGlyphDef"),
288
    REPLACEMENT_ENTRY("altglyphitem", "altGlyphItem"),
289
    REPLACEMENT_ENTRY("animatecolor", "animateColor"),
290
    REPLACEMENT_ENTRY("animatemotion", "animateMotion"),
291
    REPLACEMENT_ENTRY("animatetransform", "animateTransform"),
292
    REPLACEMENT_ENTRY("clippath", "clipPath"),
293
    REPLACEMENT_ENTRY("feblend", "feBlend"),
294
    REPLACEMENT_ENTRY("fecolormatrix", "feColorMatrix"),
295
    REPLACEMENT_ENTRY("fecomponenttransfer", "feComponentTransfer"),
296
    REPLACEMENT_ENTRY("fecomposite", "feComposite"),
297
    REPLACEMENT_ENTRY("feconvolvematrix", "feConvolveMatrix"),
298
    REPLACEMENT_ENTRY("fediffuselighting", "feDiffuseLighting"),
299
    REPLACEMENT_ENTRY("fedisplacementmap", "feDisplacementMap"),
300
    REPLACEMENT_ENTRY("fedistantlight", "feDistantLight"),
301
    REPLACEMENT_ENTRY("feflood", "feFlood"),
302
    REPLACEMENT_ENTRY("fefunca", "feFuncA"),
303
    REPLACEMENT_ENTRY("fefuncb", "feFuncB"),
304
    REPLACEMENT_ENTRY("fefuncg", "feFuncG"),
305
    REPLACEMENT_ENTRY("fefuncr", "feFuncR"),
306
    REPLACEMENT_ENTRY("fegaussianblur", "feGaussianBlur"),
307
    REPLACEMENT_ENTRY("feimage", "feImage"),
308
    REPLACEMENT_ENTRY("femerge", "feMerge"),
309
    REPLACEMENT_ENTRY("femergenode", "feMergeNode"),
310
    REPLACEMENT_ENTRY("femorphology", "feMorphology"),
311
    REPLACEMENT_ENTRY("feoffset", "feOffset"),
312
    REPLACEMENT_ENTRY("fepointlight", "fePointLight"),
313
    REPLACEMENT_ENTRY("fespecularlighting", "feSpecularLighting"),
314
    REPLACEMENT_ENTRY("fespotlight", "feSpotLight"),
315
    REPLACEMENT_ENTRY("fetile", "feTile"),
316
    REPLACEMENT_ENTRY("feturbulence", "feTurbulence"),
317
    REPLACEMENT_ENTRY("foreignobject", "foreignObject"),
318
    REPLACEMENT_ENTRY("glyphref", "glyphRef"),
319
    REPLACEMENT_ENTRY("lineargradient", "linearGradient"),
320
    REPLACEMENT_ENTRY("radialgradient", "radialGradient"),
321
    REPLACEMENT_ENTRY("textpath", "textPath"),
322
};
323
324
typedef struct _NamespacedAttributeReplacement {
325
  const char* from;
326
  const char* local_name;
327
  const GumboAttributeNamespaceEnum attr_namespace;
328
} NamespacedAttributeReplacement;
329
330
static const NamespacedAttributeReplacement kForeignAttributeReplacements[] = {
331
    {"xlink:actuate", "actuate", GUMBO_ATTR_NAMESPACE_XLINK},
332
    {"xlink:arcrole", "arcrole", GUMBO_ATTR_NAMESPACE_XLINK},
333
    {"xlink:href",    "href",    GUMBO_ATTR_NAMESPACE_XLINK},
334
    {"xlink:role",    "role",    GUMBO_ATTR_NAMESPACE_XLINK},
335
    {"xlink:show",    "show",    GUMBO_ATTR_NAMESPACE_XLINK},
336
    {"xlink:title",   "title",   GUMBO_ATTR_NAMESPACE_XLINK},
337
    {"xlink:type",    "type",    GUMBO_ATTR_NAMESPACE_XLINK},
338
    {"xml:lang",      "lang",    GUMBO_ATTR_NAMESPACE_XML},
339
    {"xml:space",     "space",   GUMBO_ATTR_NAMESPACE_XML},
340
    {"xmlns",         "xmlns",   GUMBO_ATTR_NAMESPACE_XMLNS},
341
    {"xmlns:xlink",   "xlink",   GUMBO_ATTR_NAMESPACE_XMLNS},
342
};
343
344
// The "scope marker" for the list of active formatting elements.  We use a
345
// pointer to this as a generic marker element, since the particular element
346
// scope doesn't matter.
347
static const GumboNode kActiveFormattingScopeMarker;
348
349
// The tag_is and tag_in function use true & false to denote start & end tags,
350
// but for readability, we define constants for them here.
351
static const bool kStartTag = true;
352
static const bool kEndTag = false;
353
354
// Because GumboStringPieces are immutable, we can't insert a character directly
355
// into a text node.  Instead, we accumulate all pending characters here and
356
// flush them out to a text node whenever a new element is inserted.
357
//
358
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#insert-a-character
359
typedef struct _TextNodeBufferState {
360
  // The accumulated text to be inserted into the current text node.
361
  GumboStringBuffer _buffer;
362
363
  // A pointer to the original text represented by this text node.  Note that
364
  // because of foster parenting and other strange DOM manipulations, this may
365
  // include other non-text HTML tags in it; it is defined as the span of
366
  // original text from the first character in this text node to the last
367
  // character in this text node.
368
  const char* _start_original_text;
369
370
  // The source position of the start of this text node.
371
  GumboSourcePosition _start_position;
372
373
  // The type of node that will be inserted (TEXT, CDATA, or WHITESPACE).
374
  GumboNodeType _type;
375
} TextNodeBufferState;
376
377
typedef struct GumboInternalParserState {
378
  // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#insertion-mode
379
  GumboInsertionMode _insertion_mode;
380
381
  // Used for run_generic_parsing_algorithm, which needs to switch back to the
382
  // original insertion mode at its conclusion.
383
  GumboInsertionMode _original_insertion_mode;
384
385
  // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-stack-of-open-elements
386
  GumboVector /*GumboNode*/ _open_elements;
387
388
  // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-list-of-active-formatting-elements
389
  GumboVector /*GumboNode*/ _active_formatting_elements;
390
391
  // The stack of template insertion modes.
392
  // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-insertion-mode
393
  GumboVector /*InsertionMode*/ _template_insertion_modes;
394
395
  // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-element-pointers
396
  GumboNode* _head_element;
397
  GumboNode* _form_element;
398
399
  GumboNode* _selectedcontent_target;
400
  GumboSelectedcontentState _selectedcontent_state;
401
402
  // The element used as fragment context when parsing in fragment mode
403
  GumboNode* _fragment_ctx;
404
405
  // The flag for when the spec says "Reprocess the current token in..."
406
  bool _reprocess_current_token;
407
408
  // The flag for "acknowledge the token's self-closing flag".
409
  bool _self_closing_flag_acknowledged;
410
411
  // The "frameset-ok" flag from the spec.
412
  bool _frameset_ok;
413
414
  // The flag for "If the next token is a LINE FEED, ignore that token...".
415
  bool _ignore_next_linefeed;
416
417
  // The flag for "whenever a node would be inserted into the current node, it
418
  // must instead be foster parented".  This is used for misnested table
419
  // content, which needs to be handled according to "in body" rules yet foster
420
  // parented outside of the table.
421
  // It would perhaps be more explicit to have this as a parameter to
422
  // handle_in_body and insert_element, but given how special-purpose this is
423
  // and the number of call-sites that would need to take the extra parameter,
424
  // it's easier just to have a state flag.
425
  bool _foster_parent_insertions;
426
427
  // The accumulated text node buffer state.
428
  TextNodeBufferState _text_node;
429
430
  // The current token.
431
  GumboToken* _current_token;
432
433
  // The way that the spec is written, the </body> and </html> tags are *always*
434
  // implicit, because encountering one of those tokens merely switches the
435
  // insertion mode out of "in body".  So we have individual state flags for
436
  // those end tags that are then inspected by pop_current_node when the <body>
437
  // and <html> nodes are popped to set the GUMBO_INSERTION_IMPLICIT_END_TAG
438
  // flag appropriately.
439
  bool _closed_body_tag;
440
  bool _closed_html_tag;
441
} GumboParserState;
442
443
0
static bool token_has_attribute(const GumboToken* token, const char* name) {
444
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
445
0
  return gumbo_get_attribute(&token->v.start_tag.attributes, name) != NULL;
446
0
}
447
448
// Checks if the value of the specified attribute is a case-insensitive match
449
// for the specified string.
450
static bool attribute_matches(
451
0
    const GumboVector* attributes, const char* name, const char* value) {
452
0
  const GumboAttribute* attr = gumbo_get_attribute(attributes, name);
453
0
  return attr ? strcasecmp(value, attr->value) == 0 : false;
454
0
}
455
456
// Checks if the value of the specified attribute is a case-sensitive match
457
// for the specified string.
458
static bool attribute_matches_case_sensitive(
459
0
    const GumboVector* attributes, const char* name, const char* value) {
460
0
  const GumboAttribute* attr = gumbo_get_attribute(attributes, name);
461
0
  return attr ? strcmp(value, attr->value) == 0 : false;
462
0
}
463
464
// Checks if the specified attribute vectors are identical.
465
static bool all_attributes_match(
466
0
    const GumboVector* attr1, const GumboVector* attr2) {
467
0
  unsigned int num_unmatched_attr2_elements = attr2->length;
468
0
  for (unsigned int i = 0; i < attr1->length; ++i) {
469
0
    const GumboAttribute* attr = attr1->data[i];
470
0
    if (attribute_matches_case_sensitive(attr2, attr->name, attr->value)) {
471
0
      --num_unmatched_attr2_elements;
472
0
    } else {
473
0
      return false;
474
0
    }
475
0
  }
476
0
  return num_unmatched_attr2_elements == 0;
477
0
}
478
479
0
static void set_frameset_not_ok(GumboParser* parser) {
480
0
  gumbo_debug("Setting frameset_ok to false.\n");
481
0
  parser->_parser_state->_frameset_ok = false;
482
0
}
483
484
0
static GumboNode* create_node(GumboParser* parser, GumboNodeType type) {
485
0
  GumboNode* node = gumbo_parser_allocate(parser, sizeof(GumboNode));
486
0
  node->parent = NULL;
487
0
  node->index_within_parent = -1;
488
0
  node->type = type;
489
0
  node->parse_flags = GUMBO_INSERTION_NORMAL;
490
0
  return node;
491
0
}
492
493
0
static GumboNode* new_document_node(GumboParser* parser) {
494
0
  GumboNode* document_node = create_node(parser, GUMBO_NODE_DOCUMENT);
495
0
  document_node->parse_flags = GUMBO_INSERTION_BY_PARSER;
496
0
  gumbo_vector_init(parser, 1, &document_node->v.document.children);
497
498
  // Must be initialized explicitly, as there's no guarantee that we'll see a
499
  // doc type token.
500
0
  GumboDocument* document = &document_node->v.document;
501
0
  document->has_doctype = false;
502
0
  document->name = NULL;
503
0
  document->public_identifier = NULL;
504
0
  document->system_identifier = NULL;
505
0
  return document_node;
506
0
}
507
508
0
static void output_init(GumboParser* parser) {
509
0
  GumboOutput* output = gumbo_parser_allocate(parser, sizeof(GumboOutput));
510
0
  output->root = NULL;
511
0
  output->document = new_document_node(parser);
512
0
  parser->_output = output;
513
0
  gumbo_init_errors(parser);
514
0
}
515
516
0
static void parser_state_init(GumboParser* parser) {
517
0
  GumboParserState* parser_state =
518
0
      gumbo_parser_allocate(parser, sizeof(GumboParserState));
519
0
  parser_state->_insertion_mode = GUMBO_INSERTION_MODE_INITIAL;
520
0
  parser_state->_reprocess_current_token = false;
521
0
  parser_state->_frameset_ok = true;
522
0
  parser_state->_ignore_next_linefeed = false;
523
0
  parser_state->_foster_parent_insertions = false;
524
0
  parser_state->_text_node._type = GUMBO_NODE_WHITESPACE;
525
0
  gumbo_string_buffer_init(parser, &parser_state->_text_node._buffer);
526
0
  gumbo_vector_init(parser, 10, &parser_state->_open_elements);
527
0
  gumbo_vector_init(parser, 5, &parser_state->_active_formatting_elements);
528
0
  gumbo_vector_init(parser, 5, &parser_state->_template_insertion_modes);
529
0
  parser_state->_head_element = NULL;
530
0
  parser_state->_form_element = NULL;
531
0
  parser_state->_selectedcontent_target = NULL;
532
0
  parser_state->_selectedcontent_state = GUMBO_SELECTEDCONTENT_EMPTY;
533
0
  parser_state->_fragment_ctx = NULL;
534
0
  parser_state->_current_token = NULL;
535
0
  parser_state->_closed_body_tag = false;
536
0
  parser_state->_closed_html_tag = false;
537
0
  parser->_parser_state = parser_state;
538
0
}
539
540
0
static void parser_state_destroy(GumboParser* parser) {
541
0
  GumboParserState* state = parser->_parser_state;
542
0
  if (state->_fragment_ctx) {
543
0
    destroy_node(parser, state->_fragment_ctx);
544
0
  }
545
0
  gumbo_vector_destroy(parser, &state->_active_formatting_elements);
546
0
  gumbo_vector_destroy(parser, &state->_open_elements);
547
0
  gumbo_vector_destroy(parser, &state->_template_insertion_modes);
548
0
  gumbo_string_buffer_destroy(parser, &state->_text_node._buffer);
549
0
  gumbo_parser_deallocate(parser, state);
550
0
}
551
552
0
static GumboNode* get_document_node(GumboParser* parser) {
553
0
  return parser->_output->document;
554
0
}
555
556
0
static bool is_fragment_parser(const GumboParser* parser) {
557
0
  return !!parser->_parser_state->_fragment_ctx;
558
0
}
559
560
// Returns the node at the bottom of the stack of open elements, or NULL if no
561
// elements have been added yet.
562
0
static GumboNode* get_current_node(GumboParser* parser) {
563
0
  GumboVector* open_elements = &parser->_parser_state->_open_elements;
564
0
  if (open_elements->length == 0) {
565
0
    assert(!parser->_output->root);
566
0
    return NULL;
567
0
  }
568
0
  assert(open_elements->length > 0);
569
0
  assert(open_elements->data != NULL);
570
0
  return open_elements->data[open_elements->length - 1];
571
0
}
572
573
0
static GumboNode* get_adjusted_current_node(GumboParser* parser) {
574
0
  GumboParserState* state = parser->_parser_state;
575
0
  if (state->_open_elements.length == 1 && state->_fragment_ctx) {
576
0
    return state->_fragment_ctx;
577
0
  }
578
0
  return get_current_node(parser);
579
0
}
580
581
// Returns true if the given needle is in the given array of literal
582
// GumboStringPieces. If exact_match is true, this requires that they match
583
// exactly; otherwise, this performs a prefix match to check if any of the
584
// elements in haystack are the start of needle. This always performs a
585
// case-insensitive match.
586
static bool is_in_static_list(
587
  const char* needle, const GumboStringPiece* haystack, bool exact_match)
588
0
{
589
0
  for (size_t i = 0; haystack[i].length > 0; ++i) {
590
0
    if (exact_match && !strcasecmp(needle, haystack[i].data)) {
591
0
      return true;
592
0
    }
593
0
    if (!exact_match && !strncasecmp(needle, haystack[i].data, haystack[i].length)) {
594
0
      return true;
595
0
    }
596
0
  }
597
0
  return false;
598
0
}
599
600
0
static void set_insertion_mode(GumboParser* parser, GumboInsertionMode mode) {
601
0
  parser->_parser_state->_insertion_mode = mode;
602
0
}
603
604
// http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#reset-the-insertion-mode-appropriately
605
// This is a helper function that returns the appropriate insertion mode instead
606
// of setting it.  Returns GUMBO_INSERTION_MODE_INITIAL as a sentinel value to
607
// indicate that there is no appropriate insertion mode, and the loop should
608
// continue.
609
static GumboInsertionMode get_appropriate_insertion_mode(
610
0
    const GumboParser* parser, int index) {
611
0
  const GumboVector* open_elements = &parser->_parser_state->_open_elements;
612
0
  const GumboNode* node = open_elements->data[index];
613
0
  const bool is_last = index == 0;
614
615
0
  if (is_last && is_fragment_parser(parser)) {
616
0
    node = parser->_parser_state->_fragment_ctx;
617
0
  }
618
619
0
  assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE);
620
0
  if (node->v.element.tag_namespace != GUMBO_NAMESPACE_HTML)
621
0
    return is_last ?
622
0
      GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_INITIAL;
623
  
624
0
  switch (node->v.element.tag) {
625
0
    case GUMBO_TAG_TD:
626
0
    case GUMBO_TAG_TH:
627
0
      if (!is_last) return GUMBO_INSERTION_MODE_IN_CELL;
628
0
      break;
629
0
    case GUMBO_TAG_TR:
630
0
      return GUMBO_INSERTION_MODE_IN_ROW;
631
0
    case GUMBO_TAG_TBODY:
632
0
    case GUMBO_TAG_THEAD:
633
0
    case GUMBO_TAG_TFOOT:
634
0
      return GUMBO_INSERTION_MODE_IN_TABLE_BODY;
635
0
    case GUMBO_TAG_CAPTION:
636
0
      return GUMBO_INSERTION_MODE_IN_CAPTION;
637
0
    case GUMBO_TAG_COLGROUP:
638
0
      return GUMBO_INSERTION_MODE_IN_COLUMN_GROUP;
639
0
    case GUMBO_TAG_TABLE:
640
0
      return GUMBO_INSERTION_MODE_IN_TABLE;
641
0
    case GUMBO_TAG_TEMPLATE:
642
0
      return get_current_template_insertion_mode(parser);
643
0
    case GUMBO_TAG_HEAD:
644
0
      if (!is_last) return GUMBO_INSERTION_MODE_IN_HEAD;
645
0
      break;
646
0
    case GUMBO_TAG_BODY:
647
0
      return GUMBO_INSERTION_MODE_IN_BODY;
648
0
    case GUMBO_TAG_FRAMESET:
649
0
      return GUMBO_INSERTION_MODE_IN_FRAMESET;
650
0
    case GUMBO_TAG_HTML:
651
0
      return parser->_parser_state->_head_element
652
0
                 ? GUMBO_INSERTION_MODE_AFTER_HEAD
653
0
                 : GUMBO_INSERTION_MODE_BEFORE_HEAD;
654
0
    default:
655
0
      break;
656
0
  }
657
0
  return is_last ? GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_INITIAL;
658
0
}
659
660
// This performs the actual "reset the insertion mode" loop.
661
0
static void reset_insertion_mode_appropriately(GumboParser* parser) {
662
0
  const GumboVector* open_elements = &parser->_parser_state->_open_elements;
663
0
  for (int i = open_elements->length; --i >= 0;) {
664
0
    GumboInsertionMode mode = get_appropriate_insertion_mode(parser, i);
665
0
    if (mode != GUMBO_INSERTION_MODE_INITIAL) {
666
0
      set_insertion_mode(parser, mode);
667
0
      return;
668
0
    }
669
0
  }
670
  // Should never get here, because is_last will be set on the last iteration
671
  // and will force GUMBO_INSERTION_MODE_IN_BODY.
672
0
  assert(0);
673
0
}
674
675
static GumboError* parser_add_parse_error(
676
0
    GumboParser* parser, const GumboToken* token) {
677
0
  gumbo_debug("Adding parse error.\n");
678
0
  GumboError* error = gumbo_add_error(parser);
679
0
  if (!error) {
680
0
    return NULL;
681
0
  }
682
0
  error->type = GUMBO_ERR_PARSER;
683
0
  error->position = token->position;
684
0
  error->original_text = token->original_text.data;
685
0
  GumboParserError* extra_data = &error->v.parser;
686
0
  extra_data->input_type = token->type;
687
0
  extra_data->input_tag = GUMBO_TAG_UNKNOWN;
688
0
  if (token->type == GUMBO_TOKEN_START_TAG) {
689
0
    extra_data->input_tag = token->v.start_tag.tag;
690
0
  } else if (token->type == GUMBO_TOKEN_END_TAG) {
691
0
    extra_data->input_tag = token->v.end_tag;
692
0
  }
693
0
  GumboParserState* state = parser->_parser_state;
694
0
  extra_data->parser_state = state->_insertion_mode;
695
0
  gumbo_vector_init(
696
0
      parser, state->_open_elements.length, &extra_data->tag_stack);
697
0
  for (unsigned int i = 0; i < state->_open_elements.length; ++i) {
698
0
    const GumboNode* node = state->_open_elements.data[i];
699
0
    assert(
700
0
        node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE);
701
0
    gumbo_vector_add(
702
0
        parser, (void*) node->v.element.tag, &extra_data->tag_stack);
703
0
  }
704
0
  return error;
705
0
}
706
707
// Returns true if the specified token is either a start or end tag (specified
708
// by is_start) with one of the tag types in the varargs list.  Terminate the
709
// list with GUMBO_TAG_LAST; this functions as a sentinel since no portion of
710
// the spec references tags that are not in the spec.
711
static bool tag_in(
712
0
    const GumboToken* token, bool is_start, const gumbo_tagset tags) {
713
0
  GumboTag token_tag;
714
0
  if (is_start && token->type == GUMBO_TOKEN_START_TAG) {
715
0
    token_tag = token->v.start_tag.tag;
716
0
  } else if (!is_start && token->type == GUMBO_TOKEN_END_TAG) {
717
0
    token_tag = token->v.end_tag;
718
0
  } else {
719
0
    return false;
720
0
  }
721
0
  return (token_tag < GUMBO_TAG_LAST && tags[(int) token_tag] != 0);
722
0
}
723
724
// Like tag_in, but for the single-tag case.
725
0
static bool tag_is(const GumboToken* token, bool is_start, GumboTag tag) {
726
0
  if (is_start && token->type == GUMBO_TOKEN_START_TAG) {
727
0
    return token->v.start_tag.tag == tag;
728
0
  } else if (!is_start && token->type == GUMBO_TOKEN_END_TAG) {
729
0
    return token->v.end_tag == tag;
730
0
  } else {
731
0
    return false;
732
0
  }
733
0
}
734
735
// Like tag_in, but checks for the tag of a node, rather than a token.
736
0
static bool node_tag_in_set(const GumboNode* node, const gumbo_tagset tags) {
737
0
  assert(node != NULL);
738
0
  if (node->type != GUMBO_NODE_ELEMENT && node->type != GUMBO_NODE_TEMPLATE) {
739
0
    return false;
740
0
  }
741
0
  return TAGSET_INCLUDES(
742
0
      tags, node->v.element.tag_namespace, node->v.element.tag);
743
0
}
744
745
// Like node_tag_in, but for the single-tag case.
746
static bool node_qualified_tag_is(
747
0
    const GumboNode* node, GumboNamespaceEnum ns, GumboTag tag) {
748
0
  assert(node);
749
0
  return (node->type == GUMBO_NODE_ELEMENT ||
750
0
             node->type == GUMBO_NODE_TEMPLATE) &&
751
0
         node->v.element.tag == tag && node->v.element.tag_namespace == ns;
752
0
}
753
754
// Like node_tag_in, but for the single-tag case in the HTML namespace
755
0
static bool node_html_tag_is(const GumboNode* node, GumboTag tag) {
756
0
  return node_qualified_tag_is(node, GUMBO_NAMESPACE_HTML, tag);
757
0
}
758
759
static void push_template_insertion_mode(
760
0
    GumboParser* parser, GumboInsertionMode mode) {
761
0
  gumbo_vector_add(
762
0
      parser, (void*) mode, &parser->_parser_state->_template_insertion_modes);
763
0
}
764
765
0
static void pop_template_insertion_mode(GumboParser* parser) {
766
0
  gumbo_vector_pop(parser, &parser->_parser_state->_template_insertion_modes);
767
0
}
768
769
// Returns the current template insertion mode.  If the stack of template
770
// insertion modes is empty, this returns GUMBO_INSERTION_MODE_INITIAL.
771
static GumboInsertionMode get_current_template_insertion_mode(
772
0
    const GumboParser* parser) {
773
0
  GumboVector* template_insertion_modes =
774
0
      &parser->_parser_state->_template_insertion_modes;
775
0
  if (template_insertion_modes->length == 0) {
776
0
    return GUMBO_INSERTION_MODE_INITIAL;
777
0
  }
778
0
  return (GumboInsertionMode)
779
0
      (intptr_t) template_insertion_modes->data[(template_insertion_modes->length - 1)];
780
0
}
781
782
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#mathml-text-integration-point
783
0
static bool is_mathml_integration_point(const GumboNode* node) {
784
0
  return node_tag_in_set(
785
0
      node, (gumbo_tagset){TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN),
786
0
                TAG_MATHML(MS), TAG_MATHML(MTEXT)});
787
0
}
788
789
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#html-integration-point
790
0
static bool is_html_integration_point(const GumboNode* node) {
791
0
  return node_tag_in_set(node, (gumbo_tagset){TAG_SVG(FOREIGNOBJECT),
792
0
                                   TAG_SVG(DESC), TAG_SVG(TITLE)}) ||
793
0
         (node_qualified_tag_is(
794
0
              node, GUMBO_NAMESPACE_MATHML, GUMBO_TAG_ANNOTATION_XML) &&
795
0
             (attribute_matches(
796
0
                  &node->v.element.attributes, "encoding", "text/html") ||
797
0
                 attribute_matches(&node->v.element.attributes, "encoding",
798
0
                     "application/xhtml+xml")));
799
0
}
800
801
// This represents a place to insert a node, consisting of a target parent and a
802
// child index within that parent.  If the node should be inserted at the end of
803
// the parent's child, index will be -1.
804
typedef struct {
805
  GumboNode* target;
806
  int index;
807
} InsertionLocation;
808
809
InsertionLocation get_appropriate_insertion_location(
810
0
    GumboParser* parser, GumboNode* override_target) {
811
0
  InsertionLocation retval = {override_target, -1};
812
0
  if (retval.target == NULL) {
813
    // No override target; default to the current node, but special-case the
814
    // root node since get_current_node() assumes the stack of open elements is
815
    // non-empty.
816
0
    retval.target = parser->_output->root != NULL ? get_current_node(parser)
817
0
                                                  : get_document_node(parser);
818
0
  }
819
0
  if (!parser->_parser_state->_foster_parent_insertions ||
820
0
      !node_tag_in_set(retval.target, (gumbo_tagset){TAG(TABLE), TAG(TBODY),
821
0
                                          TAG(TFOOT), TAG(THEAD), TAG(TR)})) {
822
0
    return retval;
823
0
  }
824
825
  // Foster-parenting case.
826
0
  int last_template_index = -1;
827
0
  int last_table_index = -1;
828
0
  GumboVector* open_elements = &parser->_parser_state->_open_elements;
829
0
  for (unsigned int i = 0; i < open_elements->length; ++i) {
830
0
    if (node_html_tag_is(open_elements->data[i], GUMBO_TAG_TEMPLATE)) {
831
0
      last_template_index = i;
832
0
    }
833
0
    if (node_html_tag_is(open_elements->data[i], GUMBO_TAG_TABLE)) {
834
0
      last_table_index = i;
835
0
    }
836
0
  }
837
0
  if (last_template_index != -1 &&
838
0
      (last_table_index == -1 || last_template_index > last_table_index)) {
839
0
    retval.target = open_elements->data[last_template_index];
840
0
    return retval;
841
0
  }
842
0
  if (last_table_index == -1) {
843
0
    retval.target = open_elements->data[0];
844
0
    return retval;
845
0
  }
846
0
  GumboNode* last_table = open_elements->data[last_table_index];
847
0
  if (last_table->parent != NULL) {
848
0
    retval.target = last_table->parent;
849
0
    retval.index = last_table->index_within_parent;
850
0
    return retval;
851
0
  }
852
853
0
  retval.target = open_elements->data[last_table_index - 1];
854
0
  return retval;
855
0
}
856
857
// Appends a node to the end of its parent, setting the "parent" and
858
// "index_within_parent" fields appropriately.
859
static void append_node(
860
0
    GumboParser* parser, GumboNode* parent, GumboNode* node) {
861
0
  assert(node->parent == NULL);
862
0
  assert(node->index_within_parent == -1);
863
0
  GumboVector* children;
864
0
  if (parent->type == GUMBO_NODE_ELEMENT ||
865
0
      parent->type == GUMBO_NODE_TEMPLATE) {
866
0
    children = &parent->v.element.children;
867
0
  } else {
868
0
    assert(parent->type == GUMBO_NODE_DOCUMENT);
869
0
    children = &parent->v.document.children;
870
0
  }
871
0
  node->parent = parent;
872
0
  node->index_within_parent = children->length;
873
0
  gumbo_vector_add(parser, (void*) node, children);
874
0
  assert(node->index_within_parent < children->length);
875
0
}
876
877
// Inserts a node at the specified InsertionLocation, updating the
878
// "parent" and "index_within_parent" fields of it and all its siblings.
879
// If the index of the location is -1, this calls append_node.
880
static void insert_node(
881
0
    GumboParser* parser, GumboNode* node, InsertionLocation location) {
882
0
  assert(node->parent == NULL);
883
0
  assert(node->index_within_parent == -1);
884
0
  GumboNode* parent = location.target;
885
0
  int index = location.index;
886
0
  if (index != -1) {
887
0
    GumboVector* children = NULL;
888
0
    if (parent->type == GUMBO_NODE_ELEMENT ||
889
0
        parent->type == GUMBO_NODE_TEMPLATE) {
890
0
      children = &parent->v.element.children;
891
0
    } else if (parent->type == GUMBO_NODE_DOCUMENT) {
892
0
      children = &parent->v.document.children;
893
0
      assert(children->length == 0);
894
0
    } else {
895
0
      assert(0);
896
0
    }
897
898
0
    assert(index >= 0);
899
0
    assert((unsigned int) index < children->length);
900
0
    node->parent = parent;
901
0
    node->index_within_parent = index;
902
0
    gumbo_vector_insert_at(parser, (void*) node, index, children);
903
0
    assert(node->index_within_parent < children->length);
904
0
    for (unsigned int i = index + 1; i < children->length; ++i) {
905
0
      GumboNode* sibling = children->data[i];
906
0
      sibling->index_within_parent = i;
907
0
      assert(sibling->index_within_parent < children->length);
908
0
    }
909
0
  } else {
910
0
    append_node(parser, parent, node);
911
0
  }
912
0
}
913
914
0
static void maybe_flush_text_node_buffer(GumboParser* parser) {
915
0
  GumboParserState* state = parser->_parser_state;
916
0
  TextNodeBufferState* buffer_state = &state->_text_node;
917
0
  if (buffer_state->_buffer.length == 0) {
918
0
    return;
919
0
  }
920
921
0
  assert(buffer_state->_type == GUMBO_NODE_WHITESPACE ||
922
0
         buffer_state->_type == GUMBO_NODE_TEXT ||
923
0
         buffer_state->_type == GUMBO_NODE_CDATA);
924
0
  GumboNode* text_node = create_node(parser, buffer_state->_type);
925
0
  GumboText* text_node_data = &text_node->v.text;
926
0
  text_node_data->text =
927
0
      gumbo_string_buffer_to_string(parser, &buffer_state->_buffer);
928
0
  text_node_data->original_text.data = buffer_state->_start_original_text;
929
0
  text_node_data->original_text.length =
930
0
      state->_current_token->original_text.data -
931
0
      buffer_state->_start_original_text;
932
0
  text_node_data->start_pos = buffer_state->_start_position;
933
934
0
  gumbo_debug("Flushing text node buffer of %.*s.\n",
935
0
      (int) buffer_state->_buffer.length, buffer_state->_buffer.data);
936
937
0
  InsertionLocation location = get_appropriate_insertion_location(parser, NULL);
938
0
  if (location.target->type == GUMBO_NODE_DOCUMENT) {
939
    // The DOM does not allow Document nodes to have Text children, so per the
940
    // spec, they are dropped on the floor.
941
0
    destroy_node(parser, text_node);
942
0
  } else {
943
0
    insert_node(parser, text_node, location);
944
0
  }
945
946
0
  gumbo_string_buffer_clear(parser, &buffer_state->_buffer);
947
0
  buffer_state->_type = GUMBO_NODE_WHITESPACE;
948
0
  assert(buffer_state->_buffer.length == 0);
949
0
}
950
951
static void record_end_of_element(
952
0
    GumboToken* current_token, GumboElement* element) {
953
0
  element->end_pos = current_token->position;
954
0
  element->original_end_tag = current_token->type == GUMBO_TOKEN_END_TAG
955
0
                                  ? current_token->original_text
956
0
                                  : kGumboEmptyString;
957
0
}
958
959
0
static GumboNode* clone_node_recursively(GumboParser* parser, const GumboNode* src) {
960
0
  if (!src) {
961
0
    return NULL;
962
0
  }
963
964
0
  switch (src->type) {
965
0
    case GUMBO_NODE_TEXT:
966
0
    case GUMBO_NODE_CDATA:
967
0
    case GUMBO_NODE_WHITESPACE:
968
0
    case GUMBO_NODE_COMMENT:
969
0
    case GUMBO_NODE_PROCESSING_INSTRUCTION: {
970
0
      GumboNode* t = create_node(parser, src->type);
971
0
      const char* s = src->v.text.text;
972
0
      t->v.text.text = s ? gumbo_copy_stringz(parser, s) : NULL;
973
0
      t->v.text.original_text.data = NULL;
974
0
      t->v.text.original_text.length = 0;
975
0
      t->v.text.start_pos = src->v.text.start_pos;
976
0
      return t;
977
0
    }
978
979
0
    case GUMBO_NODE_ELEMENT:
980
0
    case GUMBO_NODE_TEMPLATE: {
981
0
      GumboNode* e = create_node(parser, src->type);
982
0
      GumboElement* dst = &e->v.element;
983
0
      const GumboElement* se = &src->v.element;
984
985
0
      dst->tag = se->tag;
986
0
      dst->tag_namespace = se->tag_namespace;
987
0
      dst->original_tag = se->original_tag;
988
0
      dst->original_end_tag = se->original_end_tag;
989
0
      dst->start_pos = se->start_pos;
990
0
      dst->end_pos = se->end_pos;
991
992
0
      gumbo_vector_init(parser, se->attributes.length ? se->attributes.length : 1, &dst->attributes);
993
0
      for (unsigned int i = 0; i < se->attributes.length; ++i) {
994
0
        const GumboAttribute* sa = se->attributes.data[i];
995
0
        GumboAttribute* a = gumbo_parser_allocate(parser, sizeof(GumboAttribute));
996
0
        memcpy(a, sa, sizeof(*a));
997
0
        a->name  = gumbo_copy_stringz(parser, sa->name);
998
0
        a->value = gumbo_copy_stringz(parser, sa->value);
999
0
        gumbo_vector_add(parser, a, &dst->attributes);
1000
0
      }
1001
1002
0
      gumbo_vector_init(parser, se->children.length ? se->children.length : 1, &dst->children);
1003
0
      for (unsigned int i = 0; i < se->children.length; ++i) {
1004
0
        GumboNode* child_clone = clone_node_recursively(parser, se->children.data[i]);
1005
0
        if (child_clone) {
1006
0
          append_node(parser, e, child_clone);
1007
0
        }
1008
0
      }
1009
0
      return e;
1010
0
    }
1011
1012
0
    default:
1013
0
      return NULL;
1014
0
  }
1015
0
}
1016
1017
0
static void maybe_clone_option_into_selectedcontent(GumboParser* parser, GumboParserState* state, GumboNode* option_node) {
1018
0
  GumboNode* selectedcontent = state->_selectedcontent_target;
1019
0
  if (!selectedcontent) {
1020
0
    return;
1021
0
  }
1022
0
  if (option_node->type != GUMBO_NODE_ELEMENT && option_node->type != GUMBO_NODE_TEMPLATE) {
1023
0
    return;
1024
0
  }
1025
0
  if (state->_selectedcontent_state == GUMBO_SELECTEDCONTENT_FORCED) {
1026
0
    return;
1027
0
  }
1028
0
  bool is_selected_option = !!gumbo_get_attribute(&option_node->v.element.attributes, "selected");
1029
0
  if (state->_selectedcontent_state == GUMBO_SELECTEDCONTENT_AUTOMATIC) {
1030
0
    if (!is_selected_option) {
1031
0
      return;
1032
0
    }
1033
0
    GumboVector* oldies = &selectedcontent->v.element.children;
1034
0
    for (unsigned int i = 0; i < oldies->length; ++i) {
1035
0
      destroy_node(parser, oldies->data[i]);
1036
0
    }
1037
0
    gumbo_vector_clear(parser, &selectedcontent->v.element.children);
1038
0
  }
1039
0
  GumboVector* kids = &option_node->v.element.children;
1040
0
  for (unsigned int i = 0; i < kids->length; ++i) {
1041
0
    GumboNode* child = kids->data[i];
1042
0
    GumboNode* clone = clone_node_recursively(parser, child);
1043
0
    if (clone) {
1044
0
      append_node(parser, selectedcontent, clone);
1045
0
    }
1046
0
  }
1047
0
  state->_selectedcontent_state = is_selected_option ? GUMBO_SELECTEDCONTENT_FORCED : GUMBO_SELECTEDCONTENT_AUTOMATIC;
1048
0
}
1049
1050
0
static GumboNode* pop_current_node(GumboParser* parser) {
1051
0
  GumboParserState* state = parser->_parser_state;
1052
0
  maybe_flush_text_node_buffer(parser);
1053
0
  if (state->_open_elements.length > 0) {
1054
0
    assert(node_html_tag_is(state->_open_elements.data[0], GUMBO_TAG_HTML));
1055
0
    gumbo_debug("Popping %s node.\n",
1056
0
        gumbo_normalized_tagname(get_current_node(parser)->v.element.tag));
1057
0
  }
1058
0
  GumboNode* current_node = gumbo_vector_pop(parser, &state->_open_elements);
1059
0
  if (!current_node) {
1060
0
    assert(state->_open_elements.length == 0);
1061
0
    return NULL;
1062
0
  }
1063
0
  assert(current_node->type == GUMBO_NODE_ELEMENT ||
1064
0
         current_node->type == GUMBO_NODE_TEMPLATE);
1065
0
  bool is_closed_body_or_html_tag =
1066
0
      (node_html_tag_is(current_node, GUMBO_TAG_BODY) &&
1067
0
          state->_closed_body_tag) ||
1068
0
      (node_html_tag_is(current_node, GUMBO_TAG_HTML) &&
1069
0
          state->_closed_html_tag);
1070
0
  if ((state->_current_token->type != GUMBO_TOKEN_END_TAG ||
1071
0
          !node_html_tag_is(current_node, state->_current_token->v.end_tag)) &&
1072
0
      !is_closed_body_or_html_tag) {
1073
0
    current_node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG;
1074
0
  }
1075
0
  if (!is_closed_body_or_html_tag) {
1076
0
    record_end_of_element(state->_current_token, &current_node->v.element);
1077
1078
0
    if (node_html_tag_is(current_node, GUMBO_TAG_OPTION)) {
1079
0
      maybe_clone_option_into_selectedcontent(parser, state, current_node);
1080
0
    }
1081
0
  }
1082
0
  return current_node;
1083
0
}
1084
1085
0
static bool is_token_commentlike(const GumboToken* token) {
1086
0
  return token->type == GUMBO_TOKEN_COMMENT || token->type == GUMBO_TOKEN_PROCESSING_INSTRUCTION;
1087
0
}
1088
1089
static void append_commentlike_node(
1090
0
    GumboParser* parser, GumboNode* node, const GumboToken* token) {
1091
0
  maybe_flush_text_node_buffer(parser);
1092
0
  GumboNodeType type
1093
0
    = token->type == GUMBO_TOKEN_PROCESSING_INSTRUCTION
1094
0
    ? GUMBO_NODE_PROCESSING_INSTRUCTION
1095
0
    : GUMBO_NODE_COMMENT;
1096
0
  GumboNode* comment = create_node(parser, type);
1097
0
  comment->type = type;
1098
0
  comment->parse_flags = GUMBO_INSERTION_NORMAL;
1099
0
  comment->v.text.text = token->v.text;
1100
0
  comment->v.text.original_text = token->original_text;
1101
0
  comment->v.text.start_pos = token->position;
1102
0
  append_node(parser, node, comment);
1103
0
}
1104
1105
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-row-context
1106
0
static void clear_stack_to_table_row_context(GumboParser* parser) {
1107
0
  while (!node_tag_in_set(get_current_node(parser),
1108
0
             (gumbo_tagset){TAG(HTML), TAG(TR), TAG(TEMPLATE)})) {
1109
0
    pop_current_node(parser);
1110
0
  }
1111
0
}
1112
1113
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-context
1114
0
static void clear_stack_to_table_context(GumboParser* parser) {
1115
0
  while (!node_tag_in_set(get_current_node(parser),
1116
0
             (gumbo_tagset){TAG(HTML), TAG(TABLE), TAG(TEMPLATE)})) {
1117
0
    pop_current_node(parser);
1118
0
  }
1119
0
}
1120
1121
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-body-context
1122
0
void clear_stack_to_table_body_context(GumboParser* parser) {
1123
0
  while (!node_tag_in_set(get_current_node(parser),
1124
0
    (gumbo_tagset){
1125
0
        TAG(HTML),
1126
0
        TAG(TBODY),
1127
0
        TAG(TFOOT),
1128
0
        TAG(THEAD),
1129
0
        TAG(TEMPLATE)
1130
0
    })) {
1131
0
    pop_current_node(parser);
1132
0
  }
1133
0
}
1134
1135
// Creates a parser-inserted element in the HTML namespace and returns it.
1136
0
static GumboNode* create_element(GumboParser* parser, GumboTag tag) {
1137
0
  GumboNode* node = create_node(parser, GUMBO_NODE_ELEMENT);
1138
0
  GumboElement* element = &node->v.element;
1139
0
  gumbo_vector_init(parser, 1, &element->children);
1140
0
  gumbo_vector_init(parser, 0, &element->attributes);
1141
0
  element->tag = tag;
1142
0
  element->tag_namespace = GUMBO_NAMESPACE_HTML;
1143
0
  element->original_tag = kGumboEmptyString;
1144
0
  element->original_end_tag = kGumboEmptyString;
1145
0
  element->start_pos = (parser->_parser_state->_current_token)
1146
0
                           ? parser->_parser_state->_current_token->position
1147
0
                           : kGumboEmptySourcePosition;
1148
0
  element->end_pos = kGumboEmptySourcePosition;
1149
0
  return node;
1150
0
}
1151
1152
// Constructs an element from the given start tag token.
1153
static GumboNode* create_element_from_token(
1154
0
    GumboParser* parser, GumboToken* token, GumboNamespaceEnum tag_namespace) {
1155
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
1156
0
  GumboTokenStartTag* start_tag = &token->v.start_tag;
1157
1158
0
  GumboNodeType type = (tag_namespace == GUMBO_NAMESPACE_HTML &&
1159
0
                           start_tag->tag == GUMBO_TAG_TEMPLATE)
1160
0
                           ? GUMBO_NODE_TEMPLATE
1161
0
                           : GUMBO_NODE_ELEMENT;
1162
1163
0
  GumboNode* node = create_node(parser, type);
1164
0
  GumboElement* element = &node->v.element;
1165
0
  gumbo_vector_init(parser, 1, &element->children);
1166
0
  element->attributes = start_tag->attributes;
1167
0
  element->tag = start_tag->tag;
1168
0
  element->tag_namespace = tag_namespace;
1169
1170
0
  assert(token->original_text.length >= 2);
1171
0
  assert(token->original_text.data[0] == '<');
1172
0
  assert(token->original_text.data[token->original_text.length - 1] == '>');
1173
0
  element->original_tag = token->original_text;
1174
0
  element->start_pos = token->position;
1175
0
  element->original_end_tag = kGumboEmptyString;
1176
0
  element->end_pos = kGumboEmptySourcePosition;
1177
1178
  // The element takes ownership of the attributes from the token, so any
1179
  // allocated-memory fields should be nulled out.
1180
0
  start_tag->attributes = kGumboEmptyVector;
1181
0
  return node;
1182
0
}
1183
1184
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#insert-an-html-element
1185
static void insert_element(GumboParser* parser, GumboNode* node,
1186
0
    bool is_reconstructing_formatting_elements) {
1187
0
  GumboParserState* state = parser->_parser_state;
1188
  // NOTE(jdtang): The text node buffer must always be flushed before inserting
1189
  // a node, otherwise we're handling nodes in a different order than the spec
1190
  // mandated.  However, one clause of the spec (character tokens in the body)
1191
  // requires that we reconstruct the active formatting elements *before* adding
1192
  // the character, and reconstructing the active formatting elements may itself
1193
  // result in the insertion of new elements (which should be pushed onto the
1194
  // stack of open elements before the buffer is flushed).  We solve this (for
1195
  // the time being, the spec has been rewritten for <template> and the new
1196
  // version may be simpler here) with a boolean flag to this method.
1197
0
  if (!is_reconstructing_formatting_elements) {
1198
0
    maybe_flush_text_node_buffer(parser);
1199
0
  }
1200
0
  InsertionLocation location = get_appropriate_insertion_location(parser, NULL);
1201
0
  insert_node(parser, node, location);
1202
0
  gumbo_vector_add(parser, (void*) node, &state->_open_elements);
1203
0
}
1204
1205
// Convenience method that combines create_element_from_token and
1206
// insert_element, inserting the generated element directly into the current
1207
// node.  Returns the node inserted.
1208
static GumboNode* insert_element_from_token(
1209
0
    GumboParser* parser, GumboToken* token) {
1210
0
  GumboNode* element =
1211
0
      create_element_from_token(parser, token, GUMBO_NAMESPACE_HTML);
1212
0
  insert_element(parser, element, false);
1213
0
  gumbo_debug("Inserting <%s> element (@%x) from token.\n",
1214
0
      gumbo_normalized_tagname(element->v.element.tag), element);
1215
0
  return element;
1216
0
}
1217
1218
// Convenience method that combines create_element and insert_element, inserting
1219
// a parser-generated element of a specific tag type.  Returns the node
1220
// inserted.
1221
static GumboNode* insert_element_of_tag_type(
1222
0
    GumboParser* parser, GumboTag tag, GumboParseFlags reason) {
1223
0
  GumboNode* element = create_element(parser, tag);
1224
0
  element->parse_flags |= GUMBO_INSERTION_BY_PARSER | reason;
1225
0
  insert_element(parser, element, false);
1226
0
  gumbo_debug("Inserting %s element (@%x) from tag type.\n",
1227
0
      gumbo_normalized_tagname(tag), element);
1228
0
  return element;
1229
0
}
1230
1231
// Convenience method for creating foreign namespaced element.  Returns the node
1232
// inserted.
1233
static GumboNode* insert_foreign_element(
1234
0
    GumboParser* parser, GumboToken* token, GumboNamespaceEnum tag_namespace) {
1235
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
1236
0
  GumboNode* element = create_element_from_token(parser, token, tag_namespace);
1237
0
  insert_element(parser, element, false);
1238
0
  if (token_has_attribute(token, "xmlns") &&
1239
0
      !attribute_matches_case_sensitive(&token->v.start_tag.attributes, "xmlns",
1240
0
          kLegalXmlns[tag_namespace])) {
1241
    // TODO(jdtang): Since there're multiple possible error codes here, we
1242
    // eventually need reason codes to differentiate them.
1243
0
    parser_add_parse_error(parser, token);
1244
0
  }
1245
0
  if (token_has_attribute(token, "xmlns:xlink") &&
1246
0
      !attribute_matches_case_sensitive(&token->v.start_tag.attributes,
1247
0
          "xmlns:xlink", "http://www.w3.org/1999/xlink")) {
1248
0
    parser_add_parse_error(parser, token);
1249
0
  }
1250
0
  return element;
1251
0
}
1252
1253
0
static void insert_text_token(GumboParser* parser, GumboToken* token) {
1254
0
  assert(token->type == GUMBO_TOKEN_WHITESPACE ||
1255
0
         token->type == GUMBO_TOKEN_CHARACTER ||
1256
0
         token->type == GUMBO_TOKEN_NULL || token->type == GUMBO_TOKEN_CDATA);
1257
0
  TextNodeBufferState* buffer_state = &parser->_parser_state->_text_node;
1258
0
  if (buffer_state->_buffer.length == 0) {
1259
    // Initialize position fields.
1260
0
    buffer_state->_start_original_text = token->original_text.data;
1261
0
    buffer_state->_start_position = token->position;
1262
0
  }
1263
0
  gumbo_string_buffer_append_codepoint(
1264
0
      parser, token->v.character, &buffer_state->_buffer);
1265
0
  if (token->type == GUMBO_TOKEN_CHARACTER) {
1266
0
    buffer_state->_type = GUMBO_NODE_TEXT;
1267
0
  } else if (token->type == GUMBO_TOKEN_CDATA) {
1268
0
    buffer_state->_type = GUMBO_NODE_CDATA;
1269
0
  }
1270
0
  gumbo_debug("Inserting text token '%c'.\n", token->v.character);
1271
0
}
1272
1273
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#generic-rcdata-element-parsing-algorithm
1274
static void run_generic_parsing_algorithm(
1275
0
    GumboParser* parser, GumboToken* token, GumboTokenizerEnum lexer_state) {
1276
0
  insert_element_from_token(parser, token);
1277
0
  gumbo_tokenizer_set_state(parser, lexer_state);
1278
0
  parser->_parser_state->_original_insertion_mode =
1279
0
      parser->_parser_state->_insertion_mode;
1280
0
  parser->_parser_state->_insertion_mode = GUMBO_INSERTION_MODE_TEXT;
1281
0
}
1282
1283
0
static void acknowledge_self_closing_tag(GumboParser* parser) {
1284
0
  parser->_parser_state->_self_closing_flag_acknowledged = true;
1285
0
}
1286
1287
// Returns true if there's an anchor tag in the list of active formatting
1288
// elements, and fills in its index if so.
1289
0
static bool find_last_anchor_index(GumboParser* parser, int* anchor_index) {
1290
0
  GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
1291
0
  for (int i = elements->length; --i >= 0;) {
1292
0
    GumboNode* node = elements->data[i];
1293
0
    if (node == &kActiveFormattingScopeMarker) {
1294
0
      return false;
1295
0
    }
1296
0
    if (node_html_tag_is(node, GUMBO_TAG_A)) {
1297
0
      *anchor_index = i;
1298
0
      return true;
1299
0
    }
1300
0
  }
1301
0
  return false;
1302
0
}
1303
1304
// Counts the number of open formatting elements in the list of active
1305
// formatting elements (after the last active scope marker) that have a specific
1306
// tag.  If this is > 0, then earliest_matching_index will be filled in with the
1307
// index of the first such element.
1308
static int count_formatting_elements_of_tag(GumboParser* parser,
1309
0
    const GumboNode* desired_node, int* earliest_matching_index) {
1310
0
  const GumboElement* desired_element = &desired_node->v.element;
1311
0
  GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
1312
0
  int num_identical_elements = 0;
1313
0
  for (int i = elements->length; --i >= 0;) {
1314
0
    GumboNode* node = elements->data[i];
1315
0
    if (node == &kActiveFormattingScopeMarker) {
1316
0
      break;
1317
0
    }
1318
0
    assert(node->type == GUMBO_NODE_ELEMENT);
1319
0
    if (node_qualified_tag_is(
1320
0
            node, desired_element->tag_namespace, desired_element->tag) &&
1321
0
        all_attributes_match(
1322
0
            &node->v.element.attributes, &desired_element->attributes)) {
1323
0
      num_identical_elements++;
1324
0
      *earliest_matching_index = i;
1325
0
    }
1326
0
  }
1327
0
  return num_identical_elements;
1328
0
}
1329
1330
// http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#reconstruct-the-active-formatting-elements
1331
0
static void add_formatting_element(GumboParser* parser, const GumboNode* node) {
1332
0
  assert(node == &kActiveFormattingScopeMarker ||
1333
0
         node->type == GUMBO_NODE_ELEMENT);
1334
0
  GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
1335
0
  if (node == &kActiveFormattingScopeMarker) {
1336
0
    gumbo_debug("Adding a scope marker.\n");
1337
0
  } else {
1338
0
    gumbo_debug("Adding a formatting element.\n");
1339
0
  }
1340
1341
  // Hunt for identical elements.
1342
0
  int earliest_identical_element = elements->length;
1343
0
  int num_identical_elements = count_formatting_elements_of_tag(
1344
0
      parser, node, &earliest_identical_element);
1345
1346
  // Noah's Ark clause: if there're at least 3, remove the earliest.
1347
0
  if (num_identical_elements >= 3) {
1348
0
    gumbo_debug("Noah's ark clause: removing element at %d.\n",
1349
0
        earliest_identical_element);
1350
0
    gumbo_vector_remove_at(parser, earliest_identical_element, elements);
1351
0
  }
1352
1353
0
  gumbo_vector_add(parser, (void*) node, elements);
1354
0
}
1355
1356
0
static bool is_open_element(GumboParser* parser, const GumboNode* node) {
1357
0
  GumboVector* open_elements = &parser->_parser_state->_open_elements;
1358
0
  for (unsigned int i = 0; i < open_elements->length; ++i) {
1359
0
    if (open_elements->data[i] == node) {
1360
0
      return true;
1361
0
    }
1362
0
  }
1363
0
  return false;
1364
0
}
1365
1366
// Clones attributes, tags, etc. of a node, but does not copy the content.  The
1367
// clone shares no structure with the original node: all owned strings and
1368
// values are fresh copies.
1369
GumboNode* clone_node(
1370
0
    GumboParser* parser, GumboNode* node, GumboParseFlags reason) {
1371
0
  assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE);
1372
0
  GumboNode* new_node = gumbo_parser_allocate(parser, sizeof(GumboNode));
1373
0
  *new_node = *node;
1374
0
  new_node->parent = NULL;
1375
0
  new_node->index_within_parent = -1;
1376
  // Clear the GUMBO_INSERTION_IMPLICIT_END_TAG flag, as the cloned node may
1377
  // have a separate end tag.
1378
0
  new_node->parse_flags &= ~GUMBO_INSERTION_IMPLICIT_END_TAG;
1379
0
  new_node->parse_flags |= reason | GUMBO_INSERTION_BY_PARSER;
1380
0
  GumboElement* element = &new_node->v.element;
1381
0
  gumbo_vector_init(parser, 1, &element->children);
1382
1383
0
  const GumboVector* old_attributes = &node->v.element.attributes;
1384
0
  gumbo_vector_init(parser, old_attributes->length, &element->attributes);
1385
0
  for (unsigned int i = 0; i < old_attributes->length; ++i) {
1386
0
    const GumboAttribute* old_attr = old_attributes->data[i];
1387
0
    GumboAttribute* attr =
1388
0
        gumbo_parser_allocate(parser, sizeof(GumboAttribute));
1389
0
    *attr = *old_attr;
1390
0
    attr->name = gumbo_copy_stringz(parser, old_attr->name);
1391
0
    attr->value = gumbo_copy_stringz(parser, old_attr->value);
1392
0
    gumbo_vector_add(parser, attr, &element->attributes);
1393
0
  }
1394
0
  return new_node;
1395
0
}
1396
1397
// "Reconstruct active formatting elements" part of the spec.
1398
// This implementation is based on the html5lib translation from the mess of
1399
// GOTOs in the spec to reasonably structured programming.
1400
// https://github.com/html5lib/html5lib-python/blob/master/html5lib/treebuilders/base.py
1401
0
static void reconstruct_active_formatting_elements(GumboParser* parser) {
1402
0
  GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
1403
  // Step 1
1404
0
  if (elements->length == 0) {
1405
0
    return;
1406
0
  }
1407
1408
  // Step 2 & 3
1409
0
  unsigned int i = elements->length - 1;
1410
0
  GumboNode* element = elements->data[i];
1411
0
  if (element == &kActiveFormattingScopeMarker ||
1412
0
      is_open_element(parser, element)) {
1413
0
    return;
1414
0
  }
1415
1416
  // Step 6
1417
0
  do {
1418
0
    if (i == 0) {
1419
      // Step 4
1420
0
      i = -1;  // Incremented to 0 below.
1421
0
      break;
1422
0
    }
1423
    // Step 5
1424
0
    element = elements->data[--i];
1425
0
  } while (element != &kActiveFormattingScopeMarker &&
1426
0
           !is_open_element(parser, element));
1427
1428
0
  ++i;
1429
0
  gumbo_debug("Reconstructing elements from %d on %s parent.\n", i,
1430
0
      gumbo_normalized_tagname(get_current_node(parser)->v.element.tag));
1431
0
  for (; i < elements->length; ++i) {
1432
    // Step 7 & 8.
1433
0
    assert(elements->length > 0);
1434
0
    assert(i < elements->length);
1435
0
    element = elements->data[i];
1436
0
    assert(element != &kActiveFormattingScopeMarker);
1437
0
    GumboNode* clone = clone_node(
1438
0
        parser, element, GUMBO_INSERTION_RECONSTRUCTED_FORMATTING_ELEMENT);
1439
    // Step 9.
1440
0
    InsertionLocation location =
1441
0
        get_appropriate_insertion_location(parser, NULL);
1442
0
    insert_node(parser, clone, location);
1443
0
    gumbo_vector_add(
1444
0
        parser, (void*) clone, &parser->_parser_state->_open_elements);
1445
1446
    // Step 10.
1447
0
    elements->data[i] = clone;
1448
0
    gumbo_debug("Reconstructed %s element at %d.\n",
1449
0
        gumbo_normalized_tagname(clone->v.element.tag), i);
1450
0
  }
1451
0
}
1452
1453
0
static void clear_active_formatting_elements(GumboParser* parser) {
1454
0
  GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
1455
0
  int num_elements_cleared = 0;
1456
0
  const GumboNode* node;
1457
0
  do {
1458
0
    node = gumbo_vector_pop(parser, elements);
1459
0
    ++num_elements_cleared;
1460
0
  } while (node && node != &kActiveFormattingScopeMarker);
1461
0
  gumbo_debug("Cleared %d elements from active formatting list.\n",
1462
0
      num_elements_cleared);
1463
0
}
1464
1465
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-initial-insertion-mode
1466
static GumboQuirksModeEnum compute_quirks_mode(
1467
0
    const GumboTokenDocType* doctype) {
1468
0
  if (doctype->force_quirks || strcmp(doctype->name, kDoctypeHtml.data) ||
1469
0
      is_in_static_list(
1470
0
          doctype->public_identifier, kQuirksModePublicIdPrefixes, false) ||
1471
0
      is_in_static_list(
1472
0
          doctype->public_identifier, kQuirksModePublicIdExactMatches, true) ||
1473
0
      is_in_static_list(
1474
0
          doctype->system_identifier, kQuirksModeSystemIdExactMatches, true) ||
1475
0
      (is_in_static_list(doctype->public_identifier,
1476
0
           kLimitedQuirksRequiresSystemIdPublicIdPrefixes, false) &&
1477
0
          !doctype->has_system_identifier)) {
1478
0
    return GUMBO_DOCTYPE_QUIRKS;
1479
0
  } else if (is_in_static_list(doctype->public_identifier,
1480
0
                 kLimitedQuirksPublicIdPrefixes, false) ||
1481
0
             (is_in_static_list(doctype->public_identifier,
1482
0
                  kLimitedQuirksRequiresSystemIdPublicIdPrefixes, false) &&
1483
0
                 doctype->has_system_identifier)) {
1484
0
    return GUMBO_DOCTYPE_LIMITED_QUIRKS;
1485
0
  }
1486
0
  return GUMBO_DOCTYPE_NO_QUIRKS;
1487
0
}
1488
1489
// The following functions are all defined by the "has an element in __ scope"
1490
// sections of the HTML5 spec:
1491
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-the-specific-scope
1492
// The basic idea behind them is that they check for an element of the given
1493
// qualified name, contained within a scope formed by a set of other qualified
1494
// names.  For example, "has an element in list scope" looks for an element of
1495
// the given qualified name within the nearest enclosing <ol> or <ul>, along
1496
// with a bunch of generic element types that serve to "firewall" their content
1497
// from the rest of the document. Note that because of the way the spec is
1498
// written,
1499
// all elements are expected to be in the HTML namespace
1500
static bool has_an_element_in_specific_scope(GumboParser* parser,
1501
    int expected_size, const GumboTag* expected, bool negate,
1502
0
    const gumbo_tagset tags) {
1503
0
  GumboVector* open_elements = &parser->_parser_state->_open_elements;
1504
0
  for (int i = open_elements->length; --i >= 0;) {
1505
0
    const GumboNode* node = open_elements->data[i];
1506
0
    if (node->type != GUMBO_NODE_ELEMENT && node->type != GUMBO_NODE_TEMPLATE)
1507
0
      continue;
1508
1509
0
    GumboTag node_tag = node->v.element.tag;
1510
0
    GumboNamespaceEnum node_ns = node->v.element.tag_namespace;
1511
0
    for (int j = 0; j < expected_size; ++j) {
1512
0
      if (node_tag == expected[j] && node_ns == GUMBO_NAMESPACE_HTML)
1513
0
        return true;
1514
0
    }
1515
1516
0
    bool found = TAGSET_INCLUDES(tags, node_ns, node_tag);
1517
0
    if (negate != found) return false;
1518
0
  }
1519
0
  return false;
1520
0
}
1521
1522
// Checks for the presence of an open element of the specified tag type.
1523
0
static bool has_open_element(GumboParser* parser, GumboTag tag) {
1524
0
  return has_an_element_in_specific_scope(
1525
0
      parser, 1, &tag, false, (gumbo_tagset){TAG(HTML)});
1526
0
}
1527
1528
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-scope
1529
0
static bool has_an_element_in_scope(GumboParser* parser, GumboTag tag) {
1530
0
  return has_an_element_in_specific_scope(parser, 1, &tag, false, (gumbo_tagset){SCOPE_TAGS});
1531
0
}
1532
1533
// Like "has an element in scope", but for the specific case of looking for a
1534
// unique target node, not for any node with a given tag name.  This duplicates
1535
// much of the algorithm from has_an_element_in_specific_scope because the
1536
// predicate is different when checking for an exact node, and it's easier &
1537
// faster just to duplicate the code for this one case than to try and
1538
// parameterize it.
1539
0
static bool has_node_in_scope(GumboParser* parser, const GumboNode* node) {
1540
0
  GumboVector* open_elements = &parser->_parser_state->_open_elements;
1541
0
  for (int i = open_elements->length; --i >= 0;) {
1542
0
    const GumboNode* current = open_elements->data[i];
1543
0
    if (current == node) {
1544
0
      return true;
1545
0
    }
1546
0
    if (current->type != GUMBO_NODE_ELEMENT &&
1547
0
        current->type != GUMBO_NODE_TEMPLATE) {
1548
0
      continue;
1549
0
    }
1550
0
    if (node_tag_in_set(current, (gumbo_tagset){SCOPE_TAGS})) {
1551
0
      return false;
1552
0
    }
1553
0
  }
1554
0
  assert(false);
1555
0
  return false;
1556
0
}
1557
1558
// Like has_an_element_in_scope, but restricts the expected qualified name to a
1559
// range of possible qualified names instead of just a single one.
1560
static bool has_an_element_in_scope_with_tagname(
1561
0
    GumboParser* parser, int expected_len, const GumboTag expected[]) {
1562
0
  return has_an_element_in_specific_scope(parser, expected_len, expected, false,
1563
0
      (gumbo_tagset){SCOPE_TAGS});
1564
0
}
1565
1566
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-list-item-scope
1567
0
static bool has_an_element_in_list_scope(GumboParser* parser, GumboTag tag) {
1568
0
  return has_an_element_in_specific_scope(parser, 1, &tag, false,
1569
0
      (gumbo_tagset){SCOPE_TAGS, TAG(OL), TAG(UL)});
1570
0
}
1571
1572
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-button-scope
1573
0
static bool has_an_element_in_button_scope(GumboParser* parser, GumboTag tag) {
1574
0
  return has_an_element_in_specific_scope(parser, 1, &tag, false,
1575
0
      (gumbo_tagset){SCOPE_TAGS, TAG(BUTTON)});
1576
0
}
1577
1578
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-table-scope
1579
0
static bool has_an_element_in_table_scope(GumboParser* parser, GumboTag tag) {
1580
0
  return has_an_element_in_specific_scope(parser, 1, &tag, false,
1581
0
      (gumbo_tagset){TAG(HTML), TAG(TABLE), TAG(TEMPLATE)});
1582
0
}
1583
1584
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#generate-implied-end-tags
1585
// "exception" is the "element to exclude from the process" listed in the spec.
1586
// Pass GUMBO_TAG_LAST to not exclude any of them.
1587
0
static void generate_implied_end_tags(GumboParser* parser, GumboTag exception) {
1588
0
  for (; node_tag_in_set(get_current_node(parser),
1589
0
             (gumbo_tagset){
1590
0
                 TAG(DD),
1591
0
                 TAG(DT),
1592
0
                 TAG(LI),
1593
0
                 TAG(OPTION),
1594
0
                 TAG(OPTGROUP),
1595
0
                 TAG(P),
1596
0
                 TAG(RP),
1597
0
                 TAG(RB),
1598
0
                 TAG(RT),
1599
0
                 TAG(RTC)
1600
0
             }) &&
1601
0
         !node_html_tag_is(get_current_node(parser), exception);
1602
0
       pop_current_node(parser))
1603
0
    ;
1604
0
}
1605
1606
// This is the "generate all implied end tags thoroughly" clause of the spec.
1607
// https://html.spec.whatwg.org/multipage/syntax.html#closing-elements-that-have-implied-end-tags
1608
0
static void generate_all_implied_end_tags_thoroughly(GumboParser* parser) {
1609
0
  for (
1610
0
      ; node_tag_in_set(get_current_node(parser),
1611
0
          (gumbo_tagset){
1612
0
              TAG(CAPTION),
1613
0
              TAG(COLGROUP),
1614
0
              TAG(DD),
1615
0
              TAG(DT),
1616
0
              TAG(LI),
1617
0
              TAG(OPTION),
1618
0
              TAG(OPTGROUP),
1619
0
              TAG(P),
1620
0
              TAG(RP),
1621
0
              TAG(RT),
1622
0
              TAG(RTC),
1623
0
              TAG(TBODY),
1624
0
              TAG(TD),
1625
0
              TAG(TFOOT),
1626
0
              TAG(TH),
1627
0
              TAG(HEAD),
1628
0
              TAG(TR)
1629
0
          });
1630
0
      pop_current_node(parser))
1631
0
    ;
1632
0
}
1633
1634
// This factors out the clauses relating to "act as if an end tag token with tag
1635
// name "table" had been seen.  Returns true if there's a table element in table
1636
// scope which was successfully closed, false if not and the token should be
1637
// ignored.  Does not add parse errors; callers should handle that.
1638
0
static bool close_table(GumboParser* parser) {
1639
0
  if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TABLE)) {
1640
0
    return false;
1641
0
  }
1642
1643
0
  GumboNode* node = pop_current_node(parser);
1644
0
  while (!node_html_tag_is(node, GUMBO_TAG_TABLE)) {
1645
0
    node = pop_current_node(parser);
1646
0
  }
1647
0
  reset_insertion_mode_appropriately(parser);
1648
0
  return true;
1649
0
}
1650
1651
// This factors out the clauses relating to "act as if an end tag token with tag
1652
// name `cell_tag` had been seen".
1653
static bool close_table_cell(
1654
0
    GumboParser* parser, const GumboToken* token, GumboTag cell_tag) {
1655
0
  bool result = true;
1656
0
  generate_implied_end_tags(parser, GUMBO_TAG_LAST);
1657
0
  const GumboNode* node = get_current_node(parser);
1658
0
  if (!node_html_tag_is(node, cell_tag)) {
1659
0
    parser_add_parse_error(parser, token);
1660
0
    result = false;
1661
0
  }
1662
0
  do {
1663
0
    node = pop_current_node(parser);
1664
0
  } while (!node_html_tag_is(node, cell_tag));
1665
1666
0
  clear_active_formatting_elements(parser);
1667
0
  set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
1668
0
  return result;
1669
0
}
1670
1671
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#close-the-cell
1672
// This holds the logic to determine whether we should close a <td> or a <th>.
1673
0
static bool close_current_cell(GumboParser* parser, const GumboToken* token) {
1674
0
  if (has_an_element_in_table_scope(parser, GUMBO_TAG_TD)) {
1675
0
    assert(!has_an_element_in_table_scope(parser, GUMBO_TAG_TH));
1676
0
    return close_table_cell(parser, token, GUMBO_TAG_TD);
1677
0
  } else {
1678
0
    assert(has_an_element_in_table_scope(parser, GUMBO_TAG_TH));
1679
0
    return close_table_cell(parser, token, GUMBO_TAG_TH);
1680
0
  }
1681
0
}
1682
1683
// The list of nodes in the "special" category:
1684
// http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#special
1685
0
static bool is_special_node(const GumboNode* node) {
1686
0
  assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE);
1687
0
  return node_tag_in_set(node,
1688
0
      (gumbo_tagset){
1689
0
          TAG(ADDRESS),
1690
0
          TAG(APPLET),
1691
0
          TAG(AREA),
1692
0
          TAG(ARTICLE),
1693
0
          TAG(ASIDE),
1694
0
          TAG(BASE),
1695
0
          TAG(BASEFONT),
1696
0
          TAG(BGSOUND),
1697
0
          TAG(BLOCKQUOTE),
1698
0
          TAG(BODY),
1699
0
          TAG(BR),
1700
0
          TAG(BUTTON),
1701
0
          TAG(CAPTION),
1702
0
          TAG(CENTER),
1703
0
          TAG(COL),
1704
0
          TAG(COLGROUP),
1705
0
          TAG(DD),
1706
0
          TAG(DETAILS),
1707
0
          TAG(DIR),
1708
0
          TAG(DIV),
1709
0
          TAG(DL),
1710
0
          TAG(DT),
1711
0
          TAG(EMBED),
1712
0
          TAG(FIELDSET),
1713
0
          TAG(FIGCAPTION),
1714
0
          TAG(FIGURE),
1715
0
          TAG(FOOTER),
1716
0
          TAG(FORM),
1717
0
          TAG(FRAME),
1718
0
          TAG(FRAMESET),
1719
0
          TAG(H1),
1720
0
          TAG(H2),
1721
0
          TAG(H3),
1722
0
          TAG(H4),
1723
0
          TAG(H5),
1724
0
          TAG(H6),
1725
0
          TAG(HEAD),
1726
0
          TAG(HEADER),
1727
0
          TAG(HGROUP),
1728
0
          TAG(HR),
1729
0
          TAG(HTML),
1730
0
          TAG(IFRAME),
1731
0
          TAG(IMG),
1732
0
          TAG(INPUT),
1733
0
          TAG(LI),
1734
0
          TAG(LINK),
1735
0
          TAG(LISTING),
1736
0
          TAG(MARQUEE),
1737
0
          TAG(MENU),
1738
0
          TAG(META),
1739
0
          TAG(NAV),
1740
0
          TAG(NOEMBED),
1741
0
          TAG(NOFRAMES),
1742
0
          TAG(NOSCRIPT),
1743
0
          TAG(OBJECT),
1744
0
          TAG(OL),
1745
0
          TAG(P),
1746
0
          TAG(PARAM),
1747
0
          TAG(PLAINTEXT),
1748
0
          TAG(PRE),
1749
0
          TAG(SCRIPT),
1750
0
          TAG(SECTION),
1751
0
          TAG(STYLE),
1752
0
          TAG(SUMMARY),
1753
0
          TAG(TABLE),
1754
0
          TAG(TBODY),
1755
0
          TAG(TD),
1756
0
          TAG(TEMPLATE),
1757
0
          TAG(TEXTAREA),
1758
0
          TAG(TFOOT),
1759
0
          TAG(TH),
1760
0
          TAG(THEAD),
1761
0
          TAG(TITLE),
1762
0
          TAG(TR),
1763
0
          TAG(UL),
1764
0
          TAG(WBR),
1765
0
          TAG(XMP),
1766
0
          TAG_MATHML(MI),
1767
0
          TAG_MATHML(MO),
1768
0
          TAG_MATHML(MN),
1769
0
          TAG_MATHML(MS),
1770
0
          TAG_MATHML(MTEXT),
1771
0
          TAG_MATHML(ANNOTATION_XML),
1772
0
          TAG_SVG(FOREIGNOBJECT),
1773
0
          TAG_SVG(DESC)
1774
0
      });
1775
0
}
1776
1777
// Implicitly closes currently open elements until it reaches an element with
1778
// the
1779
// specified qualified name.  If the elements closed are in the set handled by
1780
// generate_implied_end_tags, this is normal operation and this function returns
1781
// true.  Otherwise, a parse error is recorded and this function returns false.
1782
static bool implicitly_close_tags(GumboParser* parser, GumboToken* token,
1783
0
    GumboNamespaceEnum target_ns, GumboTag target) {
1784
0
  bool result = true;
1785
0
  generate_implied_end_tags(parser, target);
1786
0
  if (!node_qualified_tag_is(get_current_node(parser), target_ns, target)) {
1787
0
    parser_add_parse_error(parser, token);
1788
0
    while (
1789
0
        !node_qualified_tag_is(get_current_node(parser), target_ns, target)) {
1790
0
      pop_current_node(parser);
1791
0
    }
1792
0
    result = false;
1793
0
  }
1794
0
  assert(node_qualified_tag_is(get_current_node(parser), target_ns, target));
1795
0
  pop_current_node(parser);
1796
0
  return result;
1797
0
}
1798
1799
// If the stack of open elements has a <p> tag in button scope, this acts as if
1800
// a </p> tag was encountered, implicitly closing tags.  Returns false if a
1801
// parse error occurs.  This is a convenience function because this particular
1802
// clause appears several times in the spec.
1803
static bool maybe_implicitly_close_p_tag(
1804
0
    GumboParser* parser, GumboToken* token) {
1805
0
  if (has_an_element_in_button_scope(parser, GUMBO_TAG_P)) {
1806
0
    return implicitly_close_tags(
1807
0
        parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_P);
1808
0
  }
1809
0
  return true;
1810
0
}
1811
1812
// Convenience function to encapsulate the logic for closing <li> or <dd>/<dt>
1813
// tags.  Pass true to is_li for handling <li> tags, false for <dd> and <dt>.
1814
static void maybe_implicitly_close_list_tag(
1815
0
    GumboParser* parser, GumboToken* token, bool is_li) {
1816
0
  GumboParserState* state = parser->_parser_state;
1817
0
  state->_frameset_ok = false;
1818
0
  for (int i = state->_open_elements.length; --i >= 0;) {
1819
0
    const GumboNode* node = state->_open_elements.data[i];
1820
0
    bool is_list_tag =
1821
0
        is_li ? node_html_tag_is(node, GUMBO_TAG_LI)
1822
0
              : node_tag_in_set(node, (gumbo_tagset){TAG(DD), TAG(DT)});
1823
0
    if (is_list_tag) {
1824
0
      implicitly_close_tags(
1825
0
          parser, token, node->v.element.tag_namespace, node->v.element.tag);
1826
0
      return;
1827
0
    }
1828
0
    if (is_special_node(node) &&
1829
0
        !node_tag_in_set(
1830
0
            node, (gumbo_tagset){TAG(ADDRESS), TAG(DIV), TAG(P)})) {
1831
0
      return;
1832
0
    }
1833
0
  }
1834
0
}
1835
1836
static void merge_attributes(
1837
0
    GumboParser* parser, GumboToken* token, GumboNode* node) {
1838
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
1839
0
  assert(node->type == GUMBO_NODE_ELEMENT);
1840
0
  const GumboVector* token_attr = &token->v.start_tag.attributes;
1841
0
  GumboVector* node_attr = &node->v.element.attributes;
1842
1843
0
  for (unsigned int i = 0; i < token_attr->length; ++i) {
1844
0
    GumboAttribute* attr = token_attr->data[i];
1845
0
    if (!gumbo_get_attribute(node_attr, attr->name)) {
1846
      // Ownership of the attribute is transferred by this gumbo_vector_add,
1847
      // so it has to be nulled out of the original token so it doesn't get
1848
      // double-deleted.
1849
0
      gumbo_vector_add(parser, attr, node_attr);
1850
0
      token_attr->data[i] = NULL;
1851
0
    }
1852
0
  }
1853
  // When attributes are merged, it means the token has been ignored and merged
1854
  // with another token, so we need to free its memory.  The attributes that are
1855
  // transferred need to be nulled-out in the vector above so that they aren't
1856
  // double-deleted.
1857
0
  gumbo_token_destroy(parser, token);
1858
1859
0
#ifndef NDEBUG
1860
  // Mark this sentinel so the assertion in the main loop knows it's been
1861
  // destroyed.
1862
0
  token->v.start_tag.attributes = kGumboEmptyVector;
1863
0
#endif
1864
0
}
1865
1866
0
const char* gumbo_normalize_svg_tagname(const GumboStringPiece* tag) {
1867
0
  for (size_t i = 0; i < sizeof(kSvgTagReplacements) / sizeof(ReplacementEntry);
1868
0
       ++i) {
1869
0
    const ReplacementEntry* entry = &kSvgTagReplacements[i];
1870
0
    if (gumbo_string_equals_ignore_case(tag, &entry->from)) {
1871
0
      return entry->to.data;
1872
0
    }
1873
0
  }
1874
0
  return NULL;
1875
0
}
1876
1877
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adjust-foreign-attributes
1878
// This destructively modifies any matching attributes on the token and sets the
1879
// namespace appropriately.
1880
0
static void adjust_foreign_attributes(GumboParser* parser, GumboToken* token) {
1881
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
1882
0
  const GumboVector* attributes = &token->v.start_tag.attributes;
1883
0
  for (size_t i = 0; i < sizeof(kForeignAttributeReplacements) /
1884
0
                             sizeof(NamespacedAttributeReplacement);
1885
0
       ++i) {
1886
0
    const NamespacedAttributeReplacement* entry =
1887
0
        &kForeignAttributeReplacements[i];
1888
0
    GumboAttribute* attr = gumbo_get_attribute(attributes, entry->from);
1889
0
    if (!attr) {
1890
0
      continue;
1891
0
    }
1892
0
    gumbo_parser_deallocate(parser, (void*) attr->name);
1893
0
    attr->attr_namespace = entry->attr_namespace;
1894
0
    attr->name = gumbo_copy_stringz(parser, entry->local_name);
1895
0
  }
1896
0
}
1897
1898
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#adjust-svg-attributes
1899
// This destructively modifies any matching attributes on the token.
1900
0
static void adjust_svg_attributes(GumboParser* parser, GumboToken* token) {
1901
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
1902
0
  const GumboVector* attributes = &token->v.start_tag.attributes;
1903
0
  for (size_t i = 0;
1904
0
       i < sizeof(kSvgAttributeReplacements) / sizeof(ReplacementEntry); ++i) {
1905
0
    const ReplacementEntry* entry = &kSvgAttributeReplacements[i];
1906
0
    GumboAttribute* attr = gumbo_get_attribute(attributes, entry->from.data);
1907
0
    if (!attr) {
1908
0
      continue;
1909
0
    }
1910
0
    gumbo_parser_deallocate(parser, (void*) attr->name);
1911
0
    attr->name = gumbo_copy_stringz(parser, entry->to.data);
1912
0
  }
1913
0
}
1914
1915
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#adjust-mathml-attributes
1916
// Note that this may destructively modify the token with the new attribute
1917
// value.
1918
0
static void adjust_mathml_attributes(GumboParser* parser, GumboToken* token) {
1919
0
  assert(token->type == GUMBO_TOKEN_START_TAG);
1920
0
  GumboAttribute* attr =
1921
0
      gumbo_get_attribute(&token->v.start_tag.attributes, "definitionurl");
1922
0
  if (!attr) {
1923
0
    return;
1924
0
  }
1925
0
  gumbo_parser_deallocate(parser, (void*) attr->name);
1926
0
  attr->name = gumbo_copy_stringz(parser, "definitionURL");
1927
0
}
1928
1929
static bool doctype_matches(const GumboTokenDocType* doctype,
1930
    const GumboStringPiece* public_id, const GumboStringPiece* system_id,
1931
0
    bool allow_missing_system_id) {
1932
0
  return !strcmp(doctype->public_identifier, public_id->data) &&
1933
0
         (allow_missing_system_id || doctype->has_system_identifier) &&
1934
0
         !strcmp(doctype->system_identifier, system_id->data);
1935
0
}
1936
1937
static bool maybe_add_doctype_error(
1938
0
    GumboParser* parser, const GumboToken* token) {
1939
0
  const GumboTokenDocType* doctype = &token->v.doc_type;
1940
0
  bool html_doctype = !strcmp(doctype->name, kDoctypeHtml.data);
1941
0
  if ((!html_doctype || doctype->has_public_identifier ||
1942
0
          (doctype->has_system_identifier &&
1943
0
              !strcmp(
1944
0
                  doctype->system_identifier, kSystemIdLegacyCompat.data))) &&
1945
0
      !(html_doctype && (doctype_matches(doctype, &kPublicIdHtml4_0,
1946
0
                             &kSystemIdRecHtml4_0, true) ||
1947
0
                            doctype_matches(doctype, &kPublicIdHtml4_01,
1948
0
                                &kSystemIdHtml4, true) ||
1949
0
                            doctype_matches(doctype, &kPublicIdXhtml1_0,
1950
0
                                &kSystemIdXhtmlStrict1_1, false) ||
1951
0
                            doctype_matches(doctype, &kPublicIdXhtml1_1,
1952
0
                                &kSystemIdXhtml1_1, false)))) {
1953
0
    parser_add_parse_error(parser, token);
1954
0
    return false;
1955
0
  }
1956
0
  return true;
1957
0
}
1958
1959
0
static void remove_from_parent(GumboParser* parser, GumboNode* node) {
1960
0
  if (!node->parent) {
1961
    // The node may not have a parent if, for example, it is a newly-cloned copy
1962
    // of an active formatting element.  DOM manipulations continue with the
1963
    // orphaned fragment of the DOM tree until it's appended/foster-parented to
1964
    // the common ancestor at the end of the adoption agency algorithm.
1965
0
    return;
1966
0
  }
1967
0
  assert(node->parent->type == GUMBO_NODE_ELEMENT);
1968
0
  GumboVector* children = &node->parent->v.element.children;
1969
0
  int index = gumbo_vector_index_of(children, node);
1970
0
  assert(index != -1);
1971
1972
0
  gumbo_vector_remove_at(parser, index, children);
1973
0
  node->parent = NULL;
1974
0
  node->index_within_parent = -1;
1975
0
  for (unsigned int i = index; i < children->length; ++i) {
1976
0
    GumboNode* child = children->data[i];
1977
0
    child->index_within_parent = i;
1978
0
  }
1979
0
}
1980
1981
static bool handle_any_other_end_tag(
1982
0
    GumboParser* parser, GumboToken* token, GumboTag end_tag) {
1983
0
  GumboParserState* state = parser->_parser_state;
1984
0
  assert(state->_open_elements.length > 0);
1985
0
  assert(node_html_tag_is(state->_open_elements.data[0], GUMBO_TAG_HTML));
1986
1987
  // Walk up the stack of open elements until we find one that either:
1988
  // a) Matches the tag name we saw
1989
  // b) Is in the "special" category.
1990
  // If we see a), implicitly close everything up to and including it.  If we
1991
  // see b), then record a parse error and leave the stack unchanged.
1992
0
  for (int i = state->_open_elements.length; --i >= 0;) {
1993
0
    const GumboNode* node = state->_open_elements.data[i];
1994
0
    if (node_html_tag_is(node, end_tag)) {
1995
0
      generate_implied_end_tags(parser, end_tag);
1996
0
      while (node != pop_current_node(parser))
1997
0
        ; // Pop everything.
1998
0
      return true;
1999
0
    } else if (is_special_node(node)) {
2000
0
      parser_add_parse_error(parser, token);
2001
0
      return false;
2002
0
    }
2003
0
  }
2004
2005
  // <html> is in the special category, so we should never get here.
2006
0
  assert(0);
2007
0
  return false;
2008
0
}
2009
2010
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser
2011
// Also described in the "in body" handling for end formatting tags.
2012
static bool adoption_agency_algorithm(
2013
0
    GumboParser* parser, GumboToken* token, GumboTag subject) {
2014
0
  GumboParserState* state = parser->_parser_state;
2015
0
  gumbo_debug("Entering adoption agency algorithm.\n");
2016
  // Step 1.
2017
0
  GumboNode* current_node = get_current_node(parser);
2018
0
  if (current_node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML &&
2019
0
      current_node->v.element.tag == subject &&
2020
0
      gumbo_vector_index_of(
2021
0
          &state->_active_formatting_elements, current_node) == -1) {
2022
0
    pop_current_node(parser);
2023
0
    return false;
2024
0
  }
2025
  // Steps 2-4 & 20:
2026
0
  for (unsigned int i = 0; i < 8; ++i) {
2027
    // Step 5.
2028
0
    GumboNode* formatting_node = NULL;
2029
0
    int formatting_node_in_open_elements = -1;
2030
0
    for (int j = state->_active_formatting_elements.length; --j >= 0;) {
2031
0
      GumboNode* current_node = state->_active_formatting_elements.data[j];
2032
0
      if (current_node == &kActiveFormattingScopeMarker) {
2033
0
        gumbo_debug("Broke on scope marker; no active formatting element.\n");
2034
0
        break;
2035
0
      }
2036
0
      if (node_html_tag_is(current_node, subject)) {
2037
        // Found it.
2038
0
        formatting_node = current_node;
2039
0
        formatting_node_in_open_elements =
2040
0
            gumbo_vector_index_of(&state->_open_elements, formatting_node);
2041
0
        gumbo_debug("Formatting element of tag %s at %d.\n",
2042
0
            gumbo_normalized_tagname(subject),
2043
0
            formatting_node_in_open_elements);
2044
0
        break;
2045
0
      }
2046
0
    }
2047
0
    if (!formatting_node) {
2048
0
      gumbo_debug("No active formatting elements; running any-other-end-tag.\n");
2049
0
      return handle_any_other_end_tag(parser, token, subject);
2050
0
    }
2051
2052
    // Step 6
2053
0
    if (formatting_node_in_open_elements == -1) {
2054
0
      gumbo_debug("Formatting node not on stack of open elements.\n");
2055
0
      parser_add_parse_error(parser, token);
2056
0
      gumbo_vector_remove(
2057
0
          parser, formatting_node, &state->_active_formatting_elements);
2058
0
      return false;
2059
0
    }
2060
2061
    // Step 7
2062
0
    if (!has_an_element_in_scope(parser, formatting_node->v.element.tag)) {
2063
0
      parser_add_parse_error(parser, token);
2064
0
      gumbo_debug("Element not in scope.\n");
2065
0
      return false;
2066
0
    }
2067
2068
    // Step 8
2069
0
    if (formatting_node != get_current_node(parser)) {
2070
0
      parser_add_parse_error(parser, token);  // But continue onwards.
2071
0
    }
2072
0
    assert(formatting_node);
2073
0
    assert(!node_html_tag_is(formatting_node, GUMBO_TAG_HTML));
2074
0
    assert(!node_html_tag_is(formatting_node, GUMBO_TAG_BODY));
2075
2076
    // Step 9 & 10
2077
0
    GumboNode* furthest_block = NULL;
2078
0
    for (unsigned int j = formatting_node_in_open_elements;
2079
0
         j < state->_open_elements.length; ++j) {
2080
0
      assert(j > 0);
2081
0
      GumboNode* current = state->_open_elements.data[j];
2082
0
      if (is_special_node(current)) {
2083
        // Step 9.
2084
0
        furthest_block = current;
2085
0
        break;
2086
0
      }
2087
0
    }
2088
0
    if (!furthest_block) {
2089
      // Step 10.
2090
0
      while (get_current_node(parser) != formatting_node) {
2091
0
        pop_current_node(parser);
2092
0
      }
2093
      // And the formatting element itself.
2094
0
      pop_current_node(parser);
2095
0
      gumbo_vector_remove(
2096
0
          parser, formatting_node, &state->_active_formatting_elements);
2097
0
      return false;
2098
0
    }
2099
0
    assert(!node_html_tag_is(furthest_block, GUMBO_TAG_HTML));
2100
0
    assert(furthest_block);
2101
2102
    // Step 11.
2103
    // Elements may be moved and reparented by this algorithm, so
2104
    // common_ancestor is not necessarily the same as formatting_node->parent.
2105
0
    GumboNode* common_ancestor =
2106
0
        state->_open_elements.data[gumbo_vector_index_of(&state->_open_elements,
2107
0
                                       formatting_node) -
2108
0
                                   1];
2109
0
    gumbo_debug("Common ancestor tag = %s, furthest block tag = %s.\n",
2110
0
        gumbo_normalized_tagname(common_ancestor->v.element.tag),
2111
0
        gumbo_normalized_tagname(furthest_block->v.element.tag));
2112
2113
    // Step 12.
2114
0
    int bookmark = gumbo_vector_index_of(
2115
0
                       &state->_active_formatting_elements, formatting_node) +
2116
0
                   1;
2117
0
    gumbo_debug("Bookmark at %d.\n", bookmark);
2118
    // Step 13.
2119
0
    GumboNode* node = furthest_block;
2120
0
    GumboNode* last_node = furthest_block;
2121
    // Must be stored explicitly, in case node is removed from the stack of open
2122
    // elements, to handle step 9.4.
2123
0
    int saved_node_index = gumbo_vector_index_of(&state->_open_elements, node);
2124
0
    assert(saved_node_index > 0);
2125
    // Step 13.1.
2126
0
    for (int j = 0;;) {
2127
      // Step 13.2.
2128
0
      ++j;
2129
      // Step 13.3.
2130
0
      int node_index = gumbo_vector_index_of(&state->_open_elements, node);
2131
0
      gumbo_debug(
2132
0
          "Current index: %d, last index: %d.\n", node_index, saved_node_index);
2133
0
      if (node_index == -1) {
2134
0
        node_index = saved_node_index;
2135
0
      }
2136
0
      saved_node_index = --node_index;
2137
0
      assert(node_index > 0);
2138
0
      assert((unsigned int) node_index < state->_open_elements.capacity);
2139
0
      node = state->_open_elements.data[node_index];
2140
0
      assert(node->parent);
2141
0
      if (node == formatting_node) {
2142
        // Step 13.4.
2143
0
        break;
2144
0
      }
2145
0
      int formatting_index =
2146
0
          gumbo_vector_index_of(&state->_active_formatting_elements, node);
2147
0
      if (j > 3 && formatting_index != -1) {
2148
        // Step 13.5.
2149
0
        gumbo_debug("Removing formatting element at %d.\n", formatting_index);
2150
0
        gumbo_vector_remove_at(
2151
0
            parser, formatting_index, &state->_active_formatting_elements);
2152
        // Removing the element shifts all indices over by one, so we may need
2153
        // to move the bookmark.
2154
0
        if (formatting_index < bookmark) {
2155
0
          --bookmark;
2156
0
          gumbo_debug("Moving bookmark to %d.\n", bookmark);
2157
0
        }
2158
0
        continue;
2159
0
      }
2160
0
      if (formatting_index == -1) {
2161
        // Step 13.6.
2162
0
        gumbo_vector_remove_at(parser, node_index, &state->_open_elements);
2163
0
        continue;
2164
0
      }
2165
      // Step 13.7.
2166
      // "common ancestor as the intended parent" doesn't actually mean insert
2167
      // it into the common ancestor; that happens below.
2168
0
      node = clone_node(parser, node, GUMBO_INSERTION_ADOPTION_AGENCY_CLONED);
2169
0
      assert(formatting_index >= 0);
2170
0
      state->_active_formatting_elements.data[formatting_index] = node;
2171
0
      assert(node_index >= 0);
2172
0
      state->_open_elements.data[node_index] = node;
2173
      // Step 13.8.
2174
0
      if (last_node == furthest_block) {
2175
0
        bookmark = formatting_index + 1;
2176
0
        gumbo_debug("Bookmark moved to %d.\n", bookmark);
2177
0
        assert((unsigned int) bookmark <= state->_active_formatting_elements.length);
2178
0
      }
2179
      // Step 13.9.
2180
0
      last_node->parse_flags |= GUMBO_INSERTION_ADOPTION_AGENCY_MOVED;
2181
0
      remove_from_parent(parser, last_node);
2182
0
      append_node(parser, node, last_node);
2183
      // Step 13.10.
2184
0
      last_node = node;
2185
0
    }  // Step 13.11.
2186
2187
    // Step 14.
2188
0
    gumbo_debug("Removing %s node from parent ",
2189
0
        gumbo_normalized_tagname(last_node->v.element.tag));
2190
0
    remove_from_parent(parser, last_node);
2191
0
    last_node->parse_flags |= GUMBO_INSERTION_ADOPTION_AGENCY_MOVED;
2192
0
    InsertionLocation location =
2193
0
        get_appropriate_insertion_location(parser, common_ancestor);
2194
0
    gumbo_debug("and inserting it into %s.\n",
2195
0
        gumbo_normalized_tagname(location.target->v.element.tag));
2196
0
    insert_node(parser, last_node, location);
2197
2198
    // Step 15.
2199
0
    GumboNode* new_formatting_node = clone_node(
2200
0
        parser, formatting_node, GUMBO_INSERTION_ADOPTION_AGENCY_CLONED);
2201
0
    formatting_node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG;
2202
2203
    // Step 16.  Instead of appending nodes one-by-one, we swap the children
2204
    // vector of furthest_block with the empty children of new_formatting_node,
2205
    // reducing memory traffic and allocations.  We still have to reset their
2206
    // parent pointers, though.
2207
0
    GumboVector temp = new_formatting_node->v.element.children;
2208
0
    new_formatting_node->v.element.children =
2209
0
        furthest_block->v.element.children;
2210
0
    furthest_block->v.element.children = temp;
2211
2212
0
    temp = new_formatting_node->v.element.children;
2213
0
    for (unsigned int i = 0; i < temp.length; ++i) {
2214
0
      GumboNode* child = temp.data[i];
2215
0
      child->parent = new_formatting_node;
2216
0
    }
2217
2218
    // Step 17.
2219
0
    append_node(parser, furthest_block, new_formatting_node);
2220
2221
    // Step 18.
2222
    // If the formatting node was before the bookmark, it may shift over all
2223
    // indices after it, so we need to explicitly find the index and possibly
2224
    // adjust the bookmark.
2225
0
    int formatting_node_index = gumbo_vector_index_of(
2226
0
        &state->_active_formatting_elements, formatting_node);
2227
0
    assert(formatting_node_index != -1);
2228
0
    if (formatting_node_index < bookmark) {
2229
0
      gumbo_debug(
2230
0
          "Formatting node at %d is before bookmark at %d; decrementing.\n",
2231
0
          formatting_node_index, bookmark);
2232
0
      --bookmark;
2233
0
    }
2234
0
    gumbo_vector_remove_at(
2235
0
        parser, formatting_node_index, &state->_active_formatting_elements);
2236
0
    assert(bookmark >= 0);
2237
0
    assert((unsigned int) bookmark <= state->_active_formatting_elements.length);
2238
0
    gumbo_vector_insert_at(parser, new_formatting_node, bookmark,
2239
0
        &state->_active_formatting_elements);
2240
2241
    // Step 19.
2242
0
    gumbo_vector_remove(parser, formatting_node, &state->_open_elements);
2243
0
    int insert_at =
2244
0
        gumbo_vector_index_of(&state->_open_elements, furthest_block) + 1;
2245
0
    assert(insert_at >= 0);
2246
0
    assert((unsigned int) insert_at <= state->_open_elements.length);
2247
0
    gumbo_vector_insert_at(
2248
0
        parser, new_formatting_node, insert_at, &state->_open_elements);
2249
0
  }  // Step 20.
2250
0
  return true;
2251
0
}
2252
2253
// This is here to clean up memory when the spec says "Ignore current token."
2254
0
static void ignore_token(GumboParser* parser) {
2255
0
  GumboToken* token = parser->_parser_state->_current_token;
2256
  // Ownership of the token's internal buffers are normally transferred to the
2257
  // element, but if no element is emitted (as happens in non-verbatim-mode
2258
  // when a token is ignored), we need to free it here to prevent a memory
2259
  // leak.
2260
0
  gumbo_token_destroy(parser, token);
2261
0
#ifndef NDEBUG
2262
0
  if (token->type == GUMBO_TOKEN_START_TAG) {
2263
    // Mark this sentinel so the assertion in the main loop knows it's been
2264
    // destroyed.
2265
0
    token->v.start_tag.attributes = kGumboEmptyVector;
2266
0
  }
2267
0
#endif
2268
0
}
2269
2270
// http://www.whatwg.org/specs/web-apps/current-work/complete/the-end.html
2271
0
static void finish_parsing(GumboParser* parser) {
2272
0
  gumbo_debug("Finishing parsing");
2273
0
  maybe_flush_text_node_buffer(parser);
2274
0
  GumboParserState* state = parser->_parser_state;
2275
0
  for (GumboNode* node = pop_current_node(parser); node;
2276
0
       node = pop_current_node(parser)) {
2277
0
    if ((node_html_tag_is(node, GUMBO_TAG_BODY) && state->_closed_body_tag) ||
2278
0
        (node_html_tag_is(node, GUMBO_TAG_HTML) && state->_closed_html_tag)) {
2279
0
      continue;
2280
0
    }
2281
0
    node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG;
2282
0
  }
2283
0
  while (pop_current_node(parser))
2284
0
    ;  // Pop them all.
2285
0
}
2286
2287
0
static bool handle_initial(GumboParser* parser, GumboToken* token) {
2288
0
  GumboDocument* document = &get_document_node(parser)->v.document;
2289
0
  if (token->type == GUMBO_TOKEN_WHITESPACE) {
2290
0
    ignore_token(parser);
2291
0
    return true;
2292
0
  } else if (is_token_commentlike(token)) {
2293
0
    append_commentlike_node(parser, get_document_node(parser), token);
2294
0
    return true;
2295
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
2296
0
    document->has_doctype = true;
2297
0
    document->name = token->v.doc_type.name;
2298
0
    document->public_identifier = token->v.doc_type.public_identifier;
2299
0
    document->system_identifier = token->v.doc_type.system_identifier;
2300
0
    document->doc_type_quirks_mode = compute_quirks_mode(&token->v.doc_type);
2301
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HTML);
2302
0
    return maybe_add_doctype_error(parser, token);
2303
0
  }
2304
0
  parser_add_parse_error(parser, token);
2305
0
  document->doc_type_quirks_mode = GUMBO_DOCTYPE_QUIRKS;
2306
0
  set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HTML);
2307
0
  parser->_parser_state->_reprocess_current_token = true;
2308
0
  return true;
2309
0
}
2310
2311
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-before-html-insertion-mode
2312
0
static bool handle_before_html(GumboParser* parser, GumboToken* token) {
2313
0
  if (token->type == GUMBO_TOKEN_DOCTYPE) {
2314
0
    parser_add_parse_error(parser, token);
2315
0
    ignore_token(parser);
2316
0
    return false;
2317
0
  } else if (is_token_commentlike(token)) {
2318
0
    append_commentlike_node(parser, get_document_node(parser), token);
2319
0
    return true;
2320
0
  } else if (token->type == GUMBO_TOKEN_WHITESPACE) {
2321
0
    ignore_token(parser);
2322
0
    return true;
2323
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
2324
0
    GumboNode* html_node = insert_element_from_token(parser, token);
2325
0
    parser->_output->root = html_node;
2326
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HEAD);
2327
0
    return true;
2328
0
  } else if (token->type == GUMBO_TOKEN_END_TAG &&
2329
0
             !tag_in(token, false,
2330
0
                 (gumbo_tagset){TAG(HEAD), TAG(BODY), TAG(HTML), TAG(BR)})) {
2331
0
    parser_add_parse_error(parser, token);
2332
0
    ignore_token(parser);
2333
0
    return false;
2334
0
  } else {
2335
0
    GumboNode* html_node = insert_element_of_tag_type(
2336
0
        parser, GUMBO_TAG_HTML, GUMBO_INSERTION_IMPLIED);
2337
0
    assert(html_node);
2338
0
    parser->_output->root = html_node;
2339
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HEAD);
2340
0
    parser->_parser_state->_reprocess_current_token = true;
2341
0
    return true;
2342
0
  }
2343
0
}
2344
2345
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-before-head-insertion-mode
2346
0
static bool handle_before_head(GumboParser* parser, GumboToken* token) {
2347
0
  if (token->type == GUMBO_TOKEN_DOCTYPE) {
2348
0
    parser_add_parse_error(parser, token);
2349
0
    ignore_token(parser);
2350
0
    return false;
2351
0
  } else if (is_token_commentlike(token)) {
2352
0
    append_commentlike_node(parser, get_current_node(parser), token);
2353
0
    return true;
2354
0
  } else if (token->type == GUMBO_TOKEN_WHITESPACE) {
2355
0
    ignore_token(parser);
2356
0
    return true;
2357
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD)) {
2358
0
    GumboNode* node = insert_element_from_token(parser, token);
2359
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
2360
0
    parser->_parser_state->_head_element = node;
2361
0
    return true;
2362
0
  } else if (token->type == GUMBO_TOKEN_END_TAG &&
2363
0
             !tag_in(token, false,
2364
0
                 (gumbo_tagset){TAG(HEAD), TAG(BODY), TAG(HTML), TAG(BR)})) {
2365
0
    parser_add_parse_error(parser, token);
2366
0
    ignore_token(parser);
2367
0
    return false;
2368
0
  } else {
2369
0
    GumboNode* node = insert_element_of_tag_type(
2370
0
        parser, GUMBO_TAG_HEAD, GUMBO_INSERTION_IMPLIED);
2371
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
2372
0
    parser->_parser_state->_head_element = node;
2373
0
    parser->_parser_state->_reprocess_current_token = true;
2374
0
    return true;
2375
0
  }
2376
0
}
2377
2378
// Forward declarations because of mutual dependencies.
2379
static bool handle_token(GumboParser* parser, GumboToken* token);
2380
static bool handle_in_body(GumboParser* parser, GumboToken* token);
2381
2382
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inhead
2383
0
static bool handle_in_head(GumboParser* parser, GumboToken* token) {
2384
0
  if (token->type == GUMBO_TOKEN_WHITESPACE) {
2385
0
    insert_text_token(parser, token);
2386
0
    return true;
2387
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
2388
0
    parser_add_parse_error(parser, token);
2389
0
    ignore_token(parser);
2390
0
    return false;
2391
0
  } else if (is_token_commentlike(token)) {
2392
0
    append_commentlike_node(parser, get_current_node(parser), token);
2393
0
    return true;
2394
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
2395
0
    return handle_in_body(parser, token);
2396
0
  } else if (tag_in(token, kStartTag,
2397
0
                 (gumbo_tagset){TAG(BASE), TAG(BASEFONT), TAG(BGSOUND),
2398
0
                     TAG(LINK)})) {
2399
0
    insert_element_from_token(parser, token);
2400
0
    pop_current_node(parser);
2401
0
    acknowledge_self_closing_tag(parser);
2402
0
    return true;
2403
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_META)) {
2404
0
    insert_element_from_token(parser, token);
2405
0
    pop_current_node(parser);
2406
0
    acknowledge_self_closing_tag(parser);
2407
    // NOTE(jdtang): Gumbo handles only UTF-8, so the encoding clause of the
2408
    // spec doesn't apply.  If clients want to handle meta-tag re-encoding, they
2409
    // should specifically look for that string in the document and re-encode it
2410
    // before passing to Gumbo.
2411
0
    return true;
2412
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TITLE)) {
2413
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RCDATA);
2414
0
    return true;
2415
0
  } else if (tag_in(
2416
0
                 token, kStartTag, (gumbo_tagset){TAG(NOFRAMES), TAG(STYLE)})) {
2417
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
2418
0
    return true;
2419
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_NOSCRIPT)) {
2420
0
    insert_element_from_token(parser, token);
2421
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD_NOSCRIPT);
2422
0
    return true;
2423
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_SCRIPT)) {
2424
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_SCRIPT);
2425
0
    return true;
2426
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_HEAD)) {
2427
0
    GumboNode* head = pop_current_node(parser);
2428
0
    AVOID_UNUSED_VARIABLE_WARNING(head);
2429
0
    assert(node_html_tag_is(head, GUMBO_TAG_HEAD));
2430
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD);
2431
0
    return true;
2432
0
  } else if (tag_in(token, kEndTag,
2433
0
                 (gumbo_tagset){TAG(BODY), TAG(HTML), TAG(BR)})) {
2434
0
    pop_current_node(parser);
2435
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD);
2436
0
    parser->_parser_state->_reprocess_current_token = true;
2437
0
    return true;
2438
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TEMPLATE)) {
2439
0
    insert_element_from_token(parser, token);
2440
0
    add_formatting_element(parser, &kActiveFormattingScopeMarker);
2441
0
    parser->_parser_state->_frameset_ok = false;
2442
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TEMPLATE);
2443
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TEMPLATE);
2444
0
    return true;
2445
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) {
2446
0
    if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
2447
0
      parser_add_parse_error(parser, token);
2448
0
      ignore_token(parser);
2449
0
      return false;
2450
0
    }
2451
0
    generate_all_implied_end_tags_thoroughly(parser);
2452
0
    bool success = true;
2453
0
    if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_TEMPLATE)) {
2454
0
      parser_add_parse_error(parser, token);
2455
0
      success = false;
2456
0
    }
2457
0
    while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_TEMPLATE))
2458
0
      ;
2459
0
    clear_active_formatting_elements(parser);
2460
0
    pop_template_insertion_mode(parser);
2461
0
    reset_insertion_mode_appropriately(parser);
2462
0
    return success;
2463
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD) ||
2464
0
             (token->type == GUMBO_TOKEN_END_TAG)) {
2465
0
    parser_add_parse_error(parser, token);
2466
0
    ignore_token(parser);
2467
0
    return false;
2468
0
  } else {
2469
0
    pop_current_node(parser);
2470
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD);
2471
0
    parser->_parser_state->_reprocess_current_token = true;
2472
0
    return true;
2473
0
  }
2474
0
  return true;
2475
0
}
2476
2477
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inheadnoscript
2478
0
static bool handle_in_head_noscript(GumboParser* parser, GumboToken* token) {
2479
0
  if (token->type == GUMBO_TOKEN_DOCTYPE) {
2480
0
    parser_add_parse_error(parser, token);
2481
0
    return false;
2482
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
2483
0
    return handle_in_body(parser, token);
2484
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_NOSCRIPT)) {
2485
0
    const GumboNode* node = pop_current_node(parser);
2486
0
    assert(node_html_tag_is(node, GUMBO_TAG_NOSCRIPT));
2487
0
    AVOID_UNUSED_VARIABLE_WARNING(node);
2488
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
2489
0
    return true;
2490
0
  } else if (token->type == GUMBO_TOKEN_WHITESPACE ||
2491
0
             is_token_commentlike(token) ||
2492
0
             tag_in(token, kStartTag,
2493
0
                 (gumbo_tagset){
2494
0
                     TAG(BASEFONT),
2495
0
                     TAG(BGSOUND),
2496
0
                     TAG(LINK),
2497
0
                     TAG(META),
2498
0
                     TAG(NOFRAMES),
2499
0
                     TAG(STYLE)
2500
0
                 })) {
2501
0
    return handle_in_head(parser, token);
2502
0
  } else if (tag_in(
2503
0
                 token, kStartTag, (gumbo_tagset){TAG(HEAD), TAG(NOSCRIPT)}) ||
2504
0
             (token->type == GUMBO_TOKEN_END_TAG &&
2505
0
                 !tag_is(token, kEndTag, GUMBO_TAG_BR))) {
2506
0
    parser_add_parse_error(parser, token);
2507
0
    ignore_token(parser);
2508
0
    return false;
2509
0
  } else {
2510
0
    parser_add_parse_error(parser, token);
2511
0
    const GumboNode* node = pop_current_node(parser);
2512
0
    assert(node_html_tag_is(node, GUMBO_TAG_NOSCRIPT));
2513
0
    AVOID_UNUSED_VARIABLE_WARNING(node);
2514
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD);
2515
0
    parser->_parser_state->_reprocess_current_token = true;
2516
0
    return false;
2517
0
  }
2518
0
}
2519
2520
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-head-insertion-mode
2521
0
static bool handle_after_head(GumboParser* parser, GumboToken* token) {
2522
0
  GumboParserState* state = parser->_parser_state;
2523
0
  if (token->type == GUMBO_TOKEN_WHITESPACE) {
2524
0
    insert_text_token(parser, token);
2525
0
    return true;
2526
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
2527
0
    parser_add_parse_error(parser, token);
2528
0
    ignore_token(parser);
2529
0
    return false;
2530
0
  } else if (is_token_commentlike(token)) {
2531
0
    append_commentlike_node(parser, get_current_node(parser), token);
2532
0
    return true;
2533
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
2534
0
    return handle_in_body(parser, token);
2535
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_BODY)) {
2536
0
    insert_element_from_token(parser, token);
2537
0
    state->_frameset_ok = false;
2538
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
2539
0
    return true;
2540
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) {
2541
0
    insert_element_from_token(parser, token);
2542
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_FRAMESET);
2543
0
    return true;
2544
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){HEAD_TAGS})) {
2545
0
    parser_add_parse_error(parser, token);
2546
0
    assert(state->_head_element != NULL);
2547
    // This must be flushed before we push the head element on, as there may be
2548
    // pending character tokens that should be attached to the root.
2549
0
    maybe_flush_text_node_buffer(parser);
2550
0
    gumbo_vector_add(parser, state->_head_element, &state->_open_elements);
2551
0
    bool result = handle_in_head(parser, token);
2552
0
    gumbo_vector_remove(parser, state->_head_element, &state->_open_elements);
2553
0
    return result;
2554
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) {
2555
0
    return handle_in_head(parser, token);
2556
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD) ||
2557
0
             (token->type == GUMBO_TOKEN_END_TAG &&
2558
0
                 !tag_in(token, kEndTag,
2559
0
                     (gumbo_tagset){TAG(BODY), TAG(HTML), TAG(BR)}))) {
2560
0
    parser_add_parse_error(parser, token);
2561
0
    ignore_token(parser);
2562
0
    return false;
2563
0
  } else {
2564
0
    insert_element_of_tag_type(parser, GUMBO_TAG_BODY, GUMBO_INSERTION_IMPLIED);
2565
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
2566
0
    state->_reprocess_current_token = true;
2567
0
    return true;
2568
0
  }
2569
0
}
2570
2571
0
static void destroy_node(GumboParser* parser, GumboNode* node) {
2572
0
  switch (node->type) {
2573
0
    case GUMBO_NODE_DOCUMENT: {
2574
0
      GumboDocument* doc = &node->v.document;
2575
0
      for (unsigned int i = 0; i < doc->children.length; ++i) {
2576
0
        destroy_node(parser, doc->children.data[i]);
2577
0
      }
2578
0
      gumbo_parser_deallocate(parser, (void*) doc->children.data);
2579
0
      gumbo_parser_deallocate(parser, (void*) doc->name);
2580
0
      gumbo_parser_deallocate(parser, (void*) doc->public_identifier);
2581
0
      gumbo_parser_deallocate(parser, (void*) doc->system_identifier);
2582
0
    } break;
2583
0
    case GUMBO_NODE_TEMPLATE:
2584
0
    case GUMBO_NODE_ELEMENT:
2585
0
      for (unsigned int i = 0; i < node->v.element.attributes.length; ++i) {
2586
0
        gumbo_destroy_attribute(parser, node->v.element.attributes.data[i]);
2587
0
      }
2588
0
      gumbo_parser_deallocate(parser, node->v.element.attributes.data);
2589
0
      for (unsigned int i = 0; i < node->v.element.children.length; ++i) {
2590
0
        destroy_node(parser, node->v.element.children.data[i]);
2591
0
      }
2592
0
      gumbo_parser_deallocate(parser, node->v.element.children.data);
2593
0
      break;
2594
0
    case GUMBO_NODE_TEXT:
2595
0
    case GUMBO_NODE_CDATA:
2596
0
    case GUMBO_NODE_COMMENT:
2597
0
    case GUMBO_NODE_WHITESPACE:
2598
0
    case GUMBO_NODE_PROCESSING_INSTRUCTION:
2599
0
      gumbo_parser_deallocate(parser, (void*) node->v.text.text);
2600
0
      break;
2601
0
  }
2602
0
  gumbo_parser_deallocate(parser, node);
2603
0
}
2604
2605
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inbody
2606
0
static bool handle_in_body(GumboParser* parser, GumboToken* token) {
2607
0
  GumboParserState* state = parser->_parser_state;
2608
0
  assert(state->_open_elements.length > 0);
2609
0
  if (token->type == GUMBO_TOKEN_NULL) {
2610
0
    parser_add_parse_error(parser, token);
2611
0
    ignore_token(parser);
2612
0
    return false;
2613
0
  } else if (token->type == GUMBO_TOKEN_WHITESPACE) {
2614
0
    reconstruct_active_formatting_elements(parser);
2615
0
    insert_text_token(parser, token);
2616
0
    return true;
2617
0
  } else if (token->type == GUMBO_TOKEN_CHARACTER ||
2618
0
             token->type == GUMBO_TOKEN_CDATA) {
2619
0
    reconstruct_active_formatting_elements(parser);
2620
0
    insert_text_token(parser, token);
2621
0
    set_frameset_not_ok(parser);
2622
0
    return true;
2623
0
  } else if (is_token_commentlike(token)) {
2624
0
    append_commentlike_node(parser, get_current_node(parser), token);
2625
0
    return true;
2626
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
2627
0
    parser_add_parse_error(parser, token);
2628
0
    ignore_token(parser);
2629
0
    return false;
2630
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
2631
0
    parser_add_parse_error(parser, token);
2632
0
    if (has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
2633
0
      ignore_token(parser);
2634
0
      return false;
2635
0
    }
2636
0
    assert(parser->_output->root != NULL);
2637
0
    assert(parser->_output->root->type == GUMBO_NODE_ELEMENT);
2638
0
    merge_attributes(parser, token, parser->_output->root);
2639
0
    return false;
2640
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){HEAD_TAGS}) ||
2641
0
             tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) {
2642
0
    return handle_in_head(parser, token);
2643
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_BODY)) {
2644
0
    parser_add_parse_error(parser, token);
2645
0
    if (state->_open_elements.length < 2 ||
2646
0
        !node_html_tag_is(state->_open_elements.data[1], GUMBO_TAG_BODY) ||
2647
0
        has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
2648
0
      ignore_token(parser);
2649
0
      return false;
2650
0
    }
2651
0
    state->_frameset_ok = false;
2652
0
    merge_attributes(parser, token, state->_open_elements.data[1]);
2653
0
    return false;
2654
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) {
2655
0
    parser_add_parse_error(parser, token);
2656
0
    if (state->_open_elements.length < 2 ||
2657
0
        !node_html_tag_is(state->_open_elements.data[1], GUMBO_TAG_BODY) ||
2658
0
        !state->_frameset_ok) {
2659
0
      ignore_token(parser);
2660
0
      return false;
2661
0
    }
2662
    // Save the body node for later removal.
2663
0
    GumboNode* body_node = state->_open_elements.data[1];
2664
2665
    // Pop all nodes except root HTML element.
2666
0
    GumboNode* node;
2667
0
    do {
2668
0
      node = pop_current_node(parser);
2669
0
    } while (node != state->_open_elements.data[1]);
2670
2671
    // Removing & destroying the body node is going to kill any nodes that have
2672
    // been added to the list of active formatting elements, and so we should
2673
    // clear it to prevent a use-after-free if the list of active formatting
2674
    // elements is reconstructed afterwards.  This may happen if whitespace
2675
    // follows the </frameset>.
2676
0
    clear_active_formatting_elements(parser);
2677
2678
    // Remove the body node.  We may want to factor this out into a generic
2679
    // helper, but right now this is the only code that needs to do this.
2680
0
    GumboVector* children = &parser->_output->root->v.element.children;
2681
0
    for (unsigned int i = 0; i < children->length; ++i) {
2682
0
      if (children->data[i] == body_node) {
2683
0
        gumbo_vector_remove_at(parser, i, children);
2684
0
        break;
2685
0
      }
2686
0
    }
2687
0
    destroy_node(parser, body_node);
2688
2689
    // Insert the <frameset>, and switch the insertion mode.
2690
0
    insert_element_from_token(parser, token);
2691
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_FRAMESET);
2692
0
    return true;
2693
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
2694
0
    for (unsigned int i = 0; i < state->_open_elements.length; ++i) {
2695
0
      if (!node_tag_in_set(state->_open_elements.data[i],
2696
0
          (gumbo_tagset){
2697
0
              TAG(DD),
2698
0
              TAG(DT),
2699
0
              TAG(LI),
2700
0
              TAG(P),
2701
0
              TAG(TBODY),
2702
0
              TAG(TD),
2703
0
              TAG(TFOOT),
2704
0
              TAG(TH),
2705
0
              TAG(THEAD),
2706
0
              TAG(TR),
2707
0
              TAG(BODY),
2708
0
              TAG(HTML)
2709
0
          })) {
2710
0
        parser_add_parse_error(parser, token);
2711
0
      }
2712
0
    }
2713
0
    if (get_current_template_insertion_mode(parser) !=
2714
0
        GUMBO_INSERTION_MODE_INITIAL) {
2715
0
      return handle_in_template(parser, token);
2716
0
    }
2717
0
    return true;
2718
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(HTML)})) {
2719
0
    if (!has_an_element_in_scope(parser, GUMBO_TAG_BODY)) {
2720
0
      parser_add_parse_error(parser, token);
2721
0
      ignore_token(parser);
2722
0
      return false;
2723
0
    }
2724
0
    bool success = true;
2725
0
    for (unsigned int i = 0; i < state->_open_elements.length; ++i) {
2726
0
      if (!node_tag_in_set(state->_open_elements.data[i],
2727
0
              (gumbo_tagset){
2728
0
                  TAG(DD),
2729
0
                  TAG(DT),
2730
0
                  TAG(LI),
2731
0
                  TAG(OPTGROUP),
2732
0
                  TAG(OPTION),
2733
0
                  TAG(P),
2734
0
                  TAG(RB),
2735
0
                  TAG(RP),
2736
0
                  TAG(RT),
2737
0
                  TAG(RTC),
2738
0
                  TAG(TBODY),
2739
0
                  TAG(TD),
2740
0
                  TAG(TFOOT),
2741
0
                  TAG(TH),
2742
0
                  TAG(THEAD),
2743
0
                  TAG(TR),
2744
0
                  TAG(BODY),
2745
0
                  TAG(HTML)
2746
0
              })) {
2747
0
        parser_add_parse_error(parser, token);
2748
0
        success = false;
2749
0
        break;
2750
0
      }
2751
0
    }
2752
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_BODY);
2753
0
    if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
2754
0
      parser->_parser_state->_reprocess_current_token = true;
2755
0
    } else {
2756
0
      GumboNode* body = state->_open_elements.data[1];
2757
0
      assert(node_html_tag_is(body, GUMBO_TAG_BODY));
2758
0
      record_end_of_element(state->_current_token, &body->v.element);
2759
0
    }
2760
0
    return success;
2761
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){
2762
0
        TAG(ADDRESS),
2763
0
        TAG(ARTICLE),
2764
0
        TAG(ASIDE),
2765
0
        TAG(BLOCKQUOTE),
2766
0
        TAG(CENTER),
2767
0
        TAG(DETAILS),
2768
0
        TAG(DIALOG),
2769
0
        TAG(DIR),
2770
0
        TAG(DIV),
2771
0
        TAG(DL),
2772
0
        TAG(FIELDSET),
2773
0
        TAG(FIGCAPTION),
2774
0
        TAG(FIGURE),
2775
0
        TAG(FOOTER),
2776
0
        TAG(HEADER),
2777
0
        TAG(HGROUP),
2778
0
        TAG(MENU),
2779
0
        TAG(MAIN),
2780
0
        TAG(NAV),
2781
0
        TAG(OL),
2782
0
        TAG(P),
2783
0
        TAG(SECTION),
2784
0
        TAG(SUMMARY),
2785
0
        TAG(UL),
2786
0
        TAG(SEARCH)
2787
0
  })) {
2788
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2789
0
    insert_element_from_token(parser, token);
2790
0
    return result;
2791
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3),
2792
0
                                          TAG(H4), TAG(H5), TAG(H6)})) {
2793
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2794
0
    if (node_tag_in_set(
2795
0
            get_current_node(parser), (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3),
2796
0
                                          TAG(H4), TAG(H5), TAG(H6)})) {
2797
0
      parser_add_parse_error(parser, token);
2798
0
      pop_current_node(parser);
2799
0
      result = false;
2800
0
    }
2801
0
    insert_element_from_token(parser, token);
2802
0
    return result;
2803
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(PRE), TAG(LISTING)})) {
2804
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2805
0
    insert_element_from_token(parser, token);
2806
0
    state->_ignore_next_linefeed = true;
2807
0
    state->_frameset_ok = false;
2808
0
    return result;
2809
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_FORM)) {
2810
0
    if (state->_form_element != NULL &&
2811
0
        !has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
2812
0
      gumbo_debug("Ignoring nested form.\n");
2813
0
      parser_add_parse_error(parser, token);
2814
0
      ignore_token(parser);
2815
0
      return false;
2816
0
    }
2817
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2818
0
    GumboNode* form_element = insert_element_from_token(parser, token);
2819
0
    if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
2820
0
      state->_form_element = form_element;
2821
0
    }
2822
0
    return result;
2823
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_LI)) {
2824
0
    maybe_implicitly_close_list_tag(parser, token, true);
2825
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2826
0
    insert_element_from_token(parser, token);
2827
0
    return result;
2828
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(DD), TAG(DT)})) {
2829
0
    maybe_implicitly_close_list_tag(parser, token, false);
2830
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2831
0
    insert_element_from_token(parser, token);
2832
0
    return result;
2833
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_SELECTEDCONTENT)) {
2834
0
    GumboNode* selectedcontent = insert_element_from_token(parser, token);
2835
0
    state->_selectedcontent_target = selectedcontent;
2836
0
    state->_selectedcontent_state = GUMBO_SELECTEDCONTENT_EMPTY;
2837
0
    return true;
2838
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_SELECTEDCONTENT)) {
2839
0
    implicitly_close_tags(parser, token, GUMBO_NAMESPACE_HTML, token->v.end_tag);
2840
0
    state->_selectedcontent_target = NULL;
2841
0
    return true;
2842
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_PLAINTEXT)) {
2843
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
2844
0
    insert_element_from_token(parser, token);
2845
0
    gumbo_tokenizer_set_state(parser, GUMBO_LEX_PLAINTEXT);
2846
0
    return result;
2847
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_BUTTON)) {
2848
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_BUTTON)) {
2849
0
      parser_add_parse_error(parser, token);
2850
0
      implicitly_close_tags(
2851
0
          parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_BUTTON);
2852
0
      state->_reprocess_current_token = true;
2853
0
      return false;
2854
0
    }
2855
0
    reconstruct_active_formatting_elements(parser);
2856
0
    insert_element_from_token(parser, token);
2857
0
    state->_frameset_ok = false;
2858
0
    return true;
2859
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){
2860
0
        TAG(ADDRESS),
2861
0
        TAG(ARTICLE),
2862
0
        TAG(ASIDE),
2863
0
        TAG(BLOCKQUOTE),
2864
0
        TAG(BUTTON),
2865
0
        TAG(CENTER),
2866
0
        TAG(DETAILS),
2867
0
        TAG(DIALOG),
2868
0
        TAG(DIR),
2869
0
        TAG(DIV),
2870
0
        TAG(DL),
2871
0
        TAG(FIELDSET),
2872
0
        TAG(FIGCAPTION),
2873
0
        TAG(FIGURE),
2874
0
        TAG(FOOTER),
2875
0
        TAG(HEADER),
2876
0
        TAG(HGROUP),
2877
0
        TAG(LISTING),
2878
0
        TAG(MAIN),
2879
0
        TAG(MENU),
2880
0
        TAG(NAV),
2881
0
        TAG(OL),
2882
0
        TAG(PRE),
2883
0
        TAG(SECTION),
2884
0
        TAG(SUMMARY),
2885
0
        TAG(UL),
2886
0
        TAG(SEARCH)
2887
0
  })) {
2888
0
    GumboTag tag = token->v.end_tag;
2889
0
    if (!has_an_element_in_scope(parser, tag)) {
2890
0
      parser_add_parse_error(parser, token);
2891
0
      ignore_token(parser);
2892
0
      return false;
2893
0
    }
2894
0
    implicitly_close_tags(
2895
0
        parser, token, GUMBO_NAMESPACE_HTML, token->v.end_tag);
2896
0
    return true;
2897
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_FORM)) {
2898
0
    if (has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
2899
0
      if (!has_an_element_in_scope(parser, GUMBO_TAG_FORM)) {
2900
0
        parser_add_parse_error(parser, token);
2901
0
        ignore_token(parser);
2902
0
        return false;
2903
0
      }
2904
0
      bool success = true;
2905
0
      generate_implied_end_tags(parser, GUMBO_TAG_LAST);
2906
0
      if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_FORM)) {
2907
0
        parser_add_parse_error(parser, token);
2908
0
        return false;
2909
0
      }
2910
0
      while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_FORM))
2911
0
        ;
2912
0
      return success;
2913
0
    } else {
2914
0
      bool result = true;
2915
0
      GumboNode* node = state->_form_element;
2916
0
      assert(!node || node->type == GUMBO_NODE_ELEMENT);
2917
0
      state->_form_element = NULL;
2918
0
      if (!node || !has_node_in_scope(parser, node)) {
2919
0
        gumbo_debug("Closing an unopened form.\n");
2920
0
        parser_add_parse_error(parser, token);
2921
0
        ignore_token(parser);
2922
0
        return false;
2923
0
      }
2924
      // Since we remove the form node without popping, we need to make sure
2925
      // that we flush any text nodes at the end of the form.
2926
0
      maybe_flush_text_node_buffer(parser);
2927
      // This differs from implicitly_close_tags because we remove *only* the
2928
      // <form> element; other nodes are left in scope.
2929
0
      generate_implied_end_tags(parser, GUMBO_TAG_LAST);
2930
0
      if (get_current_node(parser) == node) {
2931
0
        record_end_of_element(token, &node->v.element);
2932
0
      } else {
2933
0
        parser_add_parse_error(parser, token);
2934
0
        result = false;
2935
0
      }
2936
2937
0
      GumboVector* open_elements = &state->_open_elements;
2938
0
      int index = gumbo_vector_index_of(open_elements, node);
2939
0
      assert(index >= 0);
2940
0
      gumbo_vector_remove_at(parser, index, open_elements);
2941
0
      return result;
2942
0
    }
2943
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_P)) {
2944
0
    if (!has_an_element_in_button_scope(parser, GUMBO_TAG_P)) {
2945
0
      parser_add_parse_error(parser, token);
2946
      // reconstruct_active_formatting_elements(parser);
2947
0
      insert_element_of_tag_type(
2948
0
          parser, GUMBO_TAG_P, GUMBO_INSERTION_CONVERTED_FROM_END_TAG);
2949
0
      state->_reprocess_current_token = true;
2950
0
      return false;
2951
0
    }
2952
0
    return implicitly_close_tags(
2953
0
        parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_P);
2954
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_LI)) {
2955
0
    if (!has_an_element_in_list_scope(parser, GUMBO_TAG_LI)) {
2956
0
      parser_add_parse_error(parser, token);
2957
0
      ignore_token(parser);
2958
0
      return false;
2959
0
    }
2960
0
    return implicitly_close_tags(
2961
0
        parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_LI);
2962
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(DD), TAG(DT)})) {
2963
0
    assert(token->type == GUMBO_TOKEN_END_TAG);
2964
0
    GumboTag token_tag = token->v.end_tag;
2965
0
    if (!has_an_element_in_scope(parser, token_tag)) {
2966
0
      parser_add_parse_error(parser, token);
2967
0
      ignore_token(parser);
2968
0
      return false;
2969
0
    }
2970
0
    return implicitly_close_tags(
2971
0
        parser, token, GUMBO_NAMESPACE_HTML, token_tag);
2972
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3),
2973
0
                                        TAG(H4), TAG(H5), TAG(H6)})) {
2974
0
    if (!has_an_element_in_scope_with_tagname(
2975
0
            parser, 6, (GumboTag[]){GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3,
2976
0
                           GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6})) {
2977
      // No heading open; ignore the token entirely.
2978
0
      parser_add_parse_error(parser, token);
2979
0
      ignore_token(parser);
2980
0
      return false;
2981
0
    } else {
2982
0
      generate_implied_end_tags(parser, GUMBO_TAG_LAST);
2983
0
      const GumboNode* current_node = get_current_node(parser);
2984
0
      bool success = node_html_tag_is(current_node, token->v.end_tag);
2985
0
      if (!success) {
2986
        // There're children of the heading currently open; close them below and
2987
        // record a parse error.
2988
        // TODO(jdtang): Add a way to distinguish this error case from the one
2989
        // above.
2990
0
        parser_add_parse_error(parser, token);
2991
0
      }
2992
0
      do {
2993
0
        current_node = pop_current_node(parser);
2994
0
      } while (!node_tag_in_set(
2995
0
                   current_node, (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3),
2996
0
                                     TAG(H4), TAG(H5), TAG(H6)}));
2997
0
      return success;
2998
0
    }
2999
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_A)) {
3000
0
    bool success = true;
3001
0
    int last_a;
3002
0
    int has_matching_a = find_last_anchor_index(parser, &last_a);
3003
0
    if (has_matching_a) {
3004
0
      assert(has_matching_a == 1);
3005
0
      parser_add_parse_error(parser, token);
3006
0
      adoption_agency_algorithm(parser, token, GUMBO_TAG_A);
3007
      // The adoption agency algorithm usually removes all instances of <a>
3008
      // from the list of active formatting elements, but in case it doesn't,
3009
      // we're supposed to do this.  (The conditions where it might not are
3010
      // listed in the spec.)
3011
0
      if (find_last_anchor_index(parser, &last_a)) {
3012
0
        void* last_element = gumbo_vector_remove_at(
3013
0
            parser, last_a, &state->_active_formatting_elements);
3014
0
        gumbo_vector_remove(parser, last_element, &state->_open_elements);
3015
0
      }
3016
0
      success = false;
3017
0
    }
3018
0
    reconstruct_active_formatting_elements(parser);
3019
0
    add_formatting_element(parser, insert_element_from_token(parser, token));
3020
0
    return success;
3021
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){
3022
0
      TAG(B),
3023
0
      TAG(BIG),
3024
0
      TAG(CODE),
3025
0
      TAG(EM),
3026
0
      TAG(FONT),
3027
0
      TAG(I),
3028
0
      TAG(S),
3029
0
      TAG(SMALL),
3030
0
      TAG(STRIKE),
3031
0
      TAG(STRONG),
3032
0
      TAG(TT),
3033
0
      TAG(U)
3034
0
  })) {
3035
0
    reconstruct_active_formatting_elements(parser);
3036
0
    add_formatting_element(parser, insert_element_from_token(parser, token));
3037
0
    return true;
3038
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_NOBR)) {
3039
0
    bool result = true;
3040
0
    reconstruct_active_formatting_elements(parser);
3041
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_NOBR)) {
3042
0
      result = false;
3043
0
      parser_add_parse_error(parser, token);
3044
0
      adoption_agency_algorithm(parser, token, GUMBO_TAG_NOBR);
3045
0
      reconstruct_active_formatting_elements(parser);
3046
0
    }
3047
0
    insert_element_from_token(parser, token);
3048
0
    add_formatting_element(parser, get_current_node(parser));
3049
0
    return result;
3050
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){
3051
0
      TAG(A),
3052
0
      TAG(B),
3053
0
      TAG(BIG),
3054
0
      TAG(CODE),
3055
0
      TAG(EM),
3056
0
      TAG(FONT),
3057
0
      TAG(I),
3058
0
      TAG(NOBR),
3059
0
      TAG(S),
3060
0
      TAG(SMALL),
3061
0
      TAG(STRIKE),
3062
0
      TAG(STRONG),
3063
0
      TAG(TT),
3064
0
      TAG(U)
3065
0
  })) {
3066
0
    return adoption_agency_algorithm(parser, token, token->v.end_tag);
3067
0
  } else if (tag_in(token, kStartTag,
3068
0
                 (gumbo_tagset){TAG(APPLET), TAG(MARQUEE), TAG(OBJECT)})) {
3069
0
    reconstruct_active_formatting_elements(parser);
3070
0
    insert_element_from_token(parser, token);
3071
0
    add_formatting_element(parser, &kActiveFormattingScopeMarker);
3072
0
    set_frameset_not_ok(parser);
3073
0
    return true;
3074
0
  } else if (tag_in(token, kEndTag,
3075
0
                 (gumbo_tagset){TAG(APPLET), TAG(MARQUEE), TAG(OBJECT)})) {
3076
0
    GumboTag token_tag = token->v.end_tag;
3077
0
    if (!has_an_element_in_table_scope(parser, token_tag)) {
3078
0
      parser_add_parse_error(parser, token);
3079
0
      ignore_token(parser);
3080
0
      return false;
3081
0
    }
3082
0
    implicitly_close_tags(parser, token, GUMBO_NAMESPACE_HTML, token_tag);
3083
0
    clear_active_formatting_elements(parser);
3084
0
    return true;
3085
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TABLE)) {
3086
0
    if (get_document_node(parser)->v.document.doc_type_quirks_mode !=
3087
0
        GUMBO_DOCTYPE_QUIRKS) {
3088
0
      maybe_implicitly_close_p_tag(parser, token);
3089
0
    }
3090
0
    insert_element_from_token(parser, token);
3091
0
    set_frameset_not_ok(parser);
3092
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3093
0
    return true;
3094
0
  } else if (tag_in(token, kStartTag,
3095
0
                 (gumbo_tagset){TAG(AREA), TAG(BR), TAG(EMBED), TAG(IMG),
3096
0
                     TAG(IMAGE), TAG(KEYGEN), TAG(WBR)})) {
3097
0
    bool success = true;
3098
0
    if (tag_is(token, kStartTag, GUMBO_TAG_IMAGE)) {
3099
0
      success = false;
3100
0
      parser_add_parse_error(parser, token);
3101
0
      token->v.start_tag.tag = GUMBO_TAG_IMG;
3102
0
    }
3103
0
    reconstruct_active_formatting_elements(parser);
3104
0
    GumboNode* node = insert_element_from_token(parser, token);
3105
0
    if (tag_is(token, kStartTag, GUMBO_TAG_IMAGE)) {
3106
0
      success = false;
3107
0
      parser_add_parse_error(parser, token);
3108
0
      node->v.element.tag = GUMBO_TAG_IMG;
3109
0
      node->parse_flags |= GUMBO_INSERTION_FROM_IMAGE;
3110
0
    }
3111
0
    pop_current_node(parser);
3112
0
    acknowledge_self_closing_tag(parser);
3113
0
    set_frameset_not_ok(parser);
3114
0
    return success;
3115
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_INPUT)) {
3116
0
    if (is_fragment_parser(parser) &&
3117
0
        node_html_tag_is(state->_fragment_ctx, GUMBO_TAG_SELECT)) {
3118
0
      parser_add_parse_error(parser, token);
3119
0
      ignore_token(parser);
3120
0
      return false;
3121
0
    }
3122
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_SELECT)) {
3123
0
      parser_add_parse_error(parser, token);
3124
0
      while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_SELECT));
3125
0
      parser->_parser_state->_reprocess_current_token = true;
3126
0
      return false;
3127
0
    }
3128
0
    if (!attribute_matches(&token->v.start_tag.attributes, "type", "hidden")) {
3129
      // Must be before the element is inserted, as that takes ownership of the
3130
      // token's attribute vector.
3131
0
      set_frameset_not_ok(parser);
3132
0
    }
3133
0
    reconstruct_active_formatting_elements(parser);
3134
0
    insert_element_from_token(parser, token);
3135
0
    pop_current_node(parser);
3136
0
    acknowledge_self_closing_tag(parser);
3137
0
    return true;
3138
0
  } else if (tag_in(token, kStartTag,
3139
0
                 (gumbo_tagset){TAG(PARAM), TAG(SOURCE), TAG(TRACK)})) {
3140
0
    insert_element_from_token(parser, token);
3141
0
    pop_current_node(parser);
3142
0
    acknowledge_self_closing_tag(parser);
3143
0
    return true;
3144
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HR)) {
3145
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
3146
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_SELECT)) {
3147
0
      generate_implied_end_tags(parser, GUMBO_TAG_LAST);
3148
0
      if (has_an_element_in_scope_with_tagname(parser, 2, (GumboTag[]) {GUMBO_TAG_OPTION, GUMBO_TAG_OPTGROUP})) {
3149
0
        parser_add_parse_error(parser, token);
3150
0
      }
3151
0
    }
3152
0
    insert_element_from_token(parser, token);
3153
0
    pop_current_node(parser);
3154
0
    acknowledge_self_closing_tag(parser);
3155
0
    set_frameset_not_ok(parser);
3156
0
    return result;
3157
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_ISINDEX)) {
3158
0
    reconstruct_active_formatting_elements(parser);
3159
0
    insert_element_from_token(parser, token);
3160
0
    if (token->v.start_tag.is_self_closing) {
3161
0
      pop_current_node(parser);
3162
0
      acknowledge_self_closing_tag(parser);
3163
0
    }
3164
0
    return true;
3165
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TEXTAREA)) {
3166
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RCDATA);
3167
0
    parser->_parser_state->_ignore_next_linefeed = true;
3168
0
    set_frameset_not_ok(parser);
3169
0
    return true;
3170
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_XMP)) {
3171
0
    bool result = maybe_implicitly_close_p_tag(parser, token);
3172
0
    reconstruct_active_formatting_elements(parser);
3173
0
    set_frameset_not_ok(parser);
3174
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
3175
0
    return result;
3176
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_IFRAME)) {
3177
0
    set_frameset_not_ok(parser);
3178
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
3179
0
    return true;
3180
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_NOEMBED)) {
3181
0
    run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT);
3182
0
    return true;
3183
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_SELECT)) {
3184
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_SELECT)) {
3185
0
      parser_add_parse_error(parser, token);
3186
0
      ignore_token(parser);
3187
0
      while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_SELECT));
3188
0
    } else {
3189
0
      reconstruct_active_formatting_elements(parser);
3190
0
      insert_element_from_token(parser, token);
3191
0
      set_frameset_not_ok(parser);
3192
0
    }
3193
0
    return true;
3194
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_OPTION)) {
3195
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_SELECT)) {
3196
0
      generate_implied_end_tags(parser, GUMBO_TAG_OPTGROUP);
3197
0
      if (has_an_element_in_scope(parser, GUMBO_TAG_OPTION)) {
3198
0
        parser_add_parse_error(parser, token);
3199
0
      }
3200
0
    } else {
3201
0
      if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) {
3202
0
        pop_current_node(parser);
3203
0
      }
3204
0
    }
3205
0
    reconstruct_active_formatting_elements(parser);
3206
0
    insert_element_from_token(parser, token);
3207
0
    return true;
3208
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_OPTGROUP)) {
3209
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_SELECT)) {
3210
0
      generate_implied_end_tags(parser, GUMBO_TAG_LAST);
3211
0
      if (has_an_element_in_scope_with_tagname(parser, 2, (GumboTag[]) {
3212
0
            GUMBO_TAG_OPTION, GUMBO_TAG_OPTGROUP
3213
0
          })) {
3214
0
        parser_add_parse_error(parser, token);
3215
0
      }
3216
0
    } else {
3217
0
      if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) {
3218
0
        pop_current_node(parser);
3219
0
      }
3220
0
    }
3221
0
    reconstruct_active_formatting_elements(parser);
3222
0
    insert_element_from_token(parser, token);
3223
0
    return true;
3224
0
  } else if (tag_in(token, kStartTag,
3225
0
                 (gumbo_tagset){TAG(RB), TAG(RP), TAG(RT), TAG(RTC)})) {
3226
0
    bool success = true;
3227
0
    GumboTag exception =
3228
0
        tag_in(token, kStartTag, (gumbo_tagset){TAG(RT), TAG(RP)})
3229
0
            ? GUMBO_TAG_RTC
3230
0
            : GUMBO_TAG_LAST;
3231
0
    if (has_an_element_in_scope(parser, GUMBO_TAG_RUBY)) {
3232
0
      generate_implied_end_tags(parser, exception);
3233
0
    }
3234
0
    if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_RUBY) &&
3235
0
        !(exception == GUMBO_TAG_LAST ||
3236
0
            node_html_tag_is(get_current_node(parser), GUMBO_TAG_RTC))) {
3237
0
      parser_add_parse_error(parser, token);
3238
0
      success = false;
3239
0
    }
3240
0
    insert_element_from_token(parser, token);
3241
0
    return success;
3242
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_BR)) {
3243
0
    parser_add_parse_error(parser, token);
3244
0
    reconstruct_active_formatting_elements(parser);
3245
0
    insert_element_of_tag_type(
3246
0
        parser, GUMBO_TAG_BR, GUMBO_INSERTION_CONVERTED_FROM_END_TAG);
3247
0
    pop_current_node(parser);
3248
0
    return false;
3249
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_MATH)) {
3250
0
    reconstruct_active_formatting_elements(parser);
3251
0
    adjust_mathml_attributes(parser, token);
3252
0
    adjust_foreign_attributes(parser, token);
3253
0
    insert_foreign_element(parser, token, GUMBO_NAMESPACE_MATHML);
3254
0
    if (token->v.start_tag.is_self_closing) {
3255
0
      pop_current_node(parser);
3256
0
      acknowledge_self_closing_tag(parser);
3257
0
    }
3258
0
    return true;
3259
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_SVG)) {
3260
0
    reconstruct_active_formatting_elements(parser);
3261
0
    adjust_svg_attributes(parser, token);
3262
0
    adjust_foreign_attributes(parser, token);
3263
0
    insert_foreign_element(parser, token, GUMBO_NAMESPACE_SVG);
3264
0
    if (token->v.start_tag.is_self_closing) {
3265
0
      pop_current_node(parser);
3266
0
      acknowledge_self_closing_tag(parser);
3267
0
    }
3268
0
    return true;
3269
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){
3270
0
      TAG(CAPTION),
3271
0
      TAG(COL),
3272
0
      TAG(COLGROUP),
3273
0
      TAG(FRAME),
3274
0
      TAG(HEAD),
3275
0
      TAG(TBODY),
3276
0
      TAG(TD),
3277
0
      TAG(TFOOT),
3278
0
      TAG(TH),
3279
0
      TAG(THEAD),
3280
0
      TAG(TR)
3281
0
  })) {
3282
0
    parser_add_parse_error(parser, token);
3283
0
    ignore_token(parser);
3284
0
    return false;
3285
0
  } else if (token->type == GUMBO_TOKEN_START_TAG) {
3286
0
    reconstruct_active_formatting_elements(parser);
3287
0
    insert_element_from_token(parser, token);
3288
0
    return true;
3289
0
  } else {
3290
0
    assert(token->type == GUMBO_TOKEN_END_TAG);
3291
0
    bool success = handle_any_other_end_tag(parser, token, token->v.end_tag);
3292
0
    if (!success) {
3293
0
      ignore_token(parser);
3294
0
    }
3295
0
    return success;
3296
0
  }
3297
0
}
3298
3299
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incdata
3300
0
static bool handle_text(GumboParser* parser, GumboToken* token) {
3301
0
  if (token->type == GUMBO_TOKEN_CHARACTER ||
3302
0
      token->type == GUMBO_TOKEN_WHITESPACE) {
3303
0
    insert_text_token(parser, token);
3304
0
  } else {
3305
    // We provide only bare-bones script handling that doesn't involve any of
3306
    // the parser-pause/already-started/script-nesting flags or re-entrant
3307
    // invocations of the tokenizer.  Because the intended usage of this library
3308
    // is mostly for templating, refactoring, and static-analysis libraries, we
3309
    // provide the script body as a text-node child of the <script> element.
3310
    // This behavior doesn't support document.write of partial HTML elements,
3311
    // but should be adequate for almost all other scripting support.
3312
0
    if (token->type == GUMBO_TOKEN_EOF) {
3313
0
      parser_add_parse_error(parser, token);
3314
0
      parser->_parser_state->_reprocess_current_token = true;
3315
0
    }
3316
0
    pop_current_node(parser);
3317
0
    set_insertion_mode(parser, parser->_parser_state->_original_insertion_mode);
3318
0
  }
3319
0
  return true;
3320
0
}
3321
3322
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intable
3323
0
static bool handle_in_table(GumboParser* parser, GumboToken* token) {
3324
0
  GumboParserState* state = parser->_parser_state;
3325
0
  if (token->type == GUMBO_TOKEN_CHARACTER ||
3326
0
      token->type == GUMBO_TOKEN_WHITESPACE) {
3327
    // The "pending table character tokens" list described in the spec is
3328
    // nothing more than the TextNodeBufferState.  We accumulate text tokens as
3329
    // normal, except that when we go to flush them in the handle_in_table_text,
3330
    // we set _foster_parent_insertions if there're non-whitespace characters in
3331
    // the buffer.
3332
0
    assert(state->_text_node._buffer.length == 0);
3333
0
    state->_original_insertion_mode = state->_insertion_mode;
3334
0
    state->_reprocess_current_token = true;
3335
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_TEXT);
3336
0
    return true;
3337
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
3338
0
    parser_add_parse_error(parser, token);
3339
0
    ignore_token(parser);
3340
0
    return false;
3341
0
  } else if (is_token_commentlike(token)) {
3342
0
    append_commentlike_node(parser, get_current_node(parser), token);
3343
0
    return true;
3344
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_CAPTION)) {
3345
0
    clear_stack_to_table_context(parser);
3346
0
    add_formatting_element(parser, &kActiveFormattingScopeMarker);
3347
0
    insert_element_from_token(parser, token);
3348
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_CAPTION);
3349
0
    return true;
3350
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_COLGROUP)) {
3351
0
    clear_stack_to_table_context(parser);
3352
0
    insert_element_from_token(parser, token);
3353
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP);
3354
0
    return true;
3355
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) {
3356
0
    clear_stack_to_table_context(parser);
3357
0
    insert_element_of_tag_type(
3358
0
        parser, GUMBO_TAG_COLGROUP, GUMBO_INSERTION_IMPLIED);
3359
0
    parser->_parser_state->_reprocess_current_token = true;
3360
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP);
3361
0
    return true;
3362
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){
3363
0
      TAG(TBODY),
3364
0
      TAG(TFOOT),
3365
0
      TAG(THEAD),
3366
0
      TAG(TD),
3367
0
      TAG(TH),
3368
0
      TAG(TR)
3369
0
  })) {
3370
0
    clear_stack_to_table_context(parser);
3371
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
3372
0
    if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TD), TAG(TH), TAG(TR)})) {
3373
0
      insert_element_of_tag_type(
3374
0
          parser, GUMBO_TAG_TBODY, GUMBO_INSERTION_IMPLIED);
3375
0
      state->_reprocess_current_token = true;
3376
0
    } else {
3377
0
      insert_element_from_token(parser, token);
3378
0
    }
3379
0
    return true;
3380
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TABLE)) {
3381
0
    parser_add_parse_error(parser, token);
3382
0
    if (close_table(parser)) {
3383
0
      parser->_parser_state->_reprocess_current_token = true;
3384
0
    } else {
3385
0
      ignore_token(parser);
3386
0
    }
3387
0
    return false;
3388
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_TABLE)) {
3389
0
    if (!close_table(parser)) {
3390
0
      parser_add_parse_error(parser, token);
3391
0
      return false;
3392
0
    }
3393
0
    return true;
3394
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){
3395
0
      TAG(BODY),
3396
0
      TAG(CAPTION),
3397
0
      TAG(COL),
3398
0
      TAG(COLGROUP),
3399
0
      TAG(HTML),
3400
0
      TAG(TBODY),
3401
0
      TAG(TD),
3402
0
      TAG(TFOOT),
3403
0
      TAG(TH),
3404
0
      TAG(THEAD),
3405
0
      TAG(TR)
3406
0
  })) {
3407
0
    parser_add_parse_error(parser, token);
3408
0
    ignore_token(parser);
3409
0
    return false;
3410
0
  } else if (tag_in(token, kStartTag,
3411
0
                 (gumbo_tagset){TAG(STYLE), TAG(SCRIPT), TAG(TEMPLATE)}) ||
3412
0
             (tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE))) {
3413
0
    return handle_in_head(parser, token);
3414
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_INPUT) &&
3415
0
             attribute_matches(
3416
0
                 &token->v.start_tag.attributes, "type", "hidden")) {
3417
0
    parser_add_parse_error(parser, token);
3418
0
    insert_element_from_token(parser, token);
3419
0
    pop_current_node(parser);
3420
0
    return false;
3421
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_FORM)) {
3422
0
    parser_add_parse_error(parser, token);
3423
0
    if (state->_form_element || has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
3424
0
      ignore_token(parser);
3425
0
      return false;
3426
0
    }
3427
0
    state->_form_element = insert_element_from_token(parser, token);
3428
0
    pop_current_node(parser);
3429
0
    return false;
3430
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3431
0
    return handle_in_body(parser, token);
3432
0
  } else {
3433
0
    parser_add_parse_error(parser, token);
3434
0
    state->_foster_parent_insertions = true;
3435
0
    bool result = handle_in_body(parser, token);
3436
0
    state->_foster_parent_insertions = false;
3437
0
    return result;
3438
0
  }
3439
0
}
3440
3441
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intabletext
3442
0
static bool handle_in_table_text(GumboParser* parser, GumboToken* token) {
3443
0
  if (token->type == GUMBO_TOKEN_NULL) {
3444
0
    parser_add_parse_error(parser, token);
3445
0
    ignore_token(parser);
3446
0
    return false;
3447
0
  } else if (token->type == GUMBO_TOKEN_CHARACTER ||
3448
0
             token->type == GUMBO_TOKEN_WHITESPACE) {
3449
0
    insert_text_token(parser, token);
3450
0
    return true;
3451
0
  } else {
3452
0
    GumboParserState* state = parser->_parser_state;
3453
0
    GumboStringBuffer* buffer = &state->_text_node._buffer;
3454
    // Can't use strspn for this because GumboStringBuffers are not
3455
    // null-terminated.
3456
    // Note that TextNodeBuffer may contain UTF-8 characters, but the presence
3457
    // of any one byte that is not whitespace means we flip the flag, so this
3458
    // loop is still valid.
3459
0
    for (unsigned int i = 0; i < buffer->length; ++i) {
3460
0
      if (!isspace((unsigned char) buffer->data[i]) ||
3461
0
          buffer->data[i] == '\v') {
3462
0
        state->_foster_parent_insertions = true;
3463
0
        reconstruct_active_formatting_elements(parser);
3464
0
        break;
3465
0
      }
3466
0
    }
3467
0
    maybe_flush_text_node_buffer(parser);
3468
0
    state->_foster_parent_insertions = false;
3469
0
    state->_reprocess_current_token = true;
3470
0
    state->_insertion_mode = state->_original_insertion_mode;
3471
0
    return true;
3472
0
  }
3473
0
}
3474
3475
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incaption
3476
0
static bool handle_in_caption(GumboParser* parser, GumboToken* token) {
3477
0
  if (tag_is(token, kEndTag, GUMBO_TAG_CAPTION)) {
3478
0
    if (!has_an_element_in_table_scope(parser, GUMBO_TAG_CAPTION)) {
3479
0
      parser_add_parse_error(parser, token);
3480
0
      ignore_token(parser);
3481
0
      return false;
3482
0
    } else {
3483
0
      generate_implied_end_tags(parser, GUMBO_TAG_LAST);
3484
0
      bool result = true;
3485
0
      if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_CAPTION)) {
3486
0
        parser_add_parse_error(parser, token);
3487
0
      }
3488
0
      while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_CAPTION))
3489
0
        ;
3490
0
      clear_active_formatting_elements(parser);
3491
0
      set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3492
0
      return result;
3493
0
    }
3494
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){
3495
0
                     TAG(CAPTION),
3496
0
                     TAG(COL),
3497
0
                     TAG(COLGROUP),
3498
0
                     TAG(TBODY),
3499
0
                     TAG(TD),
3500
0
                     TAG(TFOOT),
3501
0
                     TAG(TH),
3502
0
                     TAG(THEAD),
3503
0
                     TAG(TR)
3504
0
                 }) ||
3505
0
             (tag_is(token, kEndTag, GUMBO_TAG_TABLE))) {
3506
0
    if (!has_an_element_in_table_scope(parser, GUMBO_TAG_CAPTION)) {
3507
0
      parser_add_parse_error(parser, token);
3508
0
      ignore_token(parser);
3509
0
      return false;
3510
0
    }
3511
0
    while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_CAPTION))
3512
0
      ;
3513
0
    clear_active_formatting_elements(parser);
3514
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3515
0
    parser->_parser_state->_reprocess_current_token = true;
3516
0
    return true;
3517
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){
3518
0
      TAG(BODY),
3519
0
      TAG(COL),
3520
0
      TAG(COLGROUP),
3521
0
      TAG(HTML),
3522
0
      TAG(TBODY),
3523
0
      TAG(TD),
3524
0
      TAG(TFOOT),
3525
0
      TAG(TH),
3526
0
      TAG(THEAD),
3527
0
      TAG(TR)
3528
0
  })) {
3529
0
    parser_add_parse_error(parser, token);
3530
0
    ignore_token(parser);
3531
0
    return false;
3532
0
  } else {
3533
0
    return handle_in_body(parser, token);
3534
0
  }
3535
0
}
3536
3537
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incolgroup
3538
0
static bool handle_in_column_group(GumboParser* parser, GumboToken* token) {
3539
0
  if (token->type == GUMBO_TOKEN_WHITESPACE) {
3540
0
    insert_text_token(parser, token);
3541
0
    return true;
3542
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
3543
0
    parser_add_parse_error(parser, token);
3544
0
    ignore_token(parser);
3545
0
    return false;
3546
0
  } else if (is_token_commentlike(token)) {
3547
0
    append_commentlike_node(parser, get_current_node(parser), token);
3548
0
    return true;
3549
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
3550
0
    return handle_in_body(parser, token);
3551
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) {
3552
0
    insert_element_from_token(parser, token);
3553
0
    pop_current_node(parser);
3554
0
    acknowledge_self_closing_tag(parser);
3555
0
    return true;
3556
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_COLGROUP)) {
3557
0
    if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_COLGROUP)) {
3558
0
      parser_add_parse_error(parser, token);
3559
0
      ignore_token(parser);
3560
0
      return false;
3561
0
    }
3562
0
    pop_current_node(parser);
3563
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3564
0
    return false;
3565
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_COL)) {
3566
0
    parser_add_parse_error(parser, token);
3567
0
    ignore_token(parser);
3568
0
    return false;
3569
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TEMPLATE) ||
3570
0
             tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) {
3571
0
    return handle_in_head(parser, token);
3572
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3573
0
    return handle_in_body(parser, token);
3574
0
  } else {
3575
0
    if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_COLGROUP)) {
3576
0
      parser_add_parse_error(parser, token);
3577
0
      ignore_token(parser);
3578
0
      return false;
3579
0
    }
3580
0
    pop_current_node(parser);
3581
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3582
0
    parser->_parser_state->_reprocess_current_token = true;
3583
0
    return true;
3584
0
  }
3585
0
}
3586
3587
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intbody
3588
0
static bool handle_in_table_body(GumboParser* parser, GumboToken* token) {
3589
0
  if (tag_is(token, kStartTag, GUMBO_TAG_TR)) {
3590
0
    clear_stack_to_table_body_context(parser);
3591
0
    insert_element_from_token(parser, token);
3592
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
3593
0
    return true;
3594
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TD), TAG(TH)})) {
3595
0
    parser_add_parse_error(parser, token);
3596
0
    clear_stack_to_table_body_context(parser);
3597
0
    insert_element_of_tag_type(parser, GUMBO_TAG_TR, GUMBO_INSERTION_IMPLIED);
3598
0
    parser->_parser_state->_reprocess_current_token = true;
3599
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
3600
0
    return false;
3601
0
  } else if (tag_in(token, kEndTag,
3602
0
                 (gumbo_tagset){TAG(TBODY), TAG(TFOOT), TAG(THEAD)})) {
3603
0
    if (!has_an_element_in_table_scope(parser, token->v.end_tag)) {
3604
0
      parser_add_parse_error(parser, token);
3605
0
      ignore_token(parser);
3606
0
      return false;
3607
0
    }
3608
0
    clear_stack_to_table_body_context(parser);
3609
0
    pop_current_node(parser);
3610
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3611
0
    return true;
3612
0
  } else if (tag_in(token, kStartTag,
3613
0
                 (gumbo_tagset){
3614
0
                     TAG(CAPTION),
3615
0
                     TAG(COL),
3616
0
                     TAG(COLGROUP),
3617
0
                     TAG(TBODY),
3618
0
                     TAG(TFOOT),
3619
0
                     TAG(THEAD)
3620
0
                 }) ||
3621
0
             tag_is(token, kEndTag, GUMBO_TAG_TABLE)) {
3622
0
    if (!(has_an_element_in_table_scope(parser, GUMBO_TAG_TBODY) ||
3623
0
            has_an_element_in_table_scope(parser, GUMBO_TAG_THEAD) ||
3624
0
            has_an_element_in_table_scope(parser, GUMBO_TAG_TFOOT))) {
3625
0
      parser_add_parse_error(parser, token);
3626
0
      ignore_token(parser);
3627
0
      return false;
3628
0
    }
3629
0
    clear_stack_to_table_body_context(parser);
3630
0
    pop_current_node(parser);
3631
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3632
0
    parser->_parser_state->_reprocess_current_token = true;
3633
0
    return true;
3634
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){
3635
0
      TAG(BODY),
3636
0
      TAG(CAPTION),
3637
0
      TAG(COL),
3638
0
      TAG(TR),
3639
0
      TAG(COLGROUP),
3640
0
      TAG(HTML),
3641
0
      TAG(TD),
3642
0
      TAG(TH)
3643
0
  })) {
3644
0
    parser_add_parse_error(parser, token);
3645
0
    ignore_token(parser);
3646
0
    return false;
3647
0
  } else {
3648
0
    return handle_in_table(parser, token);
3649
0
  }
3650
0
}
3651
3652
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intr
3653
0
static bool handle_in_row(GumboParser* parser, GumboToken* token) {
3654
0
  if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TH), TAG(TD)})) {
3655
0
    clear_stack_to_table_row_context(parser);
3656
0
    insert_element_from_token(parser, token);
3657
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_CELL);
3658
0
    add_formatting_element(parser, &kActiveFormattingScopeMarker);
3659
0
    return true;
3660
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_TR)) {
3661
0
    if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TR)) {
3662
0
      parser_add_parse_error(parser, token);
3663
0
      ignore_token(parser);
3664
0
      return false;
3665
0
    } else {
3666
0
      clear_stack_to_table_row_context(parser);
3667
0
      pop_current_node(parser);
3668
0
      set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
3669
0
      return true;
3670
0
    }
3671
0
  } else if (tag_in(token, kStartTag,
3672
0
                 (gumbo_tagset){
3673
0
                     TAG(CAPTION),
3674
0
                     TAG(COL),
3675
0
                     TAG(COLGROUP),
3676
0
                     TAG(TBODY),
3677
0
                     TAG(TFOOT),
3678
0
                     TAG(THEAD),
3679
0
                     TAG(TR)
3680
0
                 }) ||
3681
0
             tag_is(token, kEndTag, GUMBO_TAG_TABLE)) {
3682
0
    if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TR)) {
3683
0
      parser_add_parse_error(parser, token);
3684
0
      ignore_token(parser);
3685
0
      return false;
3686
0
    } else {
3687
0
      clear_stack_to_table_row_context(parser);
3688
0
      pop_current_node(parser);
3689
0
      set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
3690
0
      parser->_parser_state->_reprocess_current_token = true;
3691
0
      return true;
3692
0
    }
3693
0
  } else if (tag_in(token, kEndTag,
3694
0
                 (gumbo_tagset){TAG(TBODY), TAG(TFOOT), TAG(THEAD)})) {
3695
0
    if (!has_an_element_in_table_scope(parser, token->v.end_tag) ||
3696
0
        (!has_an_element_in_table_scope(parser, GUMBO_TAG_TR))) {
3697
0
      parser_add_parse_error(parser, token);
3698
0
      ignore_token(parser);
3699
0
      return false;
3700
0
    } else {
3701
0
      clear_stack_to_table_row_context(parser);
3702
0
      pop_current_node(parser);
3703
0
      set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
3704
0
      parser->_parser_state->_reprocess_current_token = true;
3705
0
      return true;
3706
0
    }
3707
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){
3708
0
      TAG(BODY),
3709
0
      TAG(CAPTION),
3710
0
      TAG(COL),
3711
0
      TAG(COLGROUP),
3712
0
      TAG(HTML),
3713
0
      TAG(TD),
3714
0
      TAG(TH)
3715
0
  })) {
3716
0
    parser_add_parse_error(parser, token);
3717
0
    ignore_token(parser);
3718
0
    return false;
3719
0
  } else {
3720
0
    return handle_in_table(parser, token);
3721
0
  }
3722
0
}
3723
3724
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intd
3725
0
static bool handle_in_cell(GumboParser* parser, GumboToken* token) {
3726
0
  if (tag_in(token, kEndTag, (gumbo_tagset){TAG(TD), TAG(TH)})) {
3727
0
    GumboTag token_tag = token->v.end_tag;
3728
0
    if (!has_an_element_in_table_scope(parser, token_tag)) {
3729
0
      parser_add_parse_error(parser, token);
3730
0
      ignore_token(parser);
3731
0
      return false;
3732
0
    }
3733
0
    return close_table_cell(parser, token, token_tag);
3734
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){
3735
0
      TAG(CAPTION),
3736
0
      TAG(COL),
3737
0
      TAG(COLGROUP),
3738
0
      TAG(TBODY),
3739
0
      TAG(TD),
3740
0
      TAG(TFOOT),
3741
0
      TAG(TH),
3742
0
      TAG(THEAD),
3743
0
      TAG(TR)
3744
0
  })) {
3745
0
    gumbo_debug("Handling <td> in cell.\n");
3746
0
    if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TH) &&
3747
0
        !has_an_element_in_table_scope(parser, GUMBO_TAG_TD)) {
3748
0
      gumbo_debug("Bailing out because there's no <td> or <th> in scope.\n");
3749
0
      parser_add_parse_error(parser, token);
3750
0
      ignore_token(parser);
3751
0
      return false;
3752
0
    }
3753
0
    parser->_parser_state->_reprocess_current_token = true;
3754
0
    return close_current_cell(parser, token);
3755
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(CAPTION),
3756
0
                                        TAG(COL), TAG(COLGROUP), TAG(HTML)})) {
3757
0
    parser_add_parse_error(parser, token);
3758
0
    ignore_token(parser);
3759
0
    return false;
3760
0
  } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(TABLE), TAG(TBODY),
3761
0
                                        TAG(TFOOT), TAG(THEAD), TAG(TR)})) {
3762
0
    if (!has_an_element_in_table_scope(parser, token->v.end_tag)) {
3763
0
      parser_add_parse_error(parser, token);
3764
0
      ignore_token(parser);
3765
0
      return false;
3766
0
    }
3767
0
    parser->_parser_state->_reprocess_current_token = true;
3768
0
    return close_current_cell(parser, token);
3769
0
  } else {
3770
0
    return handle_in_body(parser, token);
3771
0
  }
3772
0
}
3773
3774
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intemplate
3775
0
static bool handle_in_template(GumboParser* parser, GumboToken* token) {
3776
0
  GumboParserState* state = parser->_parser_state;
3777
0
  if (token->type == GUMBO_TOKEN_WHITESPACE ||
3778
0
      token->type == GUMBO_TOKEN_CHARACTER ||
3779
0
      is_token_commentlike(token) || token->type == GUMBO_TOKEN_NULL ||
3780
0
      token->type == GUMBO_TOKEN_DOCTYPE) {
3781
0
    return handle_in_body(parser, token);
3782
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){HEAD_TAGS}) ||
3783
0
             tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) {
3784
0
    return handle_in_head(parser, token);
3785
0
  } else if (tag_in(
3786
0
                 token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COLGROUP),
3787
0
                                       TAG(TBODY), TAG(TFOOT), TAG(THEAD)})) {
3788
0
    pop_template_insertion_mode(parser);
3789
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3790
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE);
3791
0
    state->_reprocess_current_token = true;
3792
0
    return true;
3793
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) {
3794
0
    pop_template_insertion_mode(parser);
3795
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP);
3796
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP);
3797
0
    state->_reprocess_current_token = true;
3798
0
    return true;
3799
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_TR)) {
3800
0
    pop_template_insertion_mode(parser);
3801
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
3802
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY);
3803
0
    state->_reprocess_current_token = true;
3804
0
    return true;
3805
0
  } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TD), TAG(TH)})) {
3806
0
    pop_template_insertion_mode(parser);
3807
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
3808
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW);
3809
0
    state->_reprocess_current_token = true;
3810
0
    return true;
3811
0
  } else if (token->type == GUMBO_TOKEN_START_TAG) {
3812
0
    pop_template_insertion_mode(parser);
3813
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
3814
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
3815
0
    state->_reprocess_current_token = true;
3816
0
    return true;
3817
0
  } else if (token->type == GUMBO_TOKEN_END_TAG) {
3818
0
    parser_add_parse_error(parser, token);
3819
0
    ignore_token(parser);
3820
0
    return false;
3821
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3822
0
    if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) {
3823
      // Stop parsing.
3824
0
      return true;
3825
0
    }
3826
0
    parser_add_parse_error(parser, token);
3827
0
    while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_TEMPLATE))
3828
0
      ;
3829
0
    clear_active_formatting_elements(parser);
3830
0
    pop_template_insertion_mode(parser);
3831
0
    reset_insertion_mode_appropriately(parser);
3832
0
    state->_reprocess_current_token = true;
3833
0
    return false;
3834
0
  } else {
3835
0
    assert(0);
3836
0
    return false;
3837
0
  }
3838
0
}
3839
3840
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-afterbody
3841
0
static bool handle_after_body(GumboParser* parser, GumboToken* token) {
3842
0
  if (token->type == GUMBO_TOKEN_WHITESPACE ||
3843
0
      tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
3844
0
    return handle_in_body(parser, token);
3845
0
  } else if (is_token_commentlike(token)) {
3846
0
    GumboNode* html_node = parser->_output->root;
3847
0
    assert(html_node != NULL);
3848
0
    append_commentlike_node(parser, html_node, token);
3849
0
    return true;
3850
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
3851
0
    parser_add_parse_error(parser, token);
3852
0
    ignore_token(parser);
3853
0
    return false;
3854
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
3855
    /* fragment case: ignore the closing HTML token */
3856
0
    if (is_fragment_parser(parser)) {
3857
0
      parser_add_parse_error(parser, token);
3858
0
      ignore_token(parser);
3859
0
      return false;
3860
0
    }
3861
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_AFTER_BODY);
3862
0
    GumboNode* html = parser->_parser_state->_open_elements.data[0];
3863
0
    assert(node_html_tag_is(html, GUMBO_TAG_HTML));
3864
0
    record_end_of_element(
3865
0
        parser->_parser_state->_current_token, &html->v.element);
3866
0
    return true;
3867
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3868
0
    return true;
3869
0
  } else {
3870
0
    parser_add_parse_error(parser, token);
3871
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
3872
0
    parser->_parser_state->_reprocess_current_token = true;
3873
0
    return false;
3874
0
  }
3875
0
}
3876
3877
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inframeset
3878
0
static bool handle_in_frameset(GumboParser* parser, GumboToken* token) {
3879
0
  if (token->type == GUMBO_TOKEN_WHITESPACE) {
3880
0
    insert_text_token(parser, token);
3881
0
    return true;
3882
0
  } else if (is_token_commentlike(token)) {
3883
0
    append_commentlike_node(parser, get_current_node(parser), token);
3884
0
    return true;
3885
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
3886
0
    parser_add_parse_error(parser, token);
3887
0
    ignore_token(parser);
3888
0
    return false;
3889
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
3890
0
    return handle_in_body(parser, token);
3891
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) {
3892
0
    insert_element_from_token(parser, token);
3893
0
    return true;
3894
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_FRAMESET)) {
3895
0
    if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) {
3896
0
      parser_add_parse_error(parser, token);
3897
0
      ignore_token(parser);
3898
0
      return false;
3899
0
    }
3900
0
    pop_current_node(parser);
3901
0
    if (!is_fragment_parser(parser) &&
3902
0
        !node_html_tag_is(get_current_node(parser), GUMBO_TAG_FRAMESET)) {
3903
0
      set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_FRAMESET);
3904
0
    }
3905
0
    return true;
3906
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAME)) {
3907
0
    insert_element_from_token(parser, token);
3908
0
    pop_current_node(parser);
3909
0
    acknowledge_self_closing_tag(parser);
3910
0
    return true;
3911
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) {
3912
0
    return handle_in_head(parser, token);
3913
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3914
0
    if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) {
3915
0
      parser_add_parse_error(parser, token);
3916
0
      return false;
3917
0
    }
3918
0
    return true;
3919
0
  } else {
3920
0
    parser_add_parse_error(parser, token);
3921
0
    ignore_token(parser);
3922
0
    return false;
3923
0
  }
3924
0
}
3925
3926
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-afterframeset
3927
0
static bool handle_after_frameset(GumboParser* parser, GumboToken* token) {
3928
0
  if (token->type == GUMBO_TOKEN_WHITESPACE) {
3929
0
    insert_text_token(parser, token);
3930
0
    return true;
3931
0
  } else if (is_token_commentlike(token)) {
3932
0
    append_commentlike_node(parser, get_current_node(parser), token);
3933
0
    return true;
3934
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE) {
3935
0
    parser_add_parse_error(parser, token);
3936
0
    ignore_token(parser);
3937
0
    return false;
3938
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
3939
0
    return handle_in_body(parser, token);
3940
0
  } else if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
3941
0
    GumboNode* html = parser->_parser_state->_open_elements.data[0];
3942
0
    assert(node_html_tag_is(html, GUMBO_TAG_HTML));
3943
0
    record_end_of_element(
3944
0
        parser->_parser_state->_current_token, &html->v.element);
3945
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_AFTER_FRAMESET);
3946
0
    return true;
3947
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) {
3948
0
    return handle_in_head(parser, token);
3949
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3950
0
    return true;
3951
0
  } else {
3952
0
    parser_add_parse_error(parser, token);
3953
0
    ignore_token(parser);
3954
0
    return false;
3955
0
  }
3956
0
}
3957
3958
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-after-body-insertion-mode
3959
0
static bool handle_after_after_body(GumboParser* parser, GumboToken* token) {
3960
0
  if (is_token_commentlike(token)) {
3961
0
    append_commentlike_node(parser, get_document_node(parser), token);
3962
0
    return true;
3963
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE ||
3964
0
             token->type == GUMBO_TOKEN_WHITESPACE ||
3965
0
             tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
3966
0
    return handle_in_body(parser, token);
3967
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3968
0
    return true;
3969
0
  } else {
3970
0
    parser_add_parse_error(parser, token);
3971
0
    set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY);
3972
0
    parser->_parser_state->_reprocess_current_token = true;
3973
0
    return false;
3974
0
  }
3975
0
}
3976
3977
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-after-frameset-insertion-mode
3978
static bool handle_after_after_frameset(
3979
0
    GumboParser* parser, GumboToken* token) {
3980
0
  if (is_token_commentlike(token)) {
3981
0
    append_commentlike_node(parser, get_document_node(parser), token);
3982
0
    return true;
3983
0
  } else if (token->type == GUMBO_TOKEN_DOCTYPE ||
3984
0
             token->type == GUMBO_TOKEN_WHITESPACE ||
3985
0
             tag_is(token, kStartTag, GUMBO_TAG_HTML)) {
3986
0
    return handle_in_body(parser, token);
3987
0
  } else if (token->type == GUMBO_TOKEN_EOF) {
3988
0
    return true;
3989
0
  } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) {
3990
0
    return handle_in_head(parser, token);
3991
0
  } else {
3992
0
    parser_add_parse_error(parser, token);
3993
0
    ignore_token(parser);
3994
0
    return false;
3995
0
  }
3996
0
}
3997
3998
// Function pointers for each insertion mode.  Keep in sync with
3999
// insertion_mode.h.
4000
typedef bool (*TokenHandler)(GumboParser* parser, GumboToken* token);
4001
static const TokenHandler kTokenHandlers[] = {
4002
    handle_initial,
4003
    handle_before_html,
4004
    handle_before_head,
4005
    handle_in_head,
4006
    handle_in_head_noscript,
4007
    handle_after_head,
4008
    handle_in_body,
4009
    handle_text,
4010
    handle_in_table,
4011
    handle_in_table_text,
4012
    handle_in_caption,
4013
    handle_in_column_group,
4014
    handle_in_table_body,
4015
    handle_in_row,
4016
    handle_in_cell,
4017
    handle_in_template,
4018
    handle_after_body,
4019
    handle_in_frameset,
4020
    handle_after_frameset,
4021
    handle_after_after_body,
4022
    handle_after_after_frameset,
4023
};
4024
4025
0
static bool handle_html_content(GumboParser* parser, GumboToken* token) {
4026
0
  return kTokenHandlers[(unsigned int) parser->_parser_state->_insertion_mode](
4027
0
      parser, token);
4028
0
}
4029
4030
// http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inforeign
4031
0
static bool handle_in_foreign_content(GumboParser* parser, GumboToken* token) {
4032
0
  gumbo_debug("Handling foreign content");
4033
0
  switch (token->type) {
4034
0
    case GUMBO_TOKEN_NULL:
4035
0
      parser_add_parse_error(parser, token);
4036
0
      token->v.character = kUtf8ReplacementChar;
4037
0
      insert_text_token(parser, token);
4038
0
      return false;
4039
0
    case GUMBO_TOKEN_WHITESPACE:
4040
0
      insert_text_token(parser, token);
4041
0
      return true;
4042
0
    case GUMBO_TOKEN_CDATA:
4043
0
    case GUMBO_TOKEN_CHARACTER:
4044
0
      insert_text_token(parser, token);
4045
0
      set_frameset_not_ok(parser);
4046
0
      return true;
4047
0
    case GUMBO_TOKEN_COMMENT:
4048
0
    case GUMBO_TOKEN_PROCESSING_INSTRUCTION:
4049
0
      append_commentlike_node(parser, get_current_node(parser), token);
4050
0
      return true;
4051
0
    case GUMBO_TOKEN_DOCTYPE:
4052
0
      parser_add_parse_error(parser, token);
4053
0
      ignore_token(parser);
4054
0
      return false;
4055
0
    default:
4056
      // Fall through to the if-statements below.
4057
0
      break;
4058
0
  }
4059
4060
0
  if (tag_in(token, kStartTag, (gumbo_tagset){
4061
0
        TAG(B),
4062
0
        TAG(BIG),
4063
0
        TAG(BLOCKQUOTE),
4064
0
        TAG(BODY),
4065
0
        TAG(BR),
4066
0
        TAG(CENTER),
4067
0
        TAG(CODE),
4068
0
        TAG(DD),
4069
0
        TAG(DIV),
4070
0
        TAG(DL),
4071
0
        TAG(DT),
4072
0
        TAG(EM),
4073
0
        TAG(EMBED),
4074
0
        TAG(H1),
4075
0
        TAG(H2),
4076
0
        TAG(H3),
4077
0
        TAG(H4),
4078
0
        TAG(H5),
4079
0
        TAG(H6),
4080
0
        TAG(HEAD),
4081
0
        TAG(HR),
4082
0
        TAG(I),
4083
0
        TAG(IMG),
4084
0
        TAG(LI),
4085
0
        TAG(LISTING),
4086
0
        TAG(MENU),
4087
0
        TAG(META),
4088
0
        TAG(NOBR),
4089
0
        TAG(OL),
4090
0
        TAG(P),
4091
0
        TAG(PRE),
4092
0
        TAG(RUBY),
4093
0
        TAG(S),
4094
0
        TAG(SMALL),
4095
0
        TAG(SPAN),
4096
0
        TAG(STRONG),
4097
0
        TAG(STRIKE),
4098
0
        TAG(SUB),
4099
0
        TAG(SUP),
4100
0
        TAG(TABLE),
4101
0
        TAG(TT),
4102
0
        TAG(U),
4103
0
        TAG(UL),
4104
0
        TAG(VAR)
4105
0
    })
4106
0
      || tag_in(token, kEndTag, (gumbo_tagset){TAG(BR), TAG(P)})
4107
0
      || (tag_is(token, kStartTag, GUMBO_TAG_FONT)
4108
0
        && (token_has_attribute(token, "color")
4109
0
          || token_has_attribute(token, "face")
4110
0
          || token_has_attribute(token, "size"))))
4111
0
  {
4112
    /* Parse error */
4113
0
    parser_add_parse_error(parser, token);
4114
4115
0
    while (!is_mathml_integration_point(get_current_node(parser))
4116
0
        && !is_html_integration_point(get_current_node(parser))
4117
0
        && get_current_node(parser)->v.element.tag_namespace != GUMBO_NAMESPACE_HTML)
4118
0
    {
4119
0
      pop_current_node(parser);
4120
0
    }
4121
4122
0
    handle_html_content(parser, token);
4123
0
    return false;
4124
0
  }
4125
4126
0
  if (token->type == GUMBO_TOKEN_START_TAG) {
4127
0
    const GumboNamespaceEnum current_namespace =
4128
0
        get_adjusted_current_node(parser)->v.element.tag_namespace;
4129
0
    if (current_namespace == GUMBO_NAMESPACE_MATHML) {
4130
0
      adjust_mathml_attributes(parser, token);
4131
0
    }
4132
0
    if (current_namespace == GUMBO_NAMESPACE_SVG) {
4133
      // Tag adjustment is left to the gumbo_normalize_svg_tagname helper
4134
      // function.
4135
0
      adjust_svg_attributes(parser, token);
4136
0
    }
4137
0
    adjust_foreign_attributes(parser, token);
4138
0
    insert_foreign_element(parser, token, current_namespace);
4139
0
    if (token->v.start_tag.is_self_closing) {
4140
0
      pop_current_node(parser);
4141
0
      acknowledge_self_closing_tag(parser);
4142
0
    }
4143
0
    return true;
4144
    // </script> tags are handled like any other end tag, putting the script's
4145
    // text into a text node child and closing the current node.
4146
0
  } else {
4147
0
    assert(token->type == GUMBO_TOKEN_END_TAG);
4148
0
    GumboNode* node = get_current_node(parser);
4149
0
    assert(node != NULL);
4150
0
    GumboStringPiece token_tagname = token->original_text;
4151
0
    GumboStringPiece node_tagname = node->v.element.original_tag;
4152
0
    gumbo_tag_from_original_text(&token_tagname);
4153
0
    gumbo_tag_from_original_text(&node_tagname);
4154
4155
0
    bool is_success = true;
4156
0
    if (!gumbo_string_equals_ignore_case(&node_tagname, &token_tagname)) {
4157
0
      parser_add_parse_error(parser, token);
4158
0
      is_success = false;
4159
0
    }
4160
0
    int i = parser->_parser_state->_open_elements.length;
4161
0
    for (--i; i > 0;) {
4162
      // Here we move up the stack until we find an HTML element (in which
4163
      // case we do nothing) or we find the element that we're about to
4164
      // close (in which case we pop everything we've seen until that
4165
      // point.)
4166
0
      gumbo_debug("Foreign %.*s node at %d.\n", node_tagname.length,
4167
0
          node_tagname.data, i);
4168
0
      if (gumbo_string_equals_ignore_case(&node_tagname, &token_tagname)) {
4169
0
        gumbo_debug("Matches.\n");
4170
0
        while (pop_current_node(parser) != node) {
4171
          // Pop all the nodes below the current one.  Node is guaranteed to
4172
          // be an element on the stack of open elements (set below), so
4173
          // this loop is guaranteed to terminate.
4174
0
        }
4175
0
        return is_success;
4176
0
      }
4177
0
      --i;
4178
0
      node = parser->_parser_state->_open_elements.data[i];
4179
0
      if (node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML) {
4180
        // Must break before gumbo_tag_from_original_text to avoid passing
4181
        // parser-inserted nodes through.
4182
0
        break;
4183
0
      }
4184
0
      node_tagname = node->v.element.original_tag;
4185
0
      gumbo_tag_from_original_text(&node_tagname);
4186
0
    }
4187
0
    assert(node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML);
4188
    // We can't call handle_token directly because the current node is still in
4189
    // the SVG namespace, so it would re-enter this and result in infinite
4190
    // recursion.
4191
0
    return handle_html_content(parser, token) && is_success;
4192
0
  }
4193
0
}
4194
4195
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#tree-construction
4196
0
static bool handle_token(GumboParser* parser, GumboToken* token) {
4197
0
  if (parser->_parser_state->_ignore_next_linefeed &&
4198
0
      token->type == GUMBO_TOKEN_WHITESPACE && token->v.character == '\n') {
4199
0
    parser->_parser_state->_ignore_next_linefeed = false;
4200
0
    ignore_token(parser);
4201
0
    return true;
4202
0
  }
4203
  // This needs to be reset both here and in the conditional above to catch both
4204
  // the case where the next token is not whitespace (so we don't ignore
4205
  // whitespace in the middle of <pre> tags) and where there are multiple
4206
  // whitespace tokens (so we don't ignore the second one).
4207
0
  parser->_parser_state->_ignore_next_linefeed = false;
4208
4209
0
  if (tag_is(token, kEndTag, GUMBO_TAG_BODY)) {
4210
0
    parser->_parser_state->_closed_body_tag = true;
4211
0
  }
4212
0
  if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) {
4213
0
    parser->_parser_state->_closed_html_tag = true;
4214
0
  }
4215
4216
0
  const GumboNode* current_node = get_adjusted_current_node(parser);
4217
0
  assert(!current_node || current_node->type == GUMBO_NODE_ELEMENT ||
4218
0
         current_node->type == GUMBO_NODE_TEMPLATE);
4219
0
  if (current_node) {
4220
0
    gumbo_debug("Current node: <%s>.\n",
4221
0
        gumbo_normalized_tagname(current_node->v.element.tag));
4222
0
  }
4223
0
  if (!current_node ||
4224
0
      current_node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML ||
4225
0
      (is_mathml_integration_point(current_node) &&
4226
0
          (token->type == GUMBO_TOKEN_CHARACTER ||
4227
0
              token->type == GUMBO_TOKEN_WHITESPACE ||
4228
0
              token->type == GUMBO_TOKEN_NULL ||
4229
0
              (token->type == GUMBO_TOKEN_START_TAG &&
4230
0
                  !tag_in(token, kStartTag,
4231
0
                      (gumbo_tagset){TAG(MGLYPH), TAG(MALIGNMARK)})))) ||
4232
0
      (current_node->v.element.tag_namespace == GUMBO_NAMESPACE_MATHML &&
4233
0
          node_qualified_tag_is(
4234
0
              current_node, GUMBO_NAMESPACE_MATHML, GUMBO_TAG_ANNOTATION_XML) &&
4235
0
          tag_is(token, kStartTag, GUMBO_TAG_SVG)) ||
4236
0
      (is_html_integration_point(current_node) &&
4237
0
          (token->type == GUMBO_TOKEN_START_TAG ||
4238
0
              token->type == GUMBO_TOKEN_CHARACTER ||
4239
0
              token->type == GUMBO_TOKEN_NULL ||
4240
0
              token->type == GUMBO_TOKEN_WHITESPACE)) ||
4241
0
      token->type == GUMBO_TOKEN_EOF) {
4242
0
    return handle_html_content(parser, token);
4243
0
  } else {
4244
0
    return handle_in_foreign_content(parser, token);
4245
0
  }
4246
0
}
4247
4248
static void fragment_parser_init(GumboParser* parser, GumboTag fragment_ctx,
4249
0
    GumboNamespaceEnum fragment_namespace) {
4250
0
  GumboNode* root;
4251
0
  assert(fragment_ctx != GUMBO_TAG_LAST);
4252
4253
  // 3
4254
0
  parser->_parser_state->_fragment_ctx = create_element(parser, fragment_ctx);
4255
0
  parser->_parser_state->_fragment_ctx->v.element.tag_namespace =
4256
0
      fragment_namespace;
4257
4258
  // 4
4259
0
  if (fragment_namespace == GUMBO_NAMESPACE_HTML) {
4260
    // Non-HTML namespaces always start in the DATA state.
4261
0
    switch (fragment_ctx) {
4262
0
      case GUMBO_TAG_TITLE:
4263
0
      case GUMBO_TAG_TEXTAREA:
4264
0
        gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA);
4265
0
        break;
4266
4267
0
      case GUMBO_TAG_STYLE:
4268
0
      case GUMBO_TAG_XMP:
4269
0
      case GUMBO_TAG_IFRAME:
4270
0
      case GUMBO_TAG_NOEMBED:
4271
0
      case GUMBO_TAG_NOFRAMES:
4272
0
        gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT);
4273
0
        break;
4274
4275
0
      case GUMBO_TAG_SCRIPT:
4276
0
        gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT);
4277
0
        break;
4278
4279
0
      case GUMBO_TAG_NOSCRIPT:
4280
        /* scripting is disabled in Gumbo, so leave the tokenizer
4281
         * in the default data state */
4282
0
        break;
4283
4284
0
      case GUMBO_TAG_PLAINTEXT:
4285
0
        gumbo_tokenizer_set_state(parser, GUMBO_LEX_PLAINTEXT);
4286
0
        break;
4287
4288
0
      default:
4289
        /* default data state */
4290
0
        break;
4291
0
    }
4292
0
  }
4293
4294
  // 5. 6. 7.
4295
0
  root = insert_element_of_tag_type(
4296
0
      parser, GUMBO_TAG_HTML, GUMBO_INSERTION_IMPLIED);
4297
0
  parser->_output->root = root;
4298
4299
  // 8.
4300
0
  if (fragment_ctx == GUMBO_TAG_TEMPLATE) {
4301
0
    push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TEMPLATE);
4302
0
  }
4303
4304
  // 10.
4305
0
  reset_insertion_mode_appropriately(parser);
4306
0
}
4307
4308
0
GumboOutput* gumbo_parse(const char* buffer) {
4309
0
  return gumbo_parse_with_options(
4310
0
      &kGumboDefaultOptions, buffer, strlen(buffer));
4311
0
}
4312
4313
GumboOutput* gumbo_parse_with_options(
4314
0
    const GumboOptions* options, const char* buffer, size_t length) {
4315
0
  GumboParser parser;
4316
0
  parser._options = options;
4317
0
  output_init(&parser);
4318
0
  gumbo_tokenizer_state_init(&parser, buffer, length);
4319
0
  parser_state_init(&parser);
4320
4321
0
  if (options->fragment_context != GUMBO_TAG_LAST) {
4322
0
    fragment_parser_init(
4323
0
        &parser, options->fragment_context, options->fragment_namespace);
4324
0
  }
4325
4326
0
  GumboParserState* state = parser._parser_state;
4327
0
  gumbo_debug("Parsing %.*s.\n", length, buffer);
4328
4329
  // Sanity check so that infinite loops die with an assertion failure instead
4330
  // of hanging the process before we ever get an error.
4331
0
  int loop_count = 0;
4332
4333
0
  GumboToken token;
4334
0
  bool has_error = false;
4335
4336
0
  do {
4337
0
    if (state->_reprocess_current_token) {
4338
0
      state->_reprocess_current_token = false;
4339
0
    } else {
4340
0
      GumboNode* current_node = get_current_node(&parser);
4341
0
      gumbo_tokenizer_set_is_current_node_foreign(&parser,
4342
0
          current_node &&
4343
0
              current_node->v.element.tag_namespace != GUMBO_NAMESPACE_HTML);
4344
0
      has_error = !gumbo_lex(&parser, &token) || has_error;
4345
0
    }
4346
0
    const char* token_type = "text";
4347
0
    switch (token.type) {
4348
0
      case GUMBO_TOKEN_DOCTYPE:
4349
0
        token_type = "doctype";
4350
0
        break;
4351
0
      case GUMBO_TOKEN_START_TAG:
4352
0
        token_type = gumbo_normalized_tagname(token.v.start_tag.tag);
4353
0
        break;
4354
0
      case GUMBO_TOKEN_END_TAG:
4355
0
        token_type = gumbo_normalized_tagname(token.v.end_tag);
4356
0
        break;
4357
0
      case GUMBO_TOKEN_COMMENT:
4358
0
        token_type = "comment";
4359
0
        break;
4360
0
      case GUMBO_TOKEN_PROCESSING_INSTRUCTION:
4361
0
        token_type = "processing instruction";
4362
0
        break;
4363
0
      default:
4364
0
        break;
4365
0
    }
4366
0
    gumbo_debug("Handling %s token @%d:%d in state %d.\n", (char*) token_type,
4367
0
        token.position.line, token.position.column, state->_insertion_mode);
4368
4369
0
    state->_current_token = &token;
4370
0
    state->_self_closing_flag_acknowledged =
4371
0
        !(token.type == GUMBO_TOKEN_START_TAG &&
4372
0
            token.v.start_tag.is_self_closing);
4373
4374
0
    has_error = !handle_token(&parser, &token) || has_error;
4375
4376
    // Check for memory leaks when ownership is transferred from start tag
4377
    // tokens to nodes.
4378
0
    assert(state->_reprocess_current_token ||
4379
0
           token.type != GUMBO_TOKEN_START_TAG ||
4380
0
           token.v.start_tag.attributes.data == NULL);
4381
4382
0
    if (!state->_self_closing_flag_acknowledged) {
4383
0
      GumboError* error = parser_add_parse_error(&parser, &token);
4384
0
      if (error) {
4385
0
        error->type = GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG;
4386
0
      }
4387
0
    }
4388
4389
0
    ++loop_count;
4390
0
    assert(loop_count < 1000000000);
4391
4392
0
  } while ((token.type != GUMBO_TOKEN_EOF || state->_reprocess_current_token) &&
4393
0
           !(options->stop_on_first_error && has_error));
4394
4395
0
  finish_parsing(&parser);
4396
  // For API uniformity reasons, if the doctype still has nulls, convert them to
4397
  // empty strings.
4398
0
  GumboDocument* doc_type = &parser._output->document->v.document;
4399
0
  if (doc_type->name == NULL) {
4400
0
    doc_type->name = gumbo_copy_stringz(&parser, "");
4401
0
  }
4402
0
  if (doc_type->public_identifier == NULL) {
4403
0
    doc_type->public_identifier = gumbo_copy_stringz(&parser, "");
4404
0
  }
4405
0
  if (doc_type->system_identifier == NULL) {
4406
0
    doc_type->system_identifier = gumbo_copy_stringz(&parser, "");
4407
0
  }
4408
4409
0
  parser_state_destroy(&parser);
4410
0
  gumbo_tokenizer_state_destroy(&parser);
4411
0
  return parser._output;
4412
0
}
4413
4414
0
void gumbo_destroy_node(GumboOptions* options, GumboNode* node) {
4415
  // Need a dummy GumboParser because the allocator comes along with the
4416
  // options object.
4417
0
  GumboParser parser;
4418
0
  parser._options = options;
4419
0
  destroy_node(&parser, node);
4420
0
}
4421
4422
0
void gumbo_destroy_output(const GumboOptions* options, GumboOutput* output) {
4423
  // Need a dummy GumboParser because the allocator comes along with the
4424
  // options object.
4425
0
  GumboParser parser;
4426
0
  parser._options = options;
4427
0
  destroy_node(&parser, output->document);
4428
0
  for (unsigned int i = 0; i < output->errors.length; ++i) {
4429
0
    gumbo_error_destroy(&parser, output->errors.data[i]);
4430
0
  }
4431
0
  gumbo_vector_destroy(&parser, &output->errors);
4432
0
  gumbo_parser_deallocate(&parser, output);
4433
0
}