Coverage Report

Created: 2026-07-16 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tidy-html5/src/parser.c
Line
Count
Source
1
/* parser.c -- HTML Parser
2
3
  (c) 1998-2007 (W3C) MIT, ERCIM, Keio University
4
  See tidy.h for the copyright notice.
5
6
*/
7
8
#include "tidy-int.h"
9
#include "lexer.h"
10
#include "parser.h"
11
#include "message.h"
12
#include "clean.h"
13
#include "tags.h"
14
#include "tmbstr.h"
15
#include "sprtf.h"
16
17
18
/****************************************************************************//*
19
 ** MARK: - Configuration Options
20
 ***************************************************************************/
21
22
23
/**
24
 *  Issue #72  - Need to know to avoid error-reporting - no warning only if
25
 *               --show-body-only yes.
26
 *  Issue #132 - Likewise avoid warning if showing body only.
27
 */
28
0
#define showingBodyOnly(doc) (cfgAutoBool(doc,TidyBodyOnly) == TidyYesState) ? yes : no
29
30
31
/****************************************************************************//*
32
 ** MARK: - Forward Declarations
33
 ***************************************************************************/
34
35
36
static Node* ParseXMLElement(TidyDocImpl* doc, Node *element, GetTokenMode mode);
37
38
39
/****************************************************************************//*
40
 ** MARK: - Node Operations
41
 ***************************************************************************/
42
43
44
/**
45
 *  Generalised search for duplicate elements.
46
 *  Issue #166 - repeated <main> element.
47
 */
48
static Bool findNodeWithId( Node *node, TidyTagId tid )
49
0
{
50
0
    Node *content;
51
0
    while (node)
52
0
    {
53
0
        if (TagIsId(node,tid))
54
0
            return yes;
55
        /*\
56
         *   Issue #459 - Under certain circumstances, with many node this use of
57
         *   'for (content = node->content; content; content = content->content)'
58
         *   would produce a **forever** circle, or at least a very extended loop...
59
         *   It is sufficient to test the content, if it exists,
60
         *   to quickly iterate all nodes. Now all nodes are tested only once.
61
        \*/
62
0
        content = node->content;
63
0
        if (content)
64
0
        {
65
0
            if ( findNodeWithId(content,tid) )
66
0
                return yes;
67
0
        }
68
0
        node = node->next;
69
0
    }
70
0
    return no;
71
0
}
72
73
74
/**
75
 *  Perform a global search for an element.
76
 *  Issue #166 - repeated <main> element
77
 */
78
static Bool findNodeById( TidyDocImpl* doc, TidyTagId tid )
79
0
{
80
0
    Node *node = (doc ? doc->root.content : NULL);
81
0
    return findNodeWithId( node,tid );
82
0
}
83
84
85
/**
86
 *  Inserts node into element at an appropriate location based
87
 *  on the type of node being inserted.
88
 */
89
static Bool InsertMisc(Node *element, Node *node)
90
0
{
91
0
    if (node->type == CommentTag ||
92
0
        node->type == ProcInsTag ||
93
0
        node->type == CDATATag ||
94
0
        node->type == SectionTag ||
95
0
        node->type == AspTag ||
96
0
        node->type == JsteTag ||
97
0
        node->type == PhpTag )
98
0
    {
99
0
        TY_(InsertNodeAtEnd)(element, node);
100
0
        return yes;
101
0
    }
102
103
0
    if ( node->type == XmlDecl )
104
0
    {
105
0
        Node* root = element;
106
0
        while ( root && root->parent )
107
0
            root = root->parent;
108
0
        if ( root && !(root->content && root->content->type == XmlDecl))
109
0
        {
110
0
          TY_(InsertNodeAtStart)( root, node );
111
0
          return yes;
112
0
        }
113
0
    }
114
115
    /* Declared empty tags seem to be slipping through
116
    ** the cracks.  This is an experiment to figure out
117
    ** a decent place to pick them up.
118
    */
119
0
    if ( node->tag &&
120
0
         TY_(nodeIsElement)(node) &&
121
0
         TY_(nodeCMIsEmpty)(node) && TagId(node) == TidyTag_UNKNOWN &&
122
0
         (node->tag->versions & VERS_PROPRIETARY) != 0 )
123
0
    {
124
0
        TY_(InsertNodeAtEnd)(element, node);
125
0
        return yes;
126
0
    }
127
128
0
    return no;
129
0
}
130
131
132
/**
133
 *  Insert "node" into markup tree in place of "element"
134
 *  which is moved to become the child of the node
135
 */
136
static void InsertNodeAsParent(Node *element, Node *node)
137
0
{
138
0
    node->content = element;
139
0
    node->last = element;
140
0
    node->parent = element->parent;
141
0
    element->parent = node;
142
143
0
    if (node->parent->content == element)
144
0
        node->parent->content = node;
145
146
0
    if (node->parent->last == element)
147
0
        node->parent->last = node;
148
149
0
    node->prev = element->prev;
150
0
    element->prev = NULL;
151
152
0
    if (node->prev)
153
0
        node->prev->next = node;
154
155
0
    node->next = element->next;
156
0
    element->next = NULL;
157
158
0
    if (node->next)
159
0
        node->next->prev = node;
160
0
}
161
162
163
/**
164
 *  Unexpected content in table row is moved to just before the table in
165
 *  in accordance with Netscape and IE. This code assumes that node hasn't
166
 *  been inserted into the row.
167
 */
168
static void MoveBeforeTable( TidyDocImpl* ARG_UNUSED(doc), Node *row,
169
                            Node *node )
170
0
{
171
0
    Node *table;
172
173
    /* first find the table element */
174
0
    for (table = row->parent; table; table = table->parent)
175
0
    {
176
0
        if ( nodeIsTABLE(table) )
177
0
        {
178
0
            TY_(InsertNodeBeforeElement)( table, node );
179
0
            return;
180
0
        }
181
0
    }
182
    /* No table element */
183
0
    TY_(InsertNodeBeforeElement)( row->parent, node );
184
0
}
185
186
187
/**
188
 *  Moves given node to end of body element.
189
 */
190
static void MoveNodeToBody( TidyDocImpl* doc, Node* node )
191
0
{
192
0
    Node* body = TY_(FindBody)( doc );
193
0
    if ( body )
194
0
    {
195
0
        TY_(RemoveNode)( node );
196
0
        TY_(InsertNodeAtEnd)( body, node );
197
0
    }
198
0
}
199
200
201
/**
202
 *  Move node to the head, where element is used as starting
203
 *  point in hunt for head. Normally called during parsing.
204
 */
205
static void MoveToHead( TidyDocImpl* doc, Node *element, Node *node )
206
0
{
207
0
    Node *head = NULL;
208
209
0
    TY_(RemoveNode)( node );  /* make sure that node is isolated */
210
211
0
    if ( TY_(nodeIsElement)(node) )
212
0
    {
213
0
        TY_(Report)(doc, element, node, TAG_NOT_ALLOWED_IN );
214
215
0
        head = TY_(FindHEAD)(doc);
216
0
        assert(head != NULL);
217
218
0
        TY_(InsertNodeAtEnd)(head, node);
219
220
0
        if ( node->tag->parser )
221
0
        {
222
            /* Only one of the existing test cases as of 2021-08-14 invoke
223
               MoveToHead, and it doesn't go deeper than one level. The
224
               parser() call is supposed to return a node if additional
225
               parsing is needed. Keep this in mind if we start to get bug
226
               reports.
227
             */
228
0
            Parser* parser = node->tag->parser;
229
0
            parser( doc, node, IgnoreWhitespace );
230
0
        }
231
0
    }
232
0
    else
233
0
    {
234
0
        TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
235
0
        TY_(FreeNode)( doc, node );
236
0
    }
237
0
}
238
239
240
/***************************************************************************//*
241
 ** MARK: - Decision Making
242
 ***************************************************************************/
243
244
245
/**
246
 *  Indicates whether or not element can be pruned based on content,
247
 *  user settings, etc.
248
 */
249
static Bool CanPrune( TidyDocImpl* doc, Node *element )
250
0
{
251
0
    if ( !cfgBool(doc, TidyDropEmptyElems) )
252
0
        return no;
253
254
0
    if ( TY_(nodeIsText)(element) )
255
0
        return yes;
256
257
0
    if ( element->content )
258
0
        return no;
259
260
0
    if ( element->tag == NULL )
261
0
        return no;
262
263
0
    if ( element->tag->model & CM_BLOCK && element->attributes != NULL )
264
0
        return no;
265
266
0
    if ( nodeIsA(element) && element->attributes != NULL )
267
0
        return no;
268
269
0
    if ( nodeIsP(element) && !cfgBool(doc, TidyDropEmptyParas) )
270
0
        return no;
271
272
0
    if ( element->tag->model & CM_ROW )
273
0
        return no;
274
275
0
    if ( element->tag->model & CM_EMPTY )
276
0
        return no;
277
278
0
    if ( nodeIsAPPLET(element) )
279
0
        return no;
280
281
0
    if ( nodeIsOBJECT(element) )
282
0
        return no;
283
284
0
    if ( nodeIsSCRIPT(element) && attrGetSRC(element) )
285
0
        return no;
286
287
0
    if ( nodeIsTITLE(element) )
288
0
        return no;
289
290
    /* #433359 - fix by Randy Waki 12 Mar 01 */
291
0
    if ( nodeIsIFRAME(element) )
292
0
        return no;
293
294
    /* fix for bug 770297 */
295
0
    if (nodeIsTEXTAREA(element))
296
0
        return no;
297
298
    /* fix for ISSUE #7 https://github.com/w3c/tidy-html5/issues/7 */
299
0
    if (nodeIsCANVAS(element))
300
0
        return no;
301
    
302
0
    if (nodeIsPROGRESS(element))
303
0
        return no;
304
305
0
    if ( attrGetID(element) || attrGetNAME(element) )
306
0
        return no;
307
308
    /* fix for bug 695408; a better fix would look for unknown and    */
309
    /* known proprietary attributes that make the element significant */
310
0
    if (attrGetDATAFLD(element))
311
0
        return no;
312
313
    /* fix for bug 723772, don't trim new-...-tags */
314
0
    if (element->tag->id == TidyTag_UNKNOWN)
315
0
        return no;
316
317
0
    if (nodeIsBODY(element))
318
0
        return no;
319
320
0
    if (nodeIsCOLGROUP(element))
321
0
        return no;
322
323
    /* HTML5 - do NOT drop empty option if it has attributes */
324
0
    if ( nodeIsOPTION(element) && element->attributes != NULL )
325
0
        return no;
326
327
    /* fix for #103 - don't drop empty dd tags lest document not validate */
328
0
    if (nodeIsDD(element))
329
0
        return no;
330
331
0
    return yes;
332
0
}
333
334
335
/**
336
 *  Indicates whether or not node is a descendant of a tag of the given tid.
337
 */
338
static Bool DescendantOf( Node *element, TidyTagId tid )
339
0
{
340
0
    Node *parent;
341
0
    for ( parent = element->parent;
342
0
         parent != NULL;
343
0
         parent = parent->parent )
344
0
    {
345
0
        if ( TagIsId(parent, tid) )
346
0
            return yes;
347
0
    }
348
0
    return no;
349
0
}
350
351
352
/**
353
 *  Indicates whether or not node is a descendant of a pre tag.
354
 */
355
static Bool IsPreDescendant(Node* node)
356
0
{
357
0
    Node *parent = node->parent;
358
359
0
    while (parent)
360
0
    {
361
0
        if (parent->tag && parent->tag->parser == TY_(ParsePre))
362
0
            return yes;
363
364
0
        parent = parent->parent;
365
0
    }
366
367
0
    return no;
368
0
}
369
370
371
/**
372
 *  Indicates whether or not the only content model for the given node
373
 *  is CM_INLINE.
374
 */
375
static Bool nodeCMIsOnlyInline( Node* node )
376
0
{
377
0
    return TY_(nodeHasCM)( node, CM_INLINE ) && !TY_(nodeHasCM)( node, CM_BLOCK );
378
0
}
379
380
381
/**
382
 *  Indicates whether or not the content of the given node is acceptable
383
 *  content for pre elements
384
 */
385
static Bool PreContent( TidyDocImpl* ARG_UNUSED(doc), Node* node )
386
0
{
387
    /* p is coerced to br's, Text OK too */
388
0
    if ( nodeIsP(node) || TY_(nodeIsText)(node) )
389
0
        return yes;
390
391
0
    if ( node->tag == NULL ||
392
0
         nodeIsPARAM(node) ||
393
0
         !TY_(nodeHasCM)(node, CM_INLINE|CM_NEW) )
394
0
        return no;
395
396
0
    return yes;
397
0
}
398
399
400
/**
401
 *  Indicates whether or not leading whitespace should be cleaned.
402
 */
403
static Bool CleanLeadingWhitespace(TidyDocImpl* ARG_UNUSED(doc), Node* node)
404
0
{
405
0
    if (!TY_(nodeIsText)(node))
406
0
        return no;
407
408
0
    if (node->parent->type == DocTypeTag)
409
0
        return no;
410
411
0
    if (IsPreDescendant(node))
412
0
        return no;
413
414
0
    if (node->parent->tag && node->parent->tag->parser == TY_(ParseScript))
415
0
        return no;
416
    
417
    /* #523, prevent blank spaces after script if the next item is script.
418
     * This is actually more generalized as, if the preceding element is
419
     * a body level script, then indicate that we want to clean leading
420
     * whitespace.
421
     */
422
0
    if ( node->prev && nodeIsSCRIPT(node->prev) && nodeIsBODY(node->prev->parent) )
423
0
        return yes;
424
425
    /* <p>...<br> <em>...</em>...</p> */
426
0
    if (nodeIsBR(node->prev))
427
0
        return yes;
428
429
    /* <p> ...</p> */
430
0
    if (node->prev == NULL && !TY_(nodeHasCM)(node->parent, CM_INLINE))
431
0
        return yes;
432
433
    /* <h4>...</h4> <em>...</em> */
434
0
    if (node->prev && !TY_(nodeHasCM)(node->prev, CM_INLINE) &&
435
0
        TY_(nodeIsElement)(node->prev))
436
0
        return yes;
437
438
    /* <p><span> ...</span></p> */
439
0
    if (!node->prev && !node->parent->prev && !TY_(nodeHasCM)(node->parent->parent, CM_INLINE))
440
0
        return yes;
441
442
0
    return no;
443
0
}
444
445
446
/**
447
 *  Indicates whether or not trailing whitespace should be cleaned.
448
 */
449
static Bool CleanTrailingWhitespace(TidyDocImpl* doc, Node* node)
450
0
{
451
0
    Node* next;
452
453
0
    if (!TY_(nodeIsText)(node))
454
0
        return no;
455
456
0
    if (node->parent->type == DocTypeTag)
457
0
        return no;
458
459
0
    if (IsPreDescendant(node))
460
0
        return no;
461
462
0
    if (node->parent->tag && node->parent->tag->parser == TY_(ParseScript))
463
0
        return no;
464
465
    /* #523, prevent blank spaces after script if the next item is script.
466
     * This is actually more generalized as, if the next element is
467
     * a body level script, then indicate that we want to clean trailing
468
     * whitespace.
469
     */
470
0
    if ( node->next && nodeIsSCRIPT(node->next) && nodeIsBODY(node->next->parent) )
471
0
        return yes;
472
473
0
    next = node->next;
474
475
    /* <p>... </p> */
476
0
    if (!next && !TY_(nodeHasCM)(node->parent, CM_INLINE))
477
0
        return yes;
478
479
    /* <div><small>... </small><h3>...</h3></div> */
480
0
    if (!next && node->parent->next && !TY_(nodeHasCM)(node->parent->next, CM_INLINE))
481
0
        return yes;
482
483
0
    if (!next)
484
0
        return no;
485
486
0
    if (nodeIsBR(next))
487
0
        return yes;
488
489
0
    if (TY_(nodeHasCM)(next, CM_INLINE))
490
0
        return no;
491
492
    /* <a href='/'>...</a> <p>...</p> */
493
0
    if (next->type == StartTag)
494
0
        return yes;
495
496
    /* <strong>...</strong> <hr /> */
497
0
    if (next->type == StartEndTag)
498
0
        return yes;
499
500
    /* evil adjacent text nodes, Tidy should not generate these :-( */
501
0
    if (TY_(nodeIsText)(next) && next->start < next->end
502
0
        && TY_(IsWhite)(doc->lexer->lexbuf[next->start]))
503
0
        return yes;
504
505
0
    return no;
506
0
}
507
508
509
/***************************************************************************//*
510
 ** MARK: - Information Accumulation
511
 ***************************************************************************/
512
513
514
/**
515
 *  Errors in positioning of form start or end tags
516
 *  generally require human intervention to fix.
517
 *  Issue #166 - repeated <main> element also uses this flag
518
 *  to indicate duplicates, discarded.
519
 */
520
static void BadForm( TidyDocImpl* doc )
521
0
{
522
0
    doc->badForm |= flg_BadForm;
523
0
}
524
525
526
/***************************************************************************//*
527
 ** MARK: - Fixes and Touchup
528
 ***************************************************************************/
529
530
531
/**
532
 *  Adds style information as a class in the document or a property
533
 *  of the node to prevent indentation of inferred UL tags.
534
 */
535
static void AddClassNoIndent( TidyDocImpl* doc, Node *node )
536
0
{
537
0
    ctmbstr sprop =
538
0
    "padding-left: 2ex; margin-left: 0ex"
539
0
    "; margin-top: 0ex; margin-bottom: 0ex";
540
0
    if ( !cfgBool(doc, TidyDecorateInferredUL) )
541
0
        return;
542
0
    if ( cfgBool(doc, TidyMakeClean) )
543
0
        TY_(AddStyleAsClass)( doc, node, sprop );
544
0
    else
545
0
        TY_(AddStyleProperty)( doc, node, sprop );
546
0
}
547
548
549
/**
550
 *  Cleans whitespace from text nodes, and drops such nodes if emptied
551
 *  completely as a result.
552
 */
553
static void CleanSpaces(TidyDocImpl* doc, Node* node)
554
0
{
555
0
    Stack *stack = TY_(newStack)(doc, 16);
556
0
    Node *next;
557
    
558
0
    while (node)
559
0
    {
560
0
        next = node->next;
561
562
0
        if (TY_(nodeIsText)(node) && CleanLeadingWhitespace(doc, node))
563
0
            while (node->start < node->end && TY_(IsWhite)(doc->lexer->lexbuf[node->start]))
564
0
                ++(node->start);
565
566
0
        if (TY_(nodeIsText)(node) && CleanTrailingWhitespace(doc, node))
567
0
            while (node->end > node->start && TY_(IsWhite)(doc->lexer->lexbuf[node->end - 1]))
568
0
                --(node->end);
569
570
0
        if (TY_(nodeIsText)(node) && !(node->start < node->end))
571
0
        {
572
0
            TY_(RemoveNode)(node);
573
0
            TY_(FreeNode)(doc, node);
574
0
            node = next ? next : TY_(pop)(stack);
575
0
            continue;
576
0
        }
577
578
0
        if (node->content)
579
0
        {
580
0
            TY_(push)(stack, next);
581
0
            node = node->content;
582
0
            continue;
583
0
        }
584
585
0
        node = next ? next : TY_(pop)(stack);
586
0
    }
587
0
    TY_(freeStack)(stack);
588
0
}
589
590
591
/**
592
 *  If a table row is empty then insert an empty cell. This practice is
593
 *  consistent with browser behavior and avoids potential problems with
594
 *  row spanning cells.
595
 */
596
static void FixEmptyRow(TidyDocImpl* doc, Node *row)
597
0
{
598
0
    Node *cell;
599
600
0
    if (row->content == NULL)
601
0
    {
602
0
        cell = TY_(InferredTag)(doc, TidyTag_TD);
603
0
        TY_(InsertNodeAtEnd)(row, cell);
604
0
        TY_(Report)(doc, row, cell, MISSING_STARTTAG);
605
0
    }
606
0
}
607
608
609
/**
610
 *  The doctype has been found after other tags,
611
 *  and needs moving to before the html element
612
 */
613
static void InsertDocType( TidyDocImpl* doc, Node *element, Node *doctype )
614
0
{
615
0
    Node* existing = TY_(FindDocType)( doc );
616
0
    if ( existing )
617
0
    {
618
0
        TY_(Report)(doc, element, doctype, DISCARDING_UNEXPECTED );
619
0
        TY_(FreeNode)( doc, doctype );
620
0
    }
621
0
    else
622
0
    {
623
0
        TY_(Report)(doc, element, doctype, DOCTYPE_AFTER_TAGS );
624
0
        while ( !nodeIsHTML(element) )
625
0
            element = element->parent;
626
0
        TY_(InsertNodeBeforeElement)( element, doctype );
627
0
    }
628
0
}
629
630
631
/**
632
 *  This maps
633
 *     <p>hello<em> world</em>
634
 *  to
635
 *     <p>hello <em>world</em>
636
 *
637
 *  Trims initial space, by moving it before the
638
 *  start tag, or if this element is the first in
639
 *  parent's content, then by discarding the space
640
 */
641
static void TrimInitialSpace( TidyDocImpl* doc, Node *element, Node *text )
642
0
{
643
0
    Lexer* lexer = doc->lexer;
644
0
    Node *prev, *node;
645
646
0
    if ( TY_(nodeIsText)(text) &&
647
0
         lexer->lexbuf[text->start] == ' ' &&
648
0
         text->start < text->end )
649
0
    {
650
0
        if ( (element->tag->model & CM_INLINE) &&
651
0
             !(element->tag->model & CM_FIELD) )
652
0
        {
653
0
            prev = element->prev;
654
655
0
            if (TY_(nodeIsText)(prev))
656
0
            {
657
0
                if (prev->end == 0 || lexer->lexbuf[prev->end - 1] != ' ')
658
0
                    lexer->lexbuf[(prev->end)++] = ' ';
659
660
0
                ++(element->start);
661
0
            }
662
0
            else /* create new node */
663
0
            {
664
0
                node = TY_(NewNode)(lexer->allocator, lexer);
665
0
                node->start = (element->start)++;
666
0
                node->end = element->start;
667
0
                lexer->lexbuf[node->start] = ' ';
668
0
                TY_(InsertNodeBeforeElement)(element ,node);
669
0
                DEBUG_LOG(SPRTF("TrimInitialSpace: Created text node, inserted before <%s>\n",
670
0
                    (element->element ? element->element : "unknown")));
671
0
            }
672
0
        }
673
674
        /* discard the space in current node */
675
0
        ++(text->start);
676
0
    }
677
0
}
678
679
680
/**
681
 *  This maps
682
 *     <em>hello </em><strong>world</strong>
683
 *  to
684
 *     <em>hello</em> <strong>world</strong>
685
 *
686
 *  If last child of element is a text node
687
 *  then trim trailing white space character
688
 *  moving it to after element's end tag.
689
 */
690
static void TrimTrailingSpace( TidyDocImpl* doc, Node *element, Node *last )
691
0
{
692
0
    Lexer* lexer = doc->lexer;
693
0
    byte c;
694
695
0
    if (TY_(nodeIsText)(last))
696
0
    {
697
0
        if (last->end > last->start)
698
0
        {
699
0
            c = (byte) lexer->lexbuf[ last->end - 1 ];
700
701
0
            if ( c == ' ' )
702
0
            {
703
0
                last->end -= 1;
704
0
                if ( (element->tag->model & CM_INLINE) &&
705
0
                     !(element->tag->model & CM_FIELD) )
706
0
                    lexer->insertspace = yes;
707
0
            }
708
0
        }
709
0
    }
710
0
}
711
712
713
/**
714
 *  Move initial and trailing space out.
715
 *  This routine maps:
716
 *     hello<em> world</em>
717
 *  to
718
 *     hello <em>world</em>
719
 *  and
720
 *     <em>hello </em><strong>world</strong>
721
 *  to
722
 *     <em>hello</em> <strong>world</strong>
723
 */
724
static void TrimSpaces( TidyDocImpl* doc, Node *element)
725
0
{
726
0
    Node* text = element->content;
727
728
0
    if (nodeIsPRE(element) || IsPreDescendant(element))
729
0
        return;
730
731
0
    if (TY_(nodeIsText)(text))
732
0
        TrimInitialSpace(doc, element, text);
733
734
0
    text = element->last;
735
736
0
    if (TY_(nodeIsText)(text))
737
0
        TrimTrailingSpace(doc, element, text);
738
0
}
739
740
741
/***************************************************************************//*
742
 ** MARK: - Parsers Support
743
 ***************************************************************************/
744
745
746
/**
747
 *  Structure used by FindDescendant_cb.
748
 */
749
struct MatchingDescendantData
750
{
751
    Node *found_node;
752
    Bool *passed_marker_node;
753
754
    /* input: */
755
    TidyTagId matching_tagId;
756
    Node *node_to_find;
757
    Node *marker_node;
758
};
759
760
761
/**
762
 *  The main engine for FindMatchingDescendant.
763
 */
764
static NodeTraversalSignal FindDescendant_cb(TidyDocImpl* ARG_UNUSED(doc), Node* node, void *propagate)
765
0
{
766
0
    struct MatchingDescendantData *cb_data = (struct MatchingDescendantData *)propagate;
767
768
0
    if (TagId(node) == cb_data->matching_tagId)
769
0
    {
770
        /* make sure we match up 'unknown' tags exactly! */
771
0
        if (cb_data->matching_tagId != TidyTag_UNKNOWN ||
772
0
            (node->element != NULL &&
773
0
            cb_data->node_to_find != NULL &&
774
0
            cb_data->node_to_find->element != NULL &&
775
0
            0 == TY_(tmbstrcmp)(cb_data->node_to_find->element, node->element)))
776
0
        {
777
0
            cb_data->found_node = node;
778
0
            return ExitTraversal;
779
0
        }
780
0
    }
781
782
0
    if (cb_data->passed_marker_node && node == cb_data->marker_node)
783
0
        *cb_data->passed_marker_node = yes;
784
785
0
    return VisitParent;
786
0
}
787
788
789
/**
790
 *  Search the parent chain (from `parent` upwards up to the root) for a node
791
 *  matching the given 'node'.
792
 *
793
 *  When the search passes beyond the `marker_node` (which is assumed to sit
794
 *  in the parent chain), this will be flagged by setting the boolean
795
 *  referenced by `is_parent_of_marker` to `yes`.
796
 *
797
 *  'is_parent_of_marker' and 'marker_node' are optional parameters and may
798
 *  be NULL.
799
 */
800
static Node *FindMatchingDescendant( Node *parent, Node *node, Node *marker_node, Bool *is_parent_of_marker )
801
0
{
802
0
    struct MatchingDescendantData cb_data = { 0 };
803
0
    cb_data.matching_tagId = TagId(node);
804
0
    cb_data.node_to_find = node;
805
0
    cb_data.marker_node = marker_node;
806
807
0
    assert(node);
808
809
0
    if (is_parent_of_marker)
810
0
        *is_parent_of_marker = no;
811
812
0
    TY_(TraverseNodeTree)(NULL, parent, FindDescendant_cb, &cb_data);
813
0
    return cb_data.found_node;
814
0
}
815
816
817
/**
818
 *   Finds the last list item for the given list, providing it in the
819
 *   in-out parameter. Returns yes or no if the item was the last list
820
 *   item.
821
 */
822
static Bool FindLastLI( Node *list, Node **lastli )
823
0
{
824
0
    Node *node;
825
826
0
    *lastli = NULL;
827
0
    for ( node = list->content; node ; node = node->next )
828
0
        if ( nodeIsLI(node) && node->type == StartTag )
829
0
            *lastli=node;
830
0
    return *lastli ? yes:no;
831
0
}
832
833
834
/***************************************************************************//*
835
 ** MARK: - Parser Stack
836
 ***************************************************************************/
837
838
839
/**
840
 *  Allocates and initializes the parser's stack.
841
 */
842
void TY_(InitParserStack)( TidyDocImpl* doc )
843
0
{
844
0
    enum { default_size = 32 };
845
0
    TidyParserMemory *content = (TidyParserMemory *) TidyAlloc( doc->allocator, sizeof(TidyParserMemory) * default_size );
846
847
0
    doc->stack.content = content;
848
0
    doc->stack.size = default_size;
849
0
    doc->stack.top = -1;
850
0
}
851
852
853
/**
854
 *  Frees the parser's stack when done.
855
 */
856
void TY_(FreeParserStack)( TidyDocImpl* doc )
857
0
{
858
0
    TidyFree( doc->allocator, doc->stack.content );
859
860
0
    doc->stack.content = NULL;
861
0
    doc->stack.size = 0;
862
0
    doc->stack.top = -1;
863
0
}
864
865
866
/**
867
 *  Increase the stack size.
868
 */
869
static void growParserStack( TidyDocImpl* doc )
870
0
{
871
0
    TidyParserMemory *content;
872
0
    content = (TidyParserMemory *) TidyAlloc( doc->allocator, sizeof(TidyParserMemory) * doc->stack.size * 2 );
873
874
0
    memcpy( content, doc->stack.content, sizeof(TidyParserMemory) * (doc->stack.top + 1) );
875
0
    TidyFree(doc->allocator, doc->stack.content);
876
877
0
    doc->stack.content = content;
878
0
    doc->stack.size = doc->stack.size * 2;
879
0
}
880
881
882
/**
883
 *  Indicates whether or not the stack is empty.
884
 */
885
Bool TY_(isEmptyParserStack)( TidyDocImpl* doc )
886
0
{
887
0
    return doc->stack.top < 0;
888
0
}
889
890
891
/**
892
 *  Peek at the parser memory.
893
 */
894
TidyParserMemory TY_(peekMemory)( TidyDocImpl* doc )
895
0
{
896
0
    return doc->stack.content[doc->stack.top];
897
0
}
898
899
900
/**
901
 *  Peek at the parser memory "identity" field. This is just a convenience
902
 *  to avoid having to create a new struct instance in the caller.
903
 */
904
Parser* TY_(peekMemoryIdentity)( TidyDocImpl* doc )
905
0
{
906
0
    return doc->stack.content[doc->stack.top].identity;
907
0
}
908
909
910
/**
911
 *  Peek at the parser memory "mode" field. This is just a convenience
912
 *  to avoid having to create a new struct instance in the caller.
913
 */
914
GetTokenMode TY_(peekMemoryMode)( TidyDocImpl* doc )
915
0
{
916
0
    return doc->stack.content[doc->stack.top].mode;
917
0
}
918
919
920
/**
921
 *  Pop out a parser memory.
922
 */
923
TidyParserMemory TY_(popMemory)( TidyDocImpl* doc )
924
0
{
925
0
    if ( !TY_(isEmptyParserStack)( doc ) )
926
0
    {
927
0
        TidyParserMemory data = doc->stack.content[doc->stack.top];
928
0
        DEBUG_LOG(SPRTF("\n"
929
0
                        "<--POP  original: %s @ %p\n"
930
0
                        "         reentry: %s @ %p\n"
931
0
                        "     stack depth: %lu @ %p\n"
932
0
                        "            mode: %u\n"
933
0
                        "      register 1: %i\n"
934
0
                        "      register 2: %i\n\n",
935
0
                        data.original_node ? data.original_node->element : "none", data.original_node,
936
0
                        data.reentry_node ? data.reentry_node->element : "none", data.reentry_node,
937
0
                        doc->stack.top, &doc->stack.content[doc->stack.top],
938
0
                        data.mode,
939
0
                        data.register_1,
940
0
                        data.register_2
941
0
                        ));
942
0
        doc->stack.top = doc->stack.top - 1;
943
0
        return data;
944
0
    }
945
0
    TidyParserMemory blank = { NULL };
946
0
    return blank;
947
0
}
948
949
950
/**
951
 * Push the parser memory to the stack.
952
 */
953
void TY_(pushMemory)( TidyDocImpl* doc, TidyParserMemory data )
954
0
{
955
0
    if ( doc->stack.top == doc->stack.size - 1 )
956
0
        growParserStack( doc );
957
958
0
    doc->stack.top++;
959
    
960
0
    doc->stack.content[doc->stack.top] = data;
961
0
    DEBUG_LOG(SPRTF("\n"
962
0
                    "-->PUSH original: %s @ %p\n"
963
0
                    "         reentry: %s @ %p\n"
964
0
                    "     stack depth: %lu @ %p\n"
965
0
                    "            mode: %u\n"
966
0
                    "      register 1: %i\n"
967
0
                    "      register 2: %i\n\n",
968
0
                    data.original_node ? data.original_node->element : "none", data.original_node,
969
0
                    data.reentry_node ? data.reentry_node->element : "none", data.reentry_node,
970
0
                    doc->stack.top, &doc->stack.content[doc->stack.top],
971
0
                    data.mode,
972
0
                    data.register_1,
973
0
                    data.register_2
974
0
                    ));
975
0
}
976
977
978
/***************************************************************************//*
979
 ** MARK: Convenience Logging Macros
980
 ***************************************************************************/
981
982
983
#if defined(ENABLE_DEBUG_LOG)
984
#  define DEBUG_LOG_COUNTERS \
985
     static int depth_parser = 0;\
986
     static int count_parser = 0;\
987
     int old_mode = IgnoreWhitespace;
988
#  define DEBUG_LOG_GET_OLD_MODE old_mode = mode;
989
#  define DEBUG_LOG_REENTER_WITH_NODE(NODE) SPRTF("\n>>>Re-Enter %s-%u with '%s', +++mode: %u, depth: %d, cnt: %d\n", __FUNCTION__, __LINE__, NODE->element, mode, ++depth_parser, ++count_parser);
990
#  define DEBUG_LOG_ENTER_WITH_NODE(NODE) SPRTF("\n>>>Enter %s-%u with '%s', +++mode: %u, depth: %d, cnt: %d\n", __FUNCTION__, __LINE__, NODE->element, mode, ++depth_parser, ++count_parser);
991
#  define DEBUG_LOG_CHANGE_MODE SPRTF("+++%s-%u Changing mode to %u (was %u)\n", __FUNCTION__, __LINE__, mode, old_mode);
992
#  define DEBUG_LOG_GOT_TOKEN(NODE) SPRTF("---%s-%u got token '%s' with mode '%u'.\n", __FUNCTION__, __LINE__, NODE ? NODE->element : NULL, mode);
993
#  define DEBUG_LOG_EXIT_WITH_NODE(NODE) SPRTF("<<<Exit %s-%u with a node to parse: '%s', depth: %d\n", __FUNCTION__, __LINE__, NODE->element, depth_parser--);
994
#  define DEBUG_LOG_EXIT SPRTF("<<<Exit %s-%u, depth: %d\n", __FUNCTION__, __LINE__, depth_parser--);
995
#else
996
#  define DEBUG_LOG_COUNTERS
997
#  define DEBUG_LOG_GET_OLD_MODE
998
#  define DEBUG_LOG_REENTER_WITH_NODE(NODE)
999
#  define DEBUG_LOG_ENTER_WITH_NODE(NODE)
1000
#  define DEBUG_LOG_CHANGE_MODE
1001
#  define DEBUG_LOG_GOT_TOKEN(NODE)
1002
#  define DEBUG_LOG_EXIT_WITH_NODE(NODE)
1003
#  define DEBUG_LOG_EXIT
1004
#endif
1005
1006
1007
/***************************************************************************//*
1008
 ** MARK: - Parser Search and Instantiation
1009
 ***************************************************************************/
1010
1011
1012
/**
1013
 *  Retrieves the correct parser for the given node, accounting for various
1014
 *  conditions, and readies the lexer for parsing that node.
1015
 */
1016
static Parser* GetParserForNode( TidyDocImpl* doc, Node *node )
1017
0
{
1018
0
    Lexer* lexer = doc->lexer;
1019
1020
0
    if ( cfgBool( doc, TidyXmlTags ) )
1021
0
        return ParseXMLElement;
1022
    
1023
    /* [i_a]2 prevent crash for active content (php, asp) docs */
1024
0
    if (!node || node->tag == NULL)
1025
0
        return NULL;
1026
1027
    /*
1028
       Fix by GLP 2000-12-21.  Need to reset insertspace if this is both
1029
       a non-inline and empty tag (base, link, meta, isindex, hr, area).
1030
    */
1031
0
    if (node->tag->model & CM_EMPTY)
1032
0
    {
1033
0
        lexer->waswhite = no;
1034
0
        if (node->tag->parser == NULL)
1035
0
            return NULL;
1036
0
    }
1037
0
    else if (!(node->tag->model & CM_INLINE))
1038
0
        lexer->insertspace = no;
1039
1040
0
    if (node->tag->parser == NULL)
1041
0
        return NULL;
1042
1043
0
    if (node->type == StartEndTag)
1044
0
        return NULL;
1045
1046
    /* [i_a]2 added this - not sure why - CHECKME: */
1047
0
    lexer->parent = node;
1048
1049
0
    return (node->tag->parser);
1050
0
}
1051
1052
1053
/**
1054
 *  This parser controller initiates the parsing process with the document's
1055
 *  root starting with the provided node, which should be the HTML node after
1056
 *  the pre-HTML stuff is handled at a higher level.
1057
 *
1058
 *  This controller is responsible for calling each of the individual parsers,
1059
 *  based on the tokens it pulls from the lexer, or the tokens passed back via
1060
 *  the parserMemory stack from each of the parsers. Having a main, central
1061
 *  looping dispatcher in this fashion allows the prevention of recursion.
1062
 */
1063
void ParseHTMLWithNode( TidyDocImpl* doc, Node* node )
1064
0
{
1065
0
    GetTokenMode mode = IgnoreWhitespace;
1066
0
    Parser* parser = GetParserForNode( doc, node );
1067
0
    Bool something_to_do = yes;
1068
1069
    /*
1070
     This main loop is only extinguished when all of the parser tokens are
1071
     consumed. Ideally, EVERY parser will return nodes to this loop for
1072
     dispatch to the appropriate parser, but some of the recursive parsers
1073
     still consume some tokens on their own.
1074
     */
1075
0
    while (something_to_do)
1076
0
    {
1077
0
        node = parser ? parser( doc, node, mode ) : NULL;
1078
        
1079
        /*
1080
         We have a node, so anything deferred was already pushed to the stack
1081
         to be dealt with later.
1082
         */
1083
0
        if ( node )
1084
0
        {
1085
0
            parser = GetParserForNode( doc, node );
1086
0
            continue;
1087
0
        }
1088
1089
        /*
1090
         We weren't given a node, which means this particular leaf is bottomed
1091
         out. We'll re-enter the parsers using information from the stack.
1092
         */
1093
0
        if ( !TY_(isEmptyParserStack)(doc))
1094
0
        {
1095
0
            parser = TY_(peekMemoryIdentity)(doc);
1096
0
            if (parser)
1097
0
            {
1098
0
                continue;
1099
0
            }
1100
0
            else
1101
0
            {
1102
                /* No parser means we're only passing back a parsing mode. */
1103
0
                mode = TY_(peekMemoryMode)( doc );
1104
0
                TY_(popMemory)( doc );
1105
0
            }
1106
0
        }
1107
        
1108
        /*
1109
         At this point, there's nothing being returned from parsers, and
1110
         nothing on the stack, so we can draw a new node from the lexer.
1111
         */
1112
0
        node = TY_(GetToken)( doc, mode );
1113
0
        DEBUG_LOG_GOT_TOKEN(node);
1114
1115
0
        if (node)
1116
0
            parser = GetParserForNode( doc, node );
1117
0
        else
1118
0
            something_to_do = no;
1119
0
    }
1120
0
}
1121
1122
1123
/***************************************************************************//*
1124
 ** MARK: - Parsers
1125
 ***************************************************************************/
1126
1127
1128
/** MARK: TY_(ParseBlock)
1129
 *  `element` is a node created by the lexer upon seeing the start tag, or
1130
 *  by the parser when the start tag is inferred
1131
 *
1132
 *  This is a non-recursing parser. It uses the document's parser memory stack
1133
 *  to send subsequent nodes back to the controller for dispatching to parsers.
1134
 *  This parser is also re-enterable, so that post-processing can occur after
1135
 *  such dispatching.
1136
 */
1137
Node* TY_(ParseBlock)( TidyDocImpl* doc, Node *element, GetTokenMode mode )
1138
0
{
1139
0
    Lexer* lexer = doc->lexer;
1140
0
    Node *node = NULL;
1141
0
    Bool checkstack = yes;
1142
0
    uint istackbase = 0;
1143
0
    DEBUG_LOG_COUNTERS;
1144
    
1145
0
    if ( element == NULL )
1146
0
    {
1147
0
        TidyParserMemory memory = TY_(popMemory)( doc );
1148
0
        node = memory.reentry_node; /* Throwaway, because the loop overwrites this immediately. */
1149
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
1150
0
        element = memory.original_node;
1151
0
        DEBUG_LOG_GET_OLD_MODE;
1152
0
        mode = memory.reentry_mode;
1153
0
        DEBUG_LOG_CHANGE_MODE;
1154
0
    }
1155
0
    else
1156
0
    {
1157
0
        DEBUG_LOG_ENTER_WITH_NODE(element);
1158
1159
0
        if ( element->tag->model & CM_EMPTY )
1160
0
        {
1161
0
            DEBUG_LOG_EXIT;
1162
0
            return NULL;
1163
0
        }
1164
1165
0
        if ( nodeIsDIV(element) && nodeIsDL(element->parent) && TY_(IsHTML5Mode)(doc) )
1166
0
        {
1167
0
            DEBUG_LOG_EXIT;
1168
0
            return TY_(ParseDefList)(doc, element, mode); /* @warning: possible recursion! */
1169
0
        }
1170
        
1171
0
        if ( nodeIsFORM(element) && DescendantOf(element, TidyTag_FORM) )
1172
0
        {
1173
0
            TY_(Report)(doc, element, NULL, ILLEGAL_NESTING );
1174
0
        }
1175
1176
        /*
1177
         InlineDup() asks the lexer to insert inline emphasis tags
1178
         currently pushed on the istack, but take care to avoid
1179
         propagating inline emphasis inside OBJECT or APPLET.
1180
         For these elements a fresh inline stack context is created
1181
         and disposed of upon reaching the end of the element.
1182
         They thus behave like table cells in this respect.
1183
        */
1184
0
        if (element->tag->model & CM_OBJECT)
1185
0
        {
1186
0
            istackbase = lexer->istackbase;
1187
0
            lexer->istackbase = lexer->istacksize;
1188
0
        }
1189
1190
0
        if (!(element->tag->model & CM_MIXED))
1191
0
        {
1192
0
            TY_(InlineDup)( doc, NULL );
1193
0
        }
1194
1195
        /*\
1196
         *  Issue #212 - If it is likely that it may be necessary
1197
         *  to move a leading space into a text node before this
1198
         *  element, then keep the mode MixedContent to keep any
1199
         *  leading space
1200
        \*/
1201
0
        if ( !(element->tag->model & CM_INLINE) ||
1202
0
              (element->tag->model & CM_FIELD ) )
1203
0
        {
1204
0
            DEBUG_LOG_GET_OLD_MODE;
1205
0
            mode = IgnoreWhitespace;
1206
0
            DEBUG_LOG_CHANGE_MODE;
1207
0
        }
1208
0
        else if (mode == IgnoreWhitespace)
1209
0
        {
1210
            /* Issue #212 - Further fix in case ParseBlock() is called with 'IgnoreWhitespace'
1211
               when such a leading space may need to be inserted before this element to
1212
               preserve the browser view */
1213
0
            DEBUG_LOG_GET_OLD_MODE;
1214
0
            mode = MixedContent;
1215
0
            DEBUG_LOG_CHANGE_MODE;
1216
0
        }
1217
0
    } /* Re-Entering */
1218
    
1219
    /*
1220
     Main Loop
1221
     */
1222
    
1223
0
    while ((node = TY_(GetToken)(doc, mode /*MixedContent*/)) != NULL)
1224
0
    {
1225
0
        DEBUG_LOG_GOT_TOKEN(node);
1226
        /* end tag for this element */
1227
0
        if (node->type == EndTag && node->tag &&
1228
0
            (node->tag == element->tag || element->was == node->tag))
1229
0
        {
1230
0
            TY_(FreeNode)( doc, node );
1231
1232
0
            if (element->tag->model & CM_OBJECT)
1233
0
            {
1234
                /* pop inline stack */
1235
0
                while (lexer->istacksize > lexer->istackbase)
1236
0
                    TY_(PopInline)( doc, NULL );
1237
0
                lexer->istackbase = istackbase;
1238
0
            }
1239
1240
0
            element->closed = yes;
1241
0
            TrimSpaces( doc, element );
1242
0
            DEBUG_LOG_EXIT;
1243
0
            return NULL;
1244
0
        }
1245
1246
0
        if ( nodeIsHTML(node) || nodeIsHEAD(node) || nodeIsBODY(node) )
1247
0
        {
1248
0
            if ( TY_(nodeIsElement)(node) )
1249
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1250
0
            TY_(FreeNode)( doc, node );
1251
0
            continue;
1252
0
        }
1253
1254
1255
0
        if (node->type == EndTag)
1256
0
        {
1257
0
            if (node->tag == NULL)
1258
0
            {
1259
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1260
0
                TY_(FreeNode)( doc, node );
1261
0
                continue;
1262
0
            }
1263
0
            else if ( nodeIsBR(node) )
1264
0
            {
1265
0
                node->type = StartTag;
1266
0
            }
1267
0
            else if ( nodeIsP(node) )
1268
0
            {
1269
                /* Cannot have a block inside a paragraph, so no checking
1270
                   for an ancestor is necessary -- but we _can_ have
1271
                   paragraphs inside a block, so change it to an implicit
1272
                   empty paragraph, to be dealt with according to the user's
1273
                   options
1274
                */
1275
0
                node->type = StartEndTag;
1276
0
                node->implicit = yes;
1277
0
            }
1278
0
            else if (DescendantOf( element, node->tag->id ))
1279
0
            {
1280
                /*
1281
                  if this is the end tag for an ancestor element
1282
                  then infer end tag for this element
1283
                */
1284
0
                TY_(UngetToken)( doc );
1285
0
                break;
1286
0
            }
1287
0
            else
1288
0
            {
1289
                /* special case </tr> etc. for stuff moved in front of table */
1290
0
                if ( lexer->exiled
1291
0
                     && (TY_(nodeHasCM)(node, CM_TABLE) || nodeIsTABLE(node)) )
1292
0
                {
1293
0
                    TY_(UngetToken)( doc );
1294
0
                    TrimSpaces( doc, element );
1295
0
                    DEBUG_LOG_EXIT;
1296
0
                    return NULL;
1297
0
                }
1298
0
            }
1299
0
        }
1300
1301
        /* mixed content model permits text */
1302
0
        if (TY_(nodeIsText)(node))
1303
0
        {
1304
0
            if ( checkstack )
1305
0
            {
1306
0
                checkstack = no;
1307
0
                if (!(element->tag->model & CM_MIXED))
1308
0
                {
1309
0
                    if ( TY_(InlineDup)(doc, node) > 0 )
1310
0
                        continue;
1311
0
                }
1312
0
            }
1313
1314
0
            TY_(InsertNodeAtEnd)(element, node);
1315
0
            DEBUG_LOG_GET_OLD_MODE
1316
0
            mode = MixedContent;
1317
0
            DEBUG_LOG_CHANGE_MODE;
1318
            /*
1319
              HTML4 strict doesn't allow mixed content for
1320
              elements with %block; as their content model
1321
            */
1322
            /*
1323
              But only body, map, blockquote, form and
1324
              noscript have content model %block;
1325
            */
1326
0
            if ( nodeIsBODY(element)       ||
1327
0
                 nodeIsMAP(element)        ||
1328
0
                 nodeIsBLOCKQUOTE(element) ||
1329
0
                 nodeIsFORM(element)       ||
1330
0
                 nodeIsNOSCRIPT(element) )
1331
0
                TY_(ConstrainVersion)( doc, ~VERS_HTML40_STRICT );
1332
0
            continue;
1333
0
        }
1334
1335
0
        if ( InsertMisc(element, node) )
1336
0
            continue;
1337
1338
        /* allow PARAM elements? */
1339
0
        if ( nodeIsPARAM(node) )
1340
0
        {
1341
0
            if ( TY_(nodeHasCM)(element, CM_PARAM) && TY_(nodeIsElement)(node) )
1342
0
            {
1343
0
                TY_(InsertNodeAtEnd)(element, node);
1344
0
                continue;
1345
0
            }
1346
1347
            /* otherwise discard it */
1348
0
            TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1349
0
            TY_(FreeNode)( doc, node );
1350
0
            continue;
1351
0
        }
1352
1353
        /* allow AREA elements? */
1354
0
        if ( nodeIsAREA(node) )
1355
0
        {
1356
0
            if ( nodeIsMAP(element) && TY_(nodeIsElement)(node) )
1357
0
            {
1358
0
                TY_(InsertNodeAtEnd)(element, node);
1359
0
                continue;
1360
0
            }
1361
1362
            /* otherwise discard it */
1363
0
            TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1364
0
            TY_(FreeNode)( doc, node );
1365
0
            continue;
1366
0
        }
1367
1368
        /* ignore unknown start/end tags */
1369
0
        if ( node->tag == NULL )
1370
0
        {
1371
0
            TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1372
0
            TY_(FreeNode)( doc, node );
1373
0
            continue;
1374
0
        }
1375
1376
        /*
1377
          Allow CM_INLINE elements here.
1378
1379
          Allow CM_BLOCK elements here unless
1380
          lexer->excludeBlocks is yes.
1381
1382
          LI and DD are special cased.
1383
1384
          Otherwise infer end tag for this element.
1385
        */
1386
1387
0
        if ( !TY_(nodeHasCM)(node, CM_INLINE) )
1388
0
        {
1389
0
            if ( !TY_(nodeIsElement)(node) )
1390
0
            {
1391
0
                if ( nodeIsFORM(node) )
1392
0
                    BadForm( doc );
1393
1394
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1395
0
                TY_(FreeNode)( doc, node );
1396
0
                continue;
1397
0
            }
1398
            
1399
            /* #427671 - Fix by Randy Waki - 10 Aug 00 */
1400
            /*
1401
             If an LI contains an illegal FRAME, FRAMESET, OPTGROUP, or OPTION
1402
             start tag, discard the start tag and let the subsequent content get
1403
             parsed as content of the enclosing LI.  This seems to mimic IE and
1404
             Netscape, and avoids an infinite loop: without this check,
1405
             ParseBlock (which is parsing the LI's content) and ParseList (which
1406
             is parsing the LI's parent's content) repeatedly defer to each
1407
             other to parse the illegal start tag, each time inferring a missing
1408
             </li> or <li> respectively.
1409
1410
             NOTE: This check is a bit fragile.  It specifically checks for the
1411
             four tags that happen to weave their way through the current series
1412
             of tests performed by ParseBlock and ParseList to trigger the
1413
             infinite loop.
1414
            */
1415
0
            if ( nodeIsLI(element) )
1416
0
            {
1417
0
                if ( nodeIsFRAME(node)    ||
1418
0
                     nodeIsFRAMESET(node) ||
1419
0
                     nodeIsOPTGROUP(node) ||
1420
0
                     nodeIsOPTION(node) )
1421
0
                {
1422
0
                    TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1423
0
                    TY_(FreeNode)( doc, node );  /* DSR - 27Apr02 avoid memory leak */
1424
0
                    continue;
1425
0
                }
1426
0
            }
1427
1428
0
            if ( nodeIsTD(element) || nodeIsTH(element) )
1429
0
            {
1430
                /* if parent is a table cell, avoid inferring the end of the cell */
1431
1432
0
                if ( TY_(nodeHasCM)(node, CM_HEAD) )
1433
0
                {
1434
0
                    MoveToHead( doc, element, node );
1435
0
                    continue;
1436
0
                }
1437
1438
0
                if ( TY_(nodeHasCM)(node, CM_LIST) )
1439
0
                {
1440
0
                    TY_(UngetToken)( doc );
1441
0
                    node = TY_(InferredTag)(doc, TidyTag_UL);
1442
0
                    AddClassNoIndent(doc, node);
1443
0
                    lexer->excludeBlocks = yes;
1444
0
                }
1445
0
                else if ( TY_(nodeHasCM)(node, CM_DEFLIST) )
1446
0
                {
1447
0
                    TY_(UngetToken)( doc );
1448
0
                    node = TY_(InferredTag)(doc, TidyTag_DL);
1449
0
                    lexer->excludeBlocks = yes;
1450
0
                }
1451
1452
                /* infer end of current table cell */
1453
0
                if ( !TY_(nodeHasCM)(node, CM_BLOCK) )
1454
0
                {
1455
0
                    TY_(UngetToken)( doc );
1456
0
                    TrimSpaces( doc, element );
1457
0
                    DEBUG_LOG_EXIT;
1458
0
                    return NULL;
1459
0
                }
1460
0
            }
1461
0
            else if ( TY_(nodeHasCM)(node, CM_BLOCK) )
1462
0
            {
1463
0
                if ( lexer->excludeBlocks )
1464
0
                {
1465
0
                    if ( !TY_(nodeHasCM)(element, CM_OPT) )
1466
0
                        TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE );
1467
1468
0
                    TY_(UngetToken)( doc );
1469
1470
0
                    if ( TY_(nodeHasCM)(element, CM_OBJECT) )
1471
0
                        lexer->istackbase = istackbase;
1472
1473
0
                    TrimSpaces( doc, element );
1474
0
                    DEBUG_LOG_EXIT;
1475
0
                    return NULL;
1476
0
                }
1477
0
            }
1478
0
            else if ( ! nodeIsTEMPLATE( element ) )/* things like list items */
1479
0
            {
1480
0
                if (node->tag->model & CM_HEAD)
1481
0
                {
1482
0
                    MoveToHead( doc, element, node );
1483
0
                    continue;
1484
0
                }
1485
1486
                /*
1487
                 special case where a form start tag
1488
                 occurs in a tr and is followed by td or th
1489
                */
1490
1491
0
                if ( nodeIsFORM(element) &&
1492
0
                     nodeIsTD(element->parent) &&
1493
0
                     element->parent->implicit )
1494
0
                {
1495
0
                    if ( nodeIsTD(node) )
1496
0
                    {
1497
0
                        TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1498
0
                        TY_(FreeNode)( doc, node );
1499
0
                        continue;
1500
0
                    }
1501
1502
0
                    if ( nodeIsTH(node) )
1503
0
                    {
1504
0
                        TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1505
0
                        TY_(FreeNode)( doc, node );
1506
0
                        node = element->parent;
1507
0
                        TidyDocFree(doc, node->element);
1508
0
                        node->element = TY_(tmbstrdup)(doc->allocator, "th");
1509
0
                        node->tag = TY_(LookupTagDef)( TidyTag_TH );
1510
0
                        continue;
1511
0
                    }
1512
0
                }
1513
1514
0
                if ( !TY_(nodeHasCM)(element, CM_OPT) && !element->implicit )
1515
0
                    TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE );
1516
1517
                /* #521, warn on missing optional end-tags if not omitting them. */
1518
0
                if ( cfgBool( doc, TidyOmitOptionalTags ) == no && TY_(nodeHasCM)(element, CM_OPT) )
1519
0
                    TY_(Report)(doc, element, node, MISSING_ENDTAG_OPTIONAL );
1520
1521
1522
0
                TY_(UngetToken)( doc );
1523
1524
0
                if ( TY_(nodeHasCM)(node, CM_LIST) )
1525
0
                {
1526
0
                    if ( element->parent && element->parent->tag &&
1527
0
                         element->parent->tag->parser == TY_(ParseList) )
1528
0
                    {
1529
0
                        TrimSpaces( doc, element );
1530
0
                        DEBUG_LOG_EXIT;
1531
0
                        return NULL;
1532
0
                    }
1533
1534
0
                    node = TY_(InferredTag)(doc, TidyTag_UL);
1535
0
                    AddClassNoIndent(doc, node);
1536
0
                }
1537
0
                else if ( TY_(nodeHasCM)(node, CM_DEFLIST) )
1538
0
                {
1539
0
                    if ( nodeIsDL(element->parent) )
1540
0
                    {
1541
0
                        TrimSpaces( doc, element );
1542
0
                        DEBUG_LOG_EXIT;
1543
0
                        return NULL;
1544
0
                    }
1545
1546
0
                    node = TY_(InferredTag)(doc, TidyTag_DL);
1547
0
                }
1548
0
                else if ( TY_(nodeHasCM)(node, CM_TABLE) || TY_(nodeHasCM)(node, CM_ROW) )
1549
0
                {
1550
                    /* http://tidy.sf.net/issue/1316307 */
1551
                    /* In exiled mode, return so table processing can
1552
                       continue. */
1553
0
                    if (lexer->exiled)
1554
0
                    {
1555
0
                        DEBUG_LOG_EXIT;
1556
0
                        return NULL;
1557
0
                    }
1558
0
                    node = TY_(InferredTag)(doc, TidyTag_TABLE);
1559
0
                }
1560
0
                else if ( TY_(nodeHasCM)(element, CM_OBJECT) )
1561
0
                {
1562
                    /* pop inline stack */
1563
0
                    while ( lexer->istacksize > lexer->istackbase )
1564
0
                        TY_(PopInline)( doc, NULL );
1565
0
                    lexer->istackbase = istackbase;
1566
0
                    TrimSpaces( doc, element );
1567
0
                    DEBUG_LOG_EXIT;
1568
0
                    return NULL;
1569
1570
0
                }
1571
0
                else
1572
0
                {
1573
0
                    TrimSpaces( doc, element );
1574
0
                    DEBUG_LOG_EXIT;
1575
0
                    return NULL;
1576
0
                }
1577
0
            }
1578
0
        }
1579
1580
        /*\
1581
         *  Issue #307 - an <A> tag to ends any open <A> element
1582
         *  Like #427827 - fixed by Randy Waki and Bjoern Hoehrmann 23 Aug 00
1583
         *  in ParseInline(), fix copied HERE to ParseBlock()
1584
         *  href: http://www.w3.org/TR/html-markup/a.html
1585
         *  The interactive element a must not appear as a descendant of the a element.
1586
        \*/
1587
0
        if ( nodeIsA(node) && !node->implicit &&
1588
0
             (nodeIsA(element) || DescendantOf(element, TidyTag_A)) )
1589
0
        {
1590
0
            if (node->type != EndTag && node->attributes == NULL
1591
0
                && cfgBool(doc, TidyCoerceEndTags) )
1592
0
            {
1593
0
                node->type = EndTag;
1594
0
                TY_(Report)(doc, element, node, COERCE_TO_ENDTAG);
1595
0
                TY_(UngetToken)( doc );
1596
0
                continue;
1597
0
            }
1598
1599
0
            if (nodeIsA(element))
1600
0
            {
1601
0
                TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE);
1602
0
                TY_(UngetToken)( doc );
1603
0
            }
1604
0
            else
1605
0
            {
1606
                /* Issue #597 - if we not 'UngetToken' then it is being discarded.
1607
                   Add message, and 'FreeNode' - thanks @ralfjunker */
1608
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
1609
0
                TY_(FreeNode)(doc, node);
1610
0
            }
1611
1612
0
            if (!(mode & Preformatted))
1613
0
                TrimSpaces(doc, element);
1614
1615
0
            DEBUG_LOG_EXIT;
1616
0
            return NULL;
1617
0
        }
1618
1619
        /* parse known element */
1620
0
        if (TY_(nodeIsElement)(node))
1621
0
        {
1622
0
            if (node->tag->model & CM_INLINE)
1623
0
            {
1624
0
                if (checkstack && !node->implicit)
1625
0
                {
1626
0
                    checkstack = no;
1627
1628
0
                    if (!(element->tag->model & CM_MIXED)) /* #431731 - fix by Randy Waki 25 Dec 00 */
1629
0
                    {
1630
0
                        if ( TY_(InlineDup)(doc, node) > 0 )
1631
0
                            continue;
1632
0
                    }
1633
0
                }
1634
1635
0
                DEBUG_LOG_GET_OLD_MODE;
1636
0
                mode = MixedContent;
1637
0
                DEBUG_LOG_CHANGE_MODE;
1638
0
            }
1639
0
            else
1640
0
            {
1641
0
                checkstack = yes;
1642
0
                DEBUG_LOG_GET_OLD_MODE;
1643
0
                mode = IgnoreWhitespace;
1644
0
                DEBUG_LOG_CHANGE_MODE;
1645
0
            }
1646
1647
            /* trim white space before <br> */
1648
0
            if ( nodeIsBR(node) )
1649
0
                TrimSpaces( doc, element );
1650
1651
0
            TY_(InsertNodeAtEnd)(element, node);
1652
1653
0
            if (node->implicit)
1654
0
                TY_(Report)(doc, element, node, INSERTING_TAG );
1655
1656
            /* Issue #212 - WHY is this hard coded to 'IgnoreWhitespace' while an
1657
               effort has been made above to set a 'MixedContent' mode in some cases?
1658
               WHY IS THE 'mode' VARIABLE NOT USED HERE???? */
1659
1660
0
            {
1661
0
                TidyParserMemory memory = {0};
1662
0
                memory.identity = TY_(ParseBlock);
1663
0
                memory.reentry_node = node;
1664
0
                memory.reentry_mode = mode;
1665
0
                memory.original_node = element;
1666
0
                TY_(pushMemory)(doc, memory);
1667
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
1668
0
            }
1669
0
            return node;
1670
0
        }
1671
1672
        /* discard unexpected tags */
1673
0
        if (node->type == EndTag)
1674
0
            TY_(PopInline)( doc, node );  /* if inline end tag */
1675
1676
0
        TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
1677
0
        TY_(FreeNode)( doc, node );
1678
0
        continue;
1679
0
    }
1680
1681
0
    if (!(element->tag->model & CM_OPT))
1682
0
        TY_(Report)(doc, element, node, MISSING_ENDTAG_FOR);
1683
1684
0
    if (element->tag->model & CM_OBJECT)
1685
0
    {
1686
        /* pop inline stack */
1687
0
        while ( lexer->istacksize > lexer->istackbase )
1688
0
            TY_(PopInline)( doc, NULL );
1689
0
        lexer->istackbase = istackbase;
1690
0
    }
1691
1692
0
    TrimSpaces( doc, element );
1693
1694
0
    DEBUG_LOG_EXIT;
1695
0
    return NULL;
1696
0
}
1697
1698
1699
/** MARK: TY_(ParseBody)
1700
 *  Parses the `body` tag.
1701
 *
1702
 *  This is a non-recursing parser. It uses the document's parser memory stack
1703
 *  to send subsequent nodes back to the controller for dispatching to parsers.
1704
 *  This parser is also re-enterable, so that post-processing can occur after
1705
 *  such dispatching.
1706
 */
1707
Node* TY_(ParseBody)( TidyDocImpl* doc, Node *body, GetTokenMode mode )
1708
0
{
1709
0
    Lexer* lexer = doc->lexer;
1710
0
    Node *node = NULL;
1711
0
    Bool checkstack = no;
1712
0
    Bool iswhitenode = no;
1713
0
    DEBUG_LOG_COUNTERS;
1714
1715
0
    mode = IgnoreWhitespace;
1716
0
    checkstack = yes;
1717
1718
    /*
1719
     If we're re-entering, then we need to setup from a previous state,
1720
     instead of starting fresh. We can pull what we need from the document's
1721
     stack.
1722
     */
1723
0
    if ( body == NULL )
1724
0
    {
1725
0
        TidyParserMemory memory = TY_(popMemory)( doc );
1726
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
1727
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
1728
0
        body = memory.original_node;
1729
0
        checkstack = memory.register_1;
1730
0
        iswhitenode = memory.register_2;
1731
0
        DEBUG_LOG_GET_OLD_MODE;
1732
0
        mode = memory.mode;
1733
0
        DEBUG_LOG_CHANGE_MODE;
1734
0
    }
1735
0
    else
1736
0
    {
1737
0
        DEBUG_LOG_ENTER_WITH_NODE(body);
1738
0
        TY_(BumpObject)( doc, body->parent );
1739
0
    }
1740
    
1741
0
    while ((node = TY_(GetToken)(doc, mode)) != NULL)
1742
0
    {
1743
0
        DEBUG_LOG_GOT_TOKEN(node);
1744
        /* find and discard multiple <body> elements */
1745
0
        if (node->tag == body->tag && node->type == StartTag)
1746
0
        {
1747
0
            TY_(Report)(doc, body, node, DISCARDING_UNEXPECTED);
1748
0
            TY_(FreeNode)(doc, node);
1749
0
            continue;
1750
0
        }
1751
1752
        /* #538536 Extra endtags not detected */
1753
0
        if ( nodeIsHTML(node) )
1754
0
        {
1755
0
            if (TY_(nodeIsElement)(node) || lexer->seenEndHtml)
1756
0
                TY_(Report)(doc, body, node, DISCARDING_UNEXPECTED);
1757
0
            else
1758
0
                lexer->seenEndHtml = 1;
1759
1760
0
            TY_(FreeNode)( doc, node);
1761
0
            continue;
1762
0
        }
1763
1764
0
        if ( lexer->seenEndBody &&
1765
0
             ( node->type == StartTag ||
1766
0
               node->type == EndTag   ||
1767
0
               node->type == StartEndTag ) )
1768
0
        {
1769
0
            TY_(Report)(doc, body, node, CONTENT_AFTER_BODY );
1770
0
        }
1771
1772
0
        if ( node->tag == body->tag && node->type == EndTag )
1773
0
        {
1774
0
            body->closed = yes;
1775
0
            TrimSpaces(doc, body);
1776
0
            TY_(FreeNode)( doc, node);
1777
0
            lexer->seenEndBody = 1;
1778
0
            DEBUG_LOG_GET_OLD_MODE;
1779
0
            mode = IgnoreWhitespace;
1780
0
            DEBUG_LOG_CHANGE_MODE;
1781
1782
0
            if ( nodeIsNOFRAMES(body->parent) )
1783
0
                break;
1784
1785
0
            continue;
1786
0
        }
1787
1788
0
        if ( nodeIsNOFRAMES(node) )
1789
0
        {
1790
0
            if (node->type == StartTag)
1791
0
            {
1792
0
                TidyParserMemory memory = {0};
1793
1794
0
                TY_(InsertNodeAtEnd)(body, node);
1795
                
1796
0
                memory.identity = TY_(ParseBody);
1797
0
                memory.original_node = body;
1798
0
                memory.reentry_node = node;
1799
0
                memory.register_1 = checkstack;
1800
0
                memory.register_2 = iswhitenode;
1801
0
                memory.mode = mode;
1802
0
                TY_(pushMemory)( doc, memory );
1803
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
1804
0
                return node;
1805
0
            }
1806
1807
0
            if (node->type == EndTag && nodeIsNOFRAMES(body->parent) )
1808
0
            {
1809
0
                TrimSpaces(doc, body);
1810
0
                TY_(UngetToken)( doc );
1811
0
                break;
1812
0
            }
1813
0
        }
1814
1815
0
        if ( (nodeIsFRAME(node) || nodeIsFRAMESET(node))
1816
0
             && nodeIsNOFRAMES(body->parent) )
1817
0
        {
1818
0
            TrimSpaces(doc, body);
1819
0
            TY_(UngetToken)( doc );
1820
0
            break;
1821
0
        }
1822
1823
0
        iswhitenode = no;
1824
1825
0
        if ( TY_(nodeIsText)(node) &&
1826
0
             node->end <= node->start + 1 &&
1827
0
             lexer->lexbuf[node->start] == ' ' )
1828
0
            iswhitenode = yes;
1829
1830
        /* deal with comments etc. */
1831
0
        if (InsertMisc(body, node))
1832
0
            continue;
1833
1834
        /* mixed content model permits text */
1835
0
        if (TY_(nodeIsText)(node))
1836
0
        {
1837
0
            if (iswhitenode && mode == IgnoreWhitespace)
1838
0
            {
1839
0
                TY_(FreeNode)( doc, node);
1840
0
                continue;
1841
0
            }
1842
1843
            /* HTML 2 and HTML4 strict don't allow text here */
1844
0
            TY_(ConstrainVersion)(doc, ~(VERS_HTML40_STRICT | VERS_HTML20));
1845
1846
0
            if (checkstack)
1847
0
            {
1848
0
                checkstack = no;
1849
1850
0
                if ( TY_(InlineDup)(doc, node) > 0 )
1851
0
                    continue;
1852
0
            }
1853
1854
0
            TY_(InsertNodeAtEnd)(body, node);
1855
0
            DEBUG_LOG_GET_OLD_MODE;
1856
0
            mode = MixedContent;
1857
0
            DEBUG_LOG_CHANGE_MODE;
1858
0
            continue;
1859
0
        }
1860
1861
0
        if (node->type == DocTypeTag)
1862
0
        {
1863
0
            InsertDocType(doc, body, node);
1864
0
            continue;
1865
0
        }
1866
        /* discard unknown  and PARAM tags */
1867
0
        if ( node->tag == NULL || nodeIsPARAM(node) )
1868
0
        {
1869
0
            TY_(Report)(doc, body, node, DISCARDING_UNEXPECTED);
1870
0
            TY_(FreeNode)( doc, node);
1871
0
            continue;
1872
0
        }
1873
1874
        /*
1875
          Netscape allows LI and DD directly in BODY
1876
          We infer UL or DL respectively and use this
1877
          Bool to exclude block-level elements so as
1878
          to match Netscape's observed behaviour.
1879
        */
1880
0
        lexer->excludeBlocks = no;
1881
1882
0
        if ((( nodeIsINPUT(node) ||
1883
0
             (!TY_(nodeHasCM)(node, CM_BLOCK) && !TY_(nodeHasCM)(node, CM_INLINE))
1884
0
           ) && !TY_(IsHTML5Mode)(doc)) || nodeIsLI(node) )
1885
0
        {
1886
            /* avoid this error message being issued twice */
1887
0
            if (!(node->tag->model & CM_HEAD))
1888
0
                TY_(Report)(doc, body, node, TAG_NOT_ALLOWED_IN);
1889
1890
0
            if (node->tag->model & CM_HTML)
1891
0
            {
1892
                /* copy body attributes if current body was inferred */
1893
0
                if ( nodeIsBODY(node) && body->implicit
1894
0
                     && body->attributes == NULL )
1895
0
                {
1896
0
                    body->attributes = node->attributes;
1897
0
                    node->attributes = NULL;
1898
0
                }
1899
1900
0
                TY_(FreeNode)( doc, node);
1901
0
                continue;
1902
0
            }
1903
1904
0
            if (node->tag->model & CM_HEAD)
1905
0
            {
1906
0
                MoveToHead(doc, body, node);
1907
0
                continue;
1908
0
            }
1909
1910
0
            if (node->tag->model & CM_LIST)
1911
0
            {
1912
0
                TY_(UngetToken)( doc );
1913
0
                node = TY_(InferredTag)(doc, TidyTag_UL);
1914
0
                AddClassNoIndent(doc, node);
1915
0
                lexer->excludeBlocks = yes;
1916
0
            }
1917
0
            else if (node->tag->model & CM_DEFLIST)
1918
0
            {
1919
0
                TY_(UngetToken)( doc );
1920
0
                node = TY_(InferredTag)(doc, TidyTag_DL);
1921
0
                lexer->excludeBlocks = yes;
1922
0
            }
1923
0
            else if (node->tag->model & (CM_TABLE | CM_ROWGRP | CM_ROW))
1924
0
            {
1925
                /* http://tidy.sf.net/issue/2855621 */
1926
0
                if (node->type != EndTag) {
1927
0
                    TY_(UngetToken)( doc );
1928
0
                    node = TY_(InferredTag)(doc, TidyTag_TABLE);
1929
0
                }
1930
0
                lexer->excludeBlocks = yes;
1931
0
            }
1932
0
            else if ( nodeIsINPUT(node) )
1933
0
            {
1934
0
                TY_(UngetToken)( doc );
1935
0
                node = TY_(InferredTag)(doc, TidyTag_FORM);
1936
0
                lexer->excludeBlocks = yes;
1937
0
            }
1938
0
            else
1939
0
            {
1940
0
                if ( !TY_(nodeHasCM)(node, CM_ROW | CM_FIELD) )
1941
0
                {
1942
0
                    TY_(UngetToken)( doc );
1943
0
                    DEBUG_LOG_EXIT;
1944
0
                    return NULL;
1945
0
                }
1946
1947
                /* ignore </td> </th> <option> etc. */
1948
0
                TY_(FreeNode)( doc, node );
1949
0
                continue;
1950
0
            }
1951
0
        }
1952
1953
0
        if (node->type == EndTag)
1954
0
        {
1955
0
            if ( nodeIsBR(node) )
1956
0
            {
1957
0
                node->type = StartTag;
1958
0
            }
1959
0
            else if ( nodeIsP(node) )
1960
0
            {
1961
0
                node->type = StartEndTag;
1962
0
                node->implicit = yes;
1963
0
            }
1964
0
            else if ( TY_(nodeHasCM)(node, CM_INLINE) )
1965
0
                TY_(PopInline)( doc, node );
1966
0
        }
1967
1968
0
        if (TY_(nodeIsElement)(node))
1969
0
        {
1970
0
            if (nodeIsMAIN(node))
1971
0
            {
1972
                /*\ Issue #166 - repeated <main> element
1973
                 *  How to efficiently search for a previous main element?
1974
                \*/
1975
0
                if ( findNodeById(doc, TidyTag_MAIN) )
1976
0
                {
1977
0
                    doc->badForm |= flg_BadMain; /* this is an ERROR in format */
1978
0
                    TY_(Report)(doc, body, node, DISCARDING_UNEXPECTED);
1979
0
                    TY_(FreeNode)( doc, node);
1980
0
                    continue;
1981
0
                }
1982
0
            }
1983
            /* Issue #20 - merging from Ger Hobbelt fork put back CM_MIXED, which had been
1984
               removed to fix this issue - reverting to fix 880221e
1985
             */
1986
0
            if ( TY_(nodeHasCM)(node, CM_INLINE) )
1987
0
            {
1988
                /* HTML4 strict doesn't allow inline content here */
1989
                /* but HTML2 does allow img elements as children of body */
1990
0
                if ( nodeIsIMG(node) )
1991
0
                    TY_(ConstrainVersion)(doc, ~VERS_HTML40_STRICT);
1992
0
                else
1993
0
                    TY_(ConstrainVersion)(doc, ~(VERS_HTML40_STRICT|VERS_HTML20));
1994
1995
0
                if (checkstack && !node->implicit)
1996
0
                {
1997
0
                    checkstack = no;
1998
1999
0
                    if ( TY_(InlineDup)(doc, node) > 0 )
2000
0
                        continue;
2001
0
                }
2002
                
2003
0
                DEBUG_LOG_GET_OLD_MODE;
2004
0
                mode = MixedContent;
2005
0
                DEBUG_LOG_CHANGE_MODE;
2006
0
            }
2007
0
            else
2008
0
            {
2009
0
                checkstack = yes;
2010
0
                DEBUG_LOG_GET_OLD_MODE;
2011
0
                mode = IgnoreWhitespace;
2012
0
                DEBUG_LOG_CHANGE_MODE;
2013
0
            }
2014
2015
0
            if (node->implicit)
2016
0
            {
2017
0
                TY_(Report)(doc, body, node, INSERTING_TAG);
2018
0
            }
2019
2020
0
            TY_(InsertNodeAtEnd)(body, node);
2021
            
2022
0
            {
2023
0
                TidyParserMemory memory = {0};
2024
0
                memory.identity = TY_(ParseBody);
2025
0
                memory.original_node = body;
2026
0
                memory.reentry_node = node;
2027
0
                memory.register_1 = checkstack;
2028
0
                memory.register_2 = iswhitenode;
2029
0
                memory.mode = mode;
2030
0
                TY_(pushMemory)( doc, memory );
2031
0
            }
2032
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
2033
0
            return node;
2034
0
        }
2035
2036
        /* discard unexpected tags */
2037
0
        TY_(Report)(doc, body, node, DISCARDING_UNEXPECTED);
2038
0
        TY_(FreeNode)( doc, node);
2039
0
    }
2040
0
    DEBUG_LOG_EXIT;
2041
0
    return NULL;
2042
0
}
2043
2044
2045
/** MARK: TY_(ParseColGroup)
2046
 *  Parses the `colgroup` tag.
2047
 *
2048
 *  This is a non-recursing parser. It uses the document's parser memory stack
2049
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2050
 *  This parser is also re-enterable, so that post-processing can occur after
2051
 *  such dispatching.
2052
 */
2053
Node* TY_(ParseColGroup)( TidyDocImpl* doc, Node *colgroup, GetTokenMode ARG_UNUSED(mode) )
2054
0
{
2055
0
    Node *node, *parent;
2056
0
    DEBUG_LOG_COUNTERS;
2057
2058
    /*
2059
     If we're re-entering, then we need to setup from a previous state,
2060
     instead of starting fresh. We can pull what we need from the document's
2061
     stack.
2062
     */
2063
0
    if ( colgroup == NULL )
2064
0
    {
2065
0
        TidyParserMemory memory = TY_(popMemory)( doc );
2066
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
2067
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
2068
0
        colgroup = memory.original_node;
2069
0
        DEBUG_LOG_GET_OLD_MODE;
2070
0
        mode = memory.mode;
2071
0
        DEBUG_LOG_CHANGE_MODE;
2072
0
    }
2073
0
    else
2074
0
    {
2075
0
        DEBUG_LOG_ENTER_WITH_NODE(colgroup);
2076
0
        if (colgroup->tag->model & CM_EMPTY)
2077
0
            return NULL;
2078
0
    }
2079
2080
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
2081
0
    {
2082
0
        DEBUG_LOG_GOT_TOKEN(node);
2083
2084
0
        if (node->tag == colgroup->tag && node->type == EndTag)
2085
0
        {
2086
0
            TY_(FreeNode)( doc, node);
2087
0
            colgroup->closed = yes;
2088
0
            return NULL;
2089
0
        }
2090
2091
        /*
2092
          if this is the end tag for an ancestor element
2093
          then infer end tag for this element
2094
        */
2095
0
        if (node->type == EndTag)
2096
0
        {
2097
0
            if ( nodeIsFORM(node) )
2098
0
            {
2099
0
                BadForm( doc );
2100
0
                TY_(Report)(doc, colgroup, node, DISCARDING_UNEXPECTED);
2101
0
                TY_(FreeNode)( doc, node);
2102
0
                continue;
2103
0
            }
2104
2105
0
            for ( parent = colgroup->parent;
2106
0
                  parent != NULL;
2107
0
                  parent = parent->parent )
2108
0
            {
2109
0
                if (node->tag == parent->tag)
2110
0
                {
2111
0
                    TY_(UngetToken)( doc );
2112
0
                    DEBUG_LOG_EXIT;
2113
0
                    return NULL;
2114
0
                }
2115
0
            }
2116
0
        }
2117
2118
0
        if (TY_(nodeIsText)(node))
2119
0
        {
2120
0
            TY_(UngetToken)( doc );
2121
0
            DEBUG_LOG_EXIT;
2122
0
            return NULL;
2123
0
        }
2124
2125
        /* deal with comments etc. */
2126
0
        if (InsertMisc(colgroup, node))
2127
0
            continue;
2128
2129
        /* discard unknown tags */
2130
0
        if (node->tag == NULL)
2131
0
        {
2132
0
            TY_(Report)(doc, colgroup, node, DISCARDING_UNEXPECTED);
2133
0
            TY_(FreeNode)( doc, node);
2134
0
            continue;
2135
0
        }
2136
2137
0
        if ( !nodeIsCOL(node) )
2138
0
        {
2139
0
            TY_(UngetToken)( doc );
2140
0
            DEBUG_LOG_EXIT;
2141
0
            return NULL;
2142
0
        }
2143
2144
0
        if (node->type == EndTag)
2145
0
        {
2146
0
            TY_(Report)(doc, colgroup, node, DISCARDING_UNEXPECTED);
2147
0
            TY_(FreeNode)( doc, node);
2148
0
            continue;
2149
0
        }
2150
2151
        /* node should be <COL> */
2152
0
        TY_(InsertNodeAtEnd)(colgroup, node);
2153
        
2154
0
        {
2155
0
            TidyParserMemory memory = {0};
2156
0
            memory.identity = TY_(ParseColGroup);
2157
0
            memory.original_node = colgroup;
2158
0
            memory.reentry_node = node;
2159
0
            memory.mode = mode;
2160
0
            TY_(pushMemory)( doc, memory );
2161
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
2162
0
        }
2163
0
        DEBUG_LOG_EXIT;
2164
0
        return node;
2165
0
    }
2166
0
    DEBUG_LOG_EXIT;
2167
0
    return NULL;
2168
0
}
2169
2170
2171
/** MARK: TY_(ParseDatalist)
2172
 *  Parses the `datalist` tag.
2173
 *
2174
 *  This is a non-recursing parser. It uses the document's parser memory stack
2175
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2176
 *  This parser is also re-enterable, so that post-processing can occur after
2177
 *  such dispatching.
2178
*/
2179
Node* TY_(ParseDatalist)( TidyDocImpl* doc, Node *field, GetTokenMode ARG_UNUSED(mode) )
2180
0
{
2181
0
    Lexer* lexer = doc->lexer;
2182
0
    Node *node;
2183
0
    DEBUG_LOG_COUNTERS;
2184
2185
0
    if ( field == NULL )
2186
0
    {
2187
0
        TidyParserMemory memory = TY_(popMemory)( doc );
2188
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
2189
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
2190
0
        field = memory.original_node;
2191
0
        DEBUG_LOG_GET_OLD_MODE;
2192
0
        mode = memory.mode;
2193
0
        DEBUG_LOG_CHANGE_MODE;
2194
0
    }
2195
0
    else
2196
0
    {
2197
0
        DEBUG_LOG_ENTER_WITH_NODE(field);
2198
0
    }
2199
    
2200
0
    lexer->insert = NULL;  /* defer implicit inline start tags */
2201
2202
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
2203
0
    {
2204
0
        if (node->tag == field->tag && node->type == EndTag)
2205
0
        {
2206
0
            TY_(FreeNode)( doc, node);
2207
0
            field->closed = yes;
2208
0
            TrimSpaces(doc, field);
2209
2210
0
            DEBUG_LOG_EXIT;
2211
0
            return NULL;
2212
0
        }
2213
2214
        /* deal with comments etc. */
2215
0
        if (InsertMisc(field, node))
2216
0
            continue;
2217
2218
0
        if ( node->type == StartTag &&
2219
0
             ( nodeIsOPTION(node)   ||
2220
0
               nodeIsOPTGROUP(node) ||
2221
0
               nodeIsDATALIST(node) ||
2222
0
               nodeIsSCRIPT(node))
2223
0
           )
2224
0
        {
2225
0
            TidyParserMemory memory = {0};
2226
0
            memory.identity = TY_(ParseDatalist);
2227
0
            memory.original_node = field;
2228
0
            memory.reentry_node = node;
2229
0
            memory.reentry_mode = IgnoreWhitespace;
2230
2231
0
            TY_(InsertNodeAtEnd)(field, node);
2232
0
            TY_(pushMemory)(doc, memory);
2233
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
2234
0
            return node;
2235
0
        }
2236
2237
        /* discard unexpected tags */
2238
0
        TY_(Report)(doc, field, node, DISCARDING_UNEXPECTED);
2239
0
        TY_(FreeNode)( doc, node);
2240
0
    }
2241
2242
0
    TY_(Report)(doc, field, node, MISSING_ENDTAG_FOR);
2243
2244
0
    DEBUG_LOG_EXIT;
2245
0
    return NULL;
2246
0
}
2247
2248
2249
/** MARK: TY_(ParseDefList)
2250
 *  Parses the `dl` tag.
2251
 *
2252
 *  This is a non-recursing parser. It uses the document's parser memory stack
2253
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2254
 *  This parser is also re-enterable, so that post-processing can occur after
2255
 *  such dispatching.
2256
*/
2257
Node* TY_(ParseDefList)( TidyDocImpl* doc, Node *list, GetTokenMode mode )
2258
0
{
2259
0
    Lexer* lexer = doc->lexer;
2260
0
    Node *node = NULL;
2261
0
    Node *parent = NULL;
2262
0
    DEBUG_LOG_COUNTERS;
2263
2264
0
    enum parserState {
2265
0
        STATE_INITIAL,                /* This is the initial state for every parser. */
2266
0
        STATE_POST_NODEISCENTER,      /* To-do after re-entering after checks. */
2267
0
        STATE_COMPLETE,               /* Done with the switch. */
2268
0
    } state = STATE_INITIAL;
2269
2270
0
    if ( list == NULL )
2271
0
    {
2272
0
        TidyParserMemory memory = TY_(popMemory)( doc );
2273
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
2274
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
2275
0
        list = memory.original_node;
2276
0
        state = memory.reentry_state;
2277
0
        DEBUG_LOG_GET_OLD_MODE;
2278
0
        mode = memory.mode;
2279
0
        DEBUG_LOG_CHANGE_MODE;
2280
0
    }
2281
0
    else
2282
0
    {
2283
0
        DEBUG_LOG_ENTER_WITH_NODE(list);
2284
0
    }
2285
2286
0
    if (list->tag->model & CM_EMPTY)
2287
0
        return NULL;
2288
2289
0
    lexer->insert = NULL;  /* defer implicit inline start tags */
2290
2291
0
    while ( state != STATE_COMPLETE )
2292
0
    {
2293
0
        if ( state == STATE_INITIAL )
2294
0
            node = TY_(GetToken)( doc, IgnoreWhitespace);
2295
        
2296
0
        switch ( state)
2297
0
        {
2298
0
            case STATE_INITIAL:
2299
0
            {
2300
0
                if ( node == NULL)
2301
0
                {
2302
0
                    state = STATE_COMPLETE;
2303
0
                    continue;
2304
0
                }
2305
2306
0
                if (node->tag == list->tag && node->type == EndTag)
2307
0
                {
2308
0
                    TY_(FreeNode)( doc, node);
2309
0
                    list->closed = yes;
2310
0
                    DEBUG_LOG_EXIT;
2311
0
                    return NULL;
2312
0
                }
2313
2314
                /* deal with comments etc. */
2315
0
                if (InsertMisc(list, node))
2316
0
                    continue;
2317
2318
0
                if (TY_(nodeIsText)(node))
2319
0
                {
2320
0
                    TY_(UngetToken)( doc );
2321
0
                    node = TY_(InferredTag)(doc, TidyTag_DT);
2322
0
                    TY_(Report)(doc, list, node, MISSING_STARTTAG);
2323
0
                }
2324
2325
0
                if (node->tag == NULL)
2326
0
                {
2327
0
                    TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
2328
0
                    TY_(FreeNode)( doc, node);
2329
0
                    continue;
2330
0
                }
2331
2332
                /*
2333
                  if this is the end tag for an ancestor element
2334
                  then infer end tag for this element
2335
                */
2336
0
                if (node->type == EndTag)
2337
0
                {
2338
0
                    Bool discardIt = no;
2339
0
                    if ( nodeIsFORM(node) )
2340
0
                    {
2341
0
                        BadForm( doc );
2342
0
                        TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
2343
0
                        TY_(FreeNode)( doc, node );
2344
0
                        continue;
2345
0
                    }
2346
2347
0
                    for (parent = list->parent;
2348
0
                            parent != NULL; parent = parent->parent)
2349
0
                    {
2350
                       /* Do not match across BODY to avoid infinite loop
2351
                          between ParseBody and this parser,
2352
                          See http://tidy.sf.net/bug/1098012. */
2353
0
                        if (nodeIsBODY(parent))
2354
0
                        {
2355
0
                            discardIt = yes;
2356
0
                            break;
2357
0
                        }
2358
0
                        if (node->tag == parent->tag)
2359
0
                        {
2360
0
                            TY_(Report)(doc, list, node, MISSING_ENDTAG_BEFORE);
2361
0
                            TY_(UngetToken)( doc );
2362
2363
0
                            DEBUG_LOG_EXIT;
2364
0
                            return NULL;
2365
0
                        }
2366
0
                    }
2367
0
                    if (discardIt)
2368
0
                    {
2369
0
                        TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
2370
0
                        TY_(FreeNode)( doc, node);
2371
0
                        continue;
2372
0
                    }
2373
0
                }
2374
2375
                /* center in a dt or a dl breaks the dl list in two */
2376
0
                if ( nodeIsCENTER(node) )
2377
0
                {
2378
0
                    if (list->content)
2379
0
                        TY_(InsertNodeAfterElement)(list, node);
2380
0
                    else /* trim empty dl list */
2381
0
                    {
2382
0
                        TY_(InsertNodeBeforeElement)(list, node);
2383
0
                    }
2384
2385
                    /* #426885 - fix by Glenn Carroll 19 Apr 00, and
2386
                                 Gary Dechaines 11 Aug 00 */
2387
                    /* ParseTag can destroy node, if it finds that
2388
                     * this <center> is followed immediately by </center>.
2389
                     * It's awkward but necessary to determine if this
2390
                     * has happened.
2391
                     */
2392
0
                    parent = node->parent;
2393
2394
                    /* and parse contents of center */
2395
0
                    lexer->excludeBlocks = no;
2396
2397
0
                    {
2398
0
                        TidyParserMemory memory = {0};
2399
0
                        memory.identity = TY_(ParseDefList);
2400
0
                        memory.original_node = list;
2401
0
                        memory.reentry_node = node;
2402
0
                        memory.reentry_state = STATE_POST_NODEISCENTER;
2403
0
                        TY_(pushMemory)( doc, memory );
2404
0
                        DEBUG_LOG_EXIT_WITH_NODE(node);
2405
0
                        return node;
2406
0
                    }
2407
0
                }
2408
2409
0
                if ( !( nodeIsDT(node) || nodeIsDD(node) || ( nodeIsDIV(node) && TY_(IsHTML5Mode)(doc) ) ) )
2410
0
                {
2411
0
                    TY_(UngetToken)( doc );
2412
2413
0
                    if (!(node->tag->model & (CM_BLOCK | CM_INLINE)))
2414
0
                    {
2415
0
                        TY_(Report)(doc, list, node, TAG_NOT_ALLOWED_IN);
2416
0
                        DEBUG_LOG_EXIT;
2417
0
                        return NULL;
2418
0
                    }
2419
2420
                    /* if DD appeared directly in BODY then exclude blocks */
2421
0
                    if (!(node->tag->model & CM_INLINE) && lexer->excludeBlocks)
2422
0
                    {
2423
0
                        DEBUG_LOG_EXIT;
2424
0
                        return NULL;
2425
0
                    }
2426
2427
0
                    node = TY_(InferredTag)(doc, TidyTag_DD);
2428
0
                    TY_(Report)(doc, list, node, MISSING_STARTTAG);
2429
0
                }
2430
2431
0
                if (node->type == EndTag)
2432
0
                {
2433
0
                    TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
2434
0
                    TY_(FreeNode)( doc, node);
2435
0
                    continue;
2436
0
                }
2437
2438
                /* node should be <DT> or <DD> or <DIV>*/
2439
0
                TY_(InsertNodeAtEnd)(list, node);
2440
0
                {
2441
0
                    TidyParserMemory memory = {0};
2442
0
                    memory.identity = TY_(ParseDefList);
2443
0
                    memory.original_node = list;
2444
0
                    memory.reentry_node = node;
2445
0
                    memory.reentry_state = STATE_INITIAL;
2446
0
                    TY_(pushMemory)( doc, memory );
2447
0
                    DEBUG_LOG_EXIT;
2448
0
                    return node;
2449
0
                }
2450
0
            } break;
2451
2452
2453
0
            case STATE_POST_NODEISCENTER:
2454
0
            {
2455
0
                lexer->excludeBlocks = yes;
2456
2457
                /* now create a new dl element,
2458
                 * unless node has been blown away because the
2459
                 * center was empty, as above.
2460
                 */
2461
0
                if (parent && parent->last == node)
2462
0
                {
2463
0
                    list = TY_(InferredTag)(doc, TidyTag_DL);
2464
0
                    TY_(InsertNodeAfterElement)(node, list);
2465
0
                }
2466
0
                state = STATE_INITIAL;
2467
0
                continue;
2468
0
            } break;
2469
2470
2471
0
            default:
2472
0
                break;
2473
0
        } /* switch */
2474
0
    } /* while */
2475
2476
0
    TY_(Report)(doc, list, node, MISSING_ENDTAG_FOR);
2477
0
    DEBUG_LOG_EXIT;
2478
0
    return NULL;
2479
0
}
2480
2481
2482
/** MARK: TY_(ParseEmpty)
2483
 *  Parse empty element nodes.
2484
 *
2485
 *  This is a non-recursing parser. It uses the document's parser memory stack
2486
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2487
 *  This parser is also re-enterable, so that post-processing can occur after
2488
 *  such dispatching.
2489
  */
2490
Node* TY_(ParseEmpty)( TidyDocImpl* doc, Node *element, GetTokenMode mode )
2491
0
{
2492
0
    Lexer* lexer = doc->lexer;
2493
0
    if ( lexer->isvoyager )
2494
0
    {
2495
0
        Node *node = TY_(GetToken)( doc, mode);
2496
0
        if ( node )
2497
0
        {
2498
0
            if ( !(node->type == EndTag && node->tag == element->tag) )
2499
0
            {
2500
                /* TY_(Report)(doc, element, node, ELEMENT_NOT_EMPTY); */
2501
0
                TY_(UngetToken)( doc );
2502
0
            }
2503
0
            else
2504
0
            {
2505
0
                TY_(FreeNode)( doc, node );
2506
0
            }
2507
0
        }
2508
0
    }
2509
0
    return NULL;
2510
0
}
2511
2512
2513
/** MARK: TY_(ParseFrameSet)
2514
 *  Parses the `frameset` tag.
2515
 *
2516
 *  This is a non-recursing parser. It uses the document's parser memory stack
2517
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2518
 *  This parser is also re-enterable, so that post-processing can occur after
2519
 *  such dispatching.
2520
 */
2521
Node* TY_(ParseFrameSet)( TidyDocImpl* doc, Node *frameset, GetTokenMode ARG_UNUSED(mode) )
2522
0
{
2523
0
    Lexer* lexer = doc->lexer;
2524
0
    Node *node;
2525
0
    DEBUG_LOG_COUNTERS;
2526
2527
    /*
2528
     If we're re-entering, then we need to setup from a previous state,
2529
     instead of starting fresh. We can pull what we need from the document's
2530
     stack.
2531
     */
2532
0
    if ( frameset == NULL )
2533
0
    {
2534
0
        TidyParserMemory memory = TY_(popMemory)( doc );
2535
0
        node = memory.reentry_node; /* Throwaway, because we replace it entering the loop. */
2536
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
2537
0
        frameset = memory.original_node;
2538
0
        DEBUG_LOG_GET_OLD_MODE;
2539
0
        mode = memory.mode;
2540
0
        DEBUG_LOG_CHANGE_MODE;
2541
0
    }
2542
0
    else
2543
0
    {
2544
0
        DEBUG_LOG_ENTER_WITH_NODE(frameset);
2545
0
        if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
2546
0
        {
2547
0
            doc->badAccess |= BA_USING_FRAMES;
2548
0
        }
2549
0
    }
2550
2551
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
2552
0
    {
2553
0
        if (node->tag == frameset->tag && node->type == EndTag)
2554
0
        {
2555
0
            TY_(FreeNode)( doc, node);
2556
0
            frameset->closed = yes;
2557
0
            TrimSpaces(doc, frameset);
2558
0
            DEBUG_LOG_EXIT;
2559
0
            return NULL;
2560
0
        }
2561
2562
        /* deal with comments etc. */
2563
0
        if (InsertMisc(frameset, node))
2564
0
            continue;
2565
2566
0
        if (node->tag == NULL)
2567
0
        {
2568
0
            TY_(Report)(doc, frameset, node, DISCARDING_UNEXPECTED);
2569
0
            TY_(FreeNode)( doc, node);
2570
0
            continue;
2571
0
        }
2572
2573
0
        if (TY_(nodeIsElement)(node))
2574
0
        {
2575
0
            if (node->tag && node->tag->model & CM_HEAD)
2576
0
            {
2577
0
                MoveToHead(doc, frameset, node);
2578
0
                continue;
2579
0
            }
2580
0
        }
2581
2582
0
        if ( nodeIsBODY(node) )
2583
0
        {
2584
0
            TY_(UngetToken)( doc );
2585
0
            node = TY_(InferredTag)(doc, TidyTag_NOFRAMES);
2586
0
            TY_(Report)(doc, frameset, node, INSERTING_TAG);
2587
0
        }
2588
2589
0
        if (node->type == StartTag && (node->tag && node->tag->model & CM_FRAMES))
2590
0
        {
2591
0
            TY_(InsertNodeAtEnd)(frameset, node);
2592
0
            lexer->excludeBlocks = no;
2593
            
2594
            /*
2595
             * We don't really have to do anything when re-entering, except
2596
             * setting up the state when we left. No post-processing means
2597
             * this stays simple.
2598
             */
2599
0
            TidyParserMemory memory = {0};
2600
0
            memory.identity = TY_(ParseFrameSet);
2601
0
            memory.original_node = frameset;
2602
0
            memory.reentry_node = node;
2603
0
            memory.mode = MixedContent;
2604
0
            TY_(pushMemory)( doc, memory );
2605
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
2606
0
            return node;
2607
0
        }
2608
0
        else if (node->type == StartEndTag && (node->tag && node->tag->model & CM_FRAMES))
2609
0
        {
2610
0
            TY_(InsertNodeAtEnd)(frameset, node);
2611
0
            continue;
2612
0
        }
2613
2614
        /* discard unexpected tags */
2615
        /* WAI [6.5.1.4] link is being discarded outside of NOFRAME */
2616
0
        if ( nodeIsA(node) )
2617
0
           doc->badAccess |= BA_INVALID_LINK_NOFRAMES;
2618
2619
0
        TY_(Report)(doc, frameset, node, DISCARDING_UNEXPECTED);
2620
0
        TY_(FreeNode)( doc, node);
2621
0
    }
2622
2623
0
    TY_(Report)(doc, frameset, node, MISSING_ENDTAG_FOR);
2624
0
    DEBUG_LOG_EXIT;
2625
0
    return NULL;
2626
0
}
2627
2628
2629
/** MARK: TY_(ParseHead)
2630
 *  Parses the `head` tag.
2631
 *
2632
 *  This is a non-recursing parser. It uses the document's parser memory stack
2633
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2634
 *  This parser is also re-enterable, so that post-processing can occur after
2635
 *  such dispatching.
2636
 */
2637
Node* TY_(ParseHead)( TidyDocImpl* doc, Node *head, GetTokenMode ARG_UNUSED(mode) )
2638
0
{
2639
0
    Lexer* lexer = doc->lexer;
2640
0
    Node *node;
2641
0
    int HasTitle = 0;
2642
0
    int HasBase = 0;
2643
0
    DEBUG_LOG_COUNTERS;
2644
2645
0
    if ( head == NULL )
2646
0
    {
2647
0
        TidyParserMemory memory = TY_(popMemory)( doc );
2648
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
2649
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
2650
0
        head = memory.original_node;
2651
0
        HasTitle = memory.register_1;
2652
0
        HasBase = memory.register_2;
2653
0
        DEBUG_LOG_GET_OLD_MODE;
2654
0
        mode = memory.mode;
2655
0
        DEBUG_LOG_CHANGE_MODE;
2656
0
    }
2657
0
    else
2658
0
    {
2659
0
        DEBUG_LOG_ENTER_WITH_NODE(head);
2660
0
    }
2661
    
2662
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
2663
0
    {
2664
0
        if (node->tag == head->tag && node->type == EndTag)
2665
0
        {
2666
0
            TY_(FreeNode)( doc, node);
2667
0
            head->closed = yes;
2668
0
            break;
2669
0
        }
2670
2671
        /* find and discard multiple <head> elements */
2672
        /* find and discard <html> in <head> elements */
2673
0
        if ((node->tag == head->tag || nodeIsHTML(node)) && node->type == StartTag)
2674
0
        {
2675
0
            TY_(Report)(doc, head, node, DISCARDING_UNEXPECTED);
2676
0
            TY_(FreeNode)(doc, node);
2677
0
            continue;
2678
0
        }
2679
2680
0
        if (TY_(nodeIsText)(node))
2681
0
        {
2682
            /*\ Issue #132 - avoid warning for missing body tag,
2683
             *  if configured to --omit-otpional-tags yes
2684
             *  Issue #314 - and if --show-body-only
2685
            \*/
2686
0
            if (!cfgBool( doc, TidyOmitOptionalTags ) &&
2687
0
                !showingBodyOnly(doc) )
2688
0
            {
2689
0
                TY_(Report)(doc, head, node, TAG_NOT_ALLOWED_IN);
2690
0
            }
2691
0
            TY_(UngetToken)( doc );
2692
0
            break;
2693
0
        }
2694
2695
0
        if (node->type == ProcInsTag && node->element &&
2696
0
            TY_(tmbstrcmp)(node->element, "xml-stylesheet") == 0)
2697
0
        {
2698
0
            TY_(Report)(doc, head, node, TAG_NOT_ALLOWED_IN);
2699
0
            TY_(InsertNodeBeforeElement)(TY_(FindHTML)(doc), node);
2700
0
            continue;
2701
0
        }
2702
2703
        /* deal with comments etc. */
2704
0
        if (InsertMisc(head, node))
2705
0
            continue;
2706
2707
0
        if (node->type == DocTypeTag)
2708
0
        {
2709
0
            InsertDocType(doc, head, node);
2710
0
            continue;
2711
0
        }
2712
2713
        /* discard unknown tags */
2714
0
        if (node->tag == NULL)
2715
0
        {
2716
0
            TY_(Report)(doc, head, node, DISCARDING_UNEXPECTED);
2717
0
            TY_(FreeNode)( doc, node);
2718
0
            continue;
2719
0
        }
2720
2721
        /*
2722
         if it doesn't belong in the head then
2723
         treat as implicit end of head and deal
2724
         with as part of the body
2725
        */
2726
0
        if (!(node->tag->model & CM_HEAD))
2727
0
        {
2728
            /* #545067 Implicit closing of head broken - warn only for XHTML input */
2729
0
            if ( lexer->isvoyager )
2730
0
                TY_(Report)(doc, head, node, TAG_NOT_ALLOWED_IN );
2731
0
            TY_(UngetToken)( doc );
2732
0
            break;
2733
0
        }
2734
2735
0
        if (TY_(nodeIsElement)(node))
2736
0
        {
2737
0
            if ( nodeIsTITLE(node) )
2738
0
            {
2739
0
                ++HasTitle;
2740
2741
0
                if (HasTitle > 1)
2742
0
                    TY_(Report)(doc, head, node,
2743
0
                                     head ?
2744
0
                                     TOO_MANY_ELEMENTS_IN : TOO_MANY_ELEMENTS);
2745
0
            }
2746
0
            else if ( nodeIsBASE(node) )
2747
0
            {
2748
0
                ++HasBase;
2749
2750
0
                if (HasBase > 1)
2751
0
                    TY_(Report)(doc, head, node,
2752
0
                                     head ?
2753
0
                                     TOO_MANY_ELEMENTS_IN : TOO_MANY_ELEMENTS);
2754
0
            }
2755
2756
0
            TY_(InsertNodeAtEnd)(head, node);
2757
2758
0
            {
2759
0
                TidyParserMemory memory = {0};
2760
0
                memory.identity = TY_(ParseHead);
2761
0
                memory.original_node = head;
2762
0
                memory.reentry_node = node;
2763
0
                memory.register_1 = HasTitle;
2764
0
                memory.register_2 = HasBase;
2765
0
                TY_(pushMemory)( doc, memory );
2766
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
2767
0
                return node;
2768
0
            }
2769
0
        }
2770
2771
        /* discard unexpected text nodes and end tags */
2772
0
        TY_(Report)(doc, head, node, DISCARDING_UNEXPECTED);
2773
0
        TY_(FreeNode)( doc, node);
2774
0
    }
2775
0
    DEBUG_LOG_EXIT;
2776
0
    return NULL;
2777
0
}
2778
2779
2780
/** MARK: TY_(ParseHTML)
2781
 *  Parses the `html` tag. At this point, other root-level stuff (doctype,
2782
 *  comments) are already set up, and here we handle all of the complexities
2783
 *  of things such as frameset documents, etc.
2784
 *
2785
 *  This is a non-recursing parser. It uses the document's parser memory stack
2786
 *  to send subsequent nodes back to the controller for dispatching to parsers.
2787
 *  This parser is also re-enterable, so that post-processing can occur after
2788
 *  such dispatching.
2789
 */
2790
Node* TY_(ParseHTML)( TidyDocImpl *doc, Node *html, GetTokenMode mode )
2791
0
{
2792
0
    Node *node = NULL;
2793
0
    Node *head = NULL;
2794
0
    Node *frameset = NULL;
2795
0
    Node *noframes = NULL;
2796
0
    DEBUG_LOG_COUNTERS;
2797
2798
0
    enum parserState {
2799
0
        STATE_INITIAL,                /* This is the initial state for every parser. */
2800
0
        STATE_COMPLETE,               /* Complete! */
2801
0
        STATE_PRE_BODY,               /* In this state, we'll consider frames vs. body. */
2802
0
        STATE_PARSE_BODY,             /* In this state, we can parse the body. */
2803
0
        STATE_PARSE_HEAD,             /* In this state, we will setup head for parsing. */
2804
0
        STATE_PARSE_HEAD_REENTER,     /* Resume here after parsing head. */
2805
0
        STATE_PARSE_NOFRAMES,         /* In this state, we can parse noframes content. */
2806
0
        STATE_PARSE_NOFRAMES_REENTER, /* In this state, we can restore more state. */
2807
0
        STATE_PARSE_FRAMESET,         /* In this state, we will parse frameset content. */
2808
0
        STATE_PARSE_FRAMESET_REENTER, /* We need to cleanup some things after parsing frameset. */
2809
0
    } state = STATE_INITIAL;
2810
2811
0
    TY_(SetOptionBool)( doc, TidyXmlTags, no );
2812
2813
0
    if ( html == NULL )
2814
0
    {
2815
0
        TidyParserMemory memory = TY_(popMemory)( doc );
2816
0
        node = memory.reentry_node;
2817
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
2818
0
        html = memory.original_node;
2819
0
        state = memory.reentry_state;
2820
0
        DEBUG_LOG_GET_OLD_MODE;
2821
0
        mode = memory.reentry_mode;
2822
0
        DEBUG_LOG_CHANGE_MODE;
2823
0
    }
2824
0
    else
2825
0
    {
2826
0
        DEBUG_LOG_ENTER_WITH_NODE(html);
2827
0
    }
2828
2829
    /*
2830
     This main loop pulls tokens from the lexer until we're out of tokens,
2831
     or until there's no more work to do.
2832
     */
2833
0
    while ( state != STATE_COMPLETE )
2834
0
    {
2835
0
        if ( state == STATE_INITIAL || state == STATE_PRE_BODY )
2836
0
        {
2837
0
            node = TY_(GetToken)( doc, IgnoreWhitespace );
2838
0
            DEBUG_LOG_GOT_TOKEN(node);
2839
0
        }
2840
2841
0
        switch ( state )
2842
0
        {
2843
            /**************************************************************
2844
             This case is all about finding a head tag and dealing with
2845
             cases were we don't, so that we can move on to parsing a head
2846
             tag.
2847
             **************************************************************/
2848
0
            case STATE_INITIAL:
2849
0
            {
2850
                /*
2851
                 The only way we can possibly be here is if the lexer
2852
                 had nothing to give us. Thus we'll create our own
2853
                 head, and set the signal to start parsing it.
2854
                 */
2855
0
                if (node == NULL)
2856
0
                {
2857
0
                    node = TY_(InferredTag)(doc, TidyTag_HEAD);
2858
0
                    state = STATE_PARSE_HEAD;
2859
0
                    continue;
2860
0
                }
2861
2862
                /* We found exactly what we expected: head. */
2863
0
                if ( nodeIsHEAD(node) )
2864
0
                {
2865
0
                    state = STATE_PARSE_HEAD;
2866
0
                    continue;
2867
0
                }
2868
2869
                /* We did not expect to find an html closing tag here! */
2870
0
                if (html && (node->tag == html->tag) && (node->type == EndTag))
2871
0
                {
2872
0
                    TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
2873
0
                    TY_(FreeNode)( doc, node);
2874
0
                    continue;
2875
0
                }
2876
2877
                /* Find and discard multiple <html> elements. */
2878
0
                if (html && (node->tag == html->tag) && (node->type == StartTag))
2879
0
                {
2880
0
                    TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
2881
0
                    TY_(FreeNode)(doc, node);
2882
0
                    continue;
2883
0
                }
2884
2885
                /* Deal with comments, etc. */
2886
0
                if (InsertMisc(html, node))
2887
0
                    continue;
2888
2889
                /*
2890
                 At this point, we didn't find a head tag, so put the
2891
                 token back and create our own head tag, so we can
2892
                 move on.
2893
                 */
2894
0
                TY_(UngetToken)( doc );
2895
0
                node = TY_(InferredTag)(doc, TidyTag_HEAD);
2896
0
                state = STATE_PARSE_HEAD;
2897
0
                continue;
2898
0
            } break;
2899
2900
2901
            /**************************************************************
2902
             This case determines whether we're dealing with body or
2903
             frameset + noframes, and sets things up accordingly.
2904
             **************************************************************/
2905
0
            case STATE_PRE_BODY:
2906
0
            {
2907
0
                if (node == NULL )
2908
0
                {
2909
0
                    if (frameset == NULL) /* Implied body. */
2910
0
                    {
2911
0
                        node = TY_(InferredTag)(doc, TidyTag_BODY);
2912
0
                        state = STATE_PARSE_BODY;
2913
0
                    } else {
2914
0
                        state = STATE_COMPLETE;
2915
0
                    }
2916
2917
0
                    continue;
2918
0
                }
2919
2920
                /* Robustly handle html tags. */
2921
0
                if (node->tag == html->tag)
2922
0
                {
2923
0
                    if (node->type != StartTag && frameset == NULL)
2924
0
                        TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
2925
2926
0
                    TY_(FreeNode)( doc, node);
2927
0
                    continue;
2928
0
                }
2929
2930
                /* Deal with comments, etc. */
2931
0
                if (InsertMisc(html, node))
2932
0
                    continue;
2933
2934
                /* If frameset document, coerce <body> to <noframes> */
2935
0
                if ( nodeIsBODY(node) )
2936
0
                {
2937
0
                    if (node->type != StartTag)
2938
0
                    {
2939
0
                        TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
2940
0
                        TY_(FreeNode)( doc, node);
2941
0
                        continue;
2942
0
                    }
2943
2944
0
                    if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
2945
0
                    {
2946
0
                        if (frameset != NULL)
2947
0
                        {
2948
0
                            TY_(UngetToken)( doc );
2949
2950
0
                            if (noframes == NULL)
2951
0
                            {
2952
0
                                noframes = TY_(InferredTag)(doc, TidyTag_NOFRAMES);
2953
0
                                TY_(InsertNodeAtEnd)(frameset, noframes);
2954
0
                                TY_(Report)(doc, html, noframes, INSERTING_TAG);
2955
0
                            }
2956
0
                            else
2957
0
                            {
2958
0
                                if (noframes->type == StartEndTag)
2959
0
                                    noframes->type = StartTag;
2960
0
                            }
2961
2962
0
                            state = STATE_PARSE_NOFRAMES;
2963
0
                            continue;
2964
0
                        }
2965
0
                    }
2966
2967
0
                    TY_(ConstrainVersion)(doc, ~VERS_FRAMESET);
2968
0
                    state = STATE_PARSE_BODY;
2969
0
                    continue;
2970
0
                }
2971
2972
                /* Flag an error if we see more than one frameset. */
2973
0
                if ( nodeIsFRAMESET(node) )
2974
0
                {
2975
0
                    if (node->type != StartTag)
2976
0
                    {
2977
0
                        TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
2978
0
                        TY_(FreeNode)( doc, node);
2979
0
                        continue;
2980
0
                    }
2981
2982
0
                    if (frameset != NULL)
2983
0
                        TY_(Report)(doc, html, node, DUPLICATE_FRAMESET);
2984
0
                    else
2985
0
                        frameset = node;
2986
2987
0
                    state = STATE_PARSE_FRAMESET;
2988
0
                    continue;
2989
0
                }
2990
2991
                /* If not a frameset document coerce <noframes> to <body>. */
2992
0
                if ( nodeIsNOFRAMES(node) )
2993
0
                {
2994
0
                    if (node->type != StartTag)
2995
0
                    {
2996
0
                        TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
2997
0
                        TY_(FreeNode)( doc, node);
2998
0
                        continue;
2999
0
                    }
3000
3001
0
                    if (frameset == NULL)
3002
0
                    {
3003
0
                        TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
3004
0
                        TY_(FreeNode)( doc, node);
3005
0
                        node = TY_(InferredTag)(doc, TidyTag_BODY);
3006
0
                        state = STATE_PARSE_BODY;
3007
0
                        continue;
3008
0
                    }
3009
3010
0
                    if (noframes == NULL)
3011
0
                    {
3012
0
                        noframes = node;
3013
0
                        TY_(InsertNodeAtEnd)(frameset, noframes);
3014
0
                        state = STATE_PARSE_NOFRAMES;
3015
0
                    }
3016
0
                    else
3017
0
                    {
3018
0
                        TY_(FreeNode)( doc, node);
3019
0
                    }
3020
3021
0
                    continue;
3022
0
                }
3023
3024
                /* Deal with some other element that we're not expecting. */
3025
0
                if (TY_(nodeIsElement)(node))
3026
0
                {
3027
0
                    if (node->tag && node->tag->model & CM_HEAD)
3028
0
                    {
3029
0
                        MoveToHead(doc, html, node);
3030
0
                        continue;
3031
0
                    }
3032
3033
                    /* Discard illegal frame element following a frameset. */
3034
0
                    if ( frameset != NULL && nodeIsFRAME(node) )
3035
0
                    {
3036
0
                        TY_(Report)(doc, html, node, DISCARDING_UNEXPECTED);
3037
0
                        TY_(FreeNode)(doc, node);
3038
0
                        continue;
3039
0
                    }
3040
0
                }
3041
3042
0
                TY_(UngetToken)( doc );
3043
3044
                /* Insert other content into noframes element. */
3045
0
                if (frameset)
3046
0
                {
3047
0
                    if (noframes == NULL)
3048
0
                    {
3049
0
                        noframes = TY_(InferredTag)(doc, TidyTag_NOFRAMES);
3050
0
                        TY_(InsertNodeAtEnd)(frameset, noframes);
3051
0
                    }
3052
0
                    else
3053
0
                    {
3054
0
                        TY_(Report)(doc, html, node, NOFRAMES_CONTENT);
3055
0
                        if (noframes->type == StartEndTag)
3056
0
                            noframes->type = StartTag;
3057
0
                    }
3058
3059
0
                    TY_(ConstrainVersion)(doc, VERS_FRAMESET);
3060
0
                    state = STATE_PARSE_NOFRAMES;
3061
0
                    continue;
3062
0
                }
3063
3064
0
                node = TY_(InferredTag)(doc, TidyTag_BODY);
3065
3066
                /* Issue #132 - disable inserting BODY tag warning
3067
                 BUT only if NOT --show-body-only yes */
3068
0
                if (!showingBodyOnly(doc))
3069
0
                    TY_(Report)(doc, html, node, INSERTING_TAG );
3070
3071
0
                TY_(ConstrainVersion)(doc, ~VERS_FRAMESET);
3072
0
                state = STATE_PARSE_BODY;
3073
0
                continue;
3074
0
            } break;
3075
3076
3077
            /**************************************************************
3078
             In this case, we're ready to parse the head, and move on to
3079
             look for the body or body alternative.
3080
             **************************************************************/
3081
0
            case STATE_PARSE_HEAD:
3082
0
            {
3083
0
                TidyParserMemory memory = {0};
3084
0
                memory.identity = TY_(ParseHTML);
3085
0
                memory.mode = mode;
3086
0
                memory.original_node = html;
3087
0
                memory.reentry_node = node;
3088
0
                memory.reentry_mode = mode;
3089
0
                memory.reentry_state = STATE_PARSE_HEAD_REENTER;
3090
0
                TY_(InsertNodeAtEnd)(html, node);
3091
0
                TY_(pushMemory)( doc, memory );
3092
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
3093
0
                return node;
3094
0
            } break;
3095
3096
0
            case STATE_PARSE_HEAD_REENTER:
3097
0
            {
3098
0
                head = node;
3099
0
                state = STATE_PRE_BODY;
3100
0
            } break;
3101
3102
3103
            /**************************************************************
3104
             In this case, we can finally parse a body.
3105
             **************************************************************/
3106
0
            case STATE_PARSE_BODY:
3107
0
            {
3108
0
                TidyParserMemory memory = {0};
3109
0
                memory.identity = NULL; /* we don't need to reenter */
3110
0
                memory.mode = mode;
3111
0
                memory.original_node = html;
3112
0
                memory.reentry_node = NULL;
3113
0
                memory.reentry_mode = mode;
3114
0
                memory.reentry_state = STATE_COMPLETE;
3115
0
                TY_(InsertNodeAtEnd)(html, node);
3116
0
                TY_(pushMemory)( doc, memory );
3117
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
3118
0
                return node;
3119
0
            } break;
3120
3121
3122
            /**************************************************************
3123
             In this case, we will parse noframes. If necessary, the
3124
             node is already inserted in the proper spot.
3125
             **************************************************************/
3126
0
            case STATE_PARSE_NOFRAMES:
3127
0
            {
3128
0
                TidyParserMemory memory = {0};
3129
0
                memory.identity = TY_(ParseHTML);
3130
0
                memory.mode = mode;
3131
0
                memory.original_node = html;
3132
0
                memory.reentry_node = frameset;
3133
0
                memory.reentry_mode = mode;
3134
0
                memory.reentry_state = STATE_PARSE_NOFRAMES_REENTER;
3135
0
                TY_(pushMemory)( doc, memory );
3136
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
3137
0
                return noframes;
3138
0
            } break;
3139
3140
0
            case STATE_PARSE_NOFRAMES_REENTER:
3141
0
            {
3142
0
                frameset = node;
3143
0
                state = STATE_PRE_BODY;
3144
0
            } break;
3145
3146
3147
            /**************************************************************
3148
             In this case, we parse the frameset, and look for noframes
3149
             content to merge later if necessary.
3150
             **************************************************************/
3151
0
            case STATE_PARSE_FRAMESET:
3152
0
            {
3153
0
                TidyParserMemory memory = {0};
3154
0
                memory.identity = TY_(ParseHTML);
3155
0
                memory.mode = mode;
3156
0
                memory.original_node = html;
3157
0
                memory.reentry_node = frameset;
3158
0
                memory.reentry_mode = mode;
3159
0
                memory.reentry_state = STATE_PARSE_FRAMESET_REENTER;
3160
0
                TY_(InsertNodeAtEnd)(html, node);
3161
0
                TY_(pushMemory)( doc, memory );
3162
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
3163
0
                return node;
3164
0
            } break;
3165
3166
0
            case (STATE_PARSE_FRAMESET_REENTER):
3167
0
            {
3168
0
                frameset = node;
3169
                /*
3170
                 See if it includes a noframes element so that
3171
                 we can merge subsequent noframes elements.
3172
                 */
3173
0
                for (node = frameset->content; node; node = node->next)
3174
0
                {
3175
0
                    if ( nodeIsNOFRAMES(node) )
3176
0
                        noframes = node;
3177
0
                }
3178
0
                state = STATE_PRE_BODY;
3179
0
            } break;
3180
3181
3182
            /**************************************************************
3183
             We really shouldn't get here, but if we do, finish nicely.
3184
             **************************************************************/
3185
0
            default:
3186
0
            {
3187
0
                state = STATE_COMPLETE;
3188
0
            }
3189
0
        } /* switch */
3190
0
    } /* while */
3191
3192
0
    DEBUG_LOG_EXIT;
3193
0
    return NULL;
3194
0
}
3195
3196
3197
/** MARK: TY_(ParseInline)
3198
 *  Parse inline element nodes.
3199
 *
3200
 *  This is a non-recursing parser. It uses the document's parser memory stack
3201
 *  to send subsequent nodes back to the controller for dispatching to parsers.
3202
 *  This parser is also re-enterable, so that post-processing can occur after
3203
 *  such dispatching.
3204
*/
3205
Node* TY_(ParseInline)( TidyDocImpl *doc, Node *element, GetTokenMode mode )
3206
0
{
3207
0
    Lexer* lexer = doc->lexer;
3208
0
    Node *node = NULL;
3209
0
    Node *parent = NULL;
3210
0
    DEBUG_LOG_COUNTERS;
3211
    
3212
0
    if ( element == NULL )
3213
0
    {
3214
0
        TidyParserMemory memory = TY_(popMemory)( doc );
3215
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
3216
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
3217
0
        element = memory.original_node;
3218
0
        DEBUG_LOG_GET_OLD_MODE;
3219
0
        mode = memory.reentry_mode;
3220
0
        DEBUG_LOG_CHANGE_MODE;
3221
0
    }
3222
0
    else
3223
0
    {
3224
0
        DEBUG_LOG_ENTER_WITH_NODE(element);
3225
3226
0
        if (element->tag->model & CM_EMPTY)
3227
0
        {
3228
0
            DEBUG_LOG_EXIT;
3229
0
            return NULL;
3230
0
        }
3231
3232
        /*
3233
         ParseInline is used for some block level elements like H1 to H6
3234
         For such elements we need to insert inline emphasis tags currently
3235
         on the inline stack. For Inline elements, we normally push them
3236
         onto the inline stack provided they aren't implicit or OBJECT/APPLET.
3237
         This test is carried out in PushInline and PopInline, see istack.c
3238
3239
         InlineDup(...) is not called for elements with a CM_MIXED (inline and
3240
         block) content model, e.g. <del> or <ins>, otherwise constructs like
3241
3242
           <p>111<a name='foo'>222<del>333</del>444</a>555</p>
3243
           <p>111<span>222<del>333</del>444</span>555</p>
3244
           <p>111<em>222<del>333</del>444</em>555</p>
3245
3246
         will get corrupted.
3247
        */
3248
0
        if ((TY_(nodeHasCM)(element, CM_BLOCK) || nodeIsDT(element)) &&
3249
0
            !TY_(nodeHasCM)(element, CM_MIXED))
3250
0
            TY_(InlineDup)(doc, NULL);
3251
0
        else if (TY_(nodeHasCM)(element, CM_INLINE))
3252
0
            TY_(PushInline)(doc, element);
3253
3254
0
        if ( nodeIsNOBR(element) )
3255
0
            doc->badLayout |= USING_NOBR;
3256
0
        else if ( nodeIsFONT(element) )
3257
0
            doc->badLayout |= USING_FONT;
3258
3259
        /* Inline elements may or may not be within a preformatted element */
3260
0
        if (mode != Preformatted)
3261
0
        {
3262
0
            DEBUG_LOG_GET_OLD_MODE;
3263
0
            mode = MixedContent;
3264
0
            DEBUG_LOG_CHANGE_MODE;
3265
0
        }
3266
0
    }
3267
    
3268
0
    while ((node = TY_(GetToken)(doc, mode)) != NULL)
3269
0
    {
3270
        /* end tag for current element */
3271
0
        if (node->tag == element->tag && node->type == EndTag)
3272
0
        {
3273
0
            if (element->tag->model & CM_INLINE)
3274
0
                TY_(PopInline)( doc, node );
3275
3276
0
            TY_(FreeNode)( doc, node );
3277
3278
0
            if (!(mode & Preformatted))
3279
0
                TrimSpaces(doc, element);
3280
3281
            /*
3282
             if a font element wraps an anchor and nothing else
3283
             then move the font element inside the anchor since
3284
             otherwise it won't alter the anchor text color
3285
            */
3286
0
            if ( nodeIsFONT(element) &&
3287
0
                 element->content && element->content == element->last )
3288
0
            {
3289
0
                Node *child = element->content;
3290
3291
0
                if ( nodeIsA(child) )
3292
0
                {
3293
0
                    child->parent = element->parent;
3294
0
                    child->next = element->next;
3295
0
                    child->prev = element->prev;
3296
3297
0
                    element->next = NULL;
3298
0
                    element->prev = NULL;
3299
0
                    element->parent = child;
3300
3301
0
                    element->content = child->content;
3302
0
                    element->last = child->last;
3303
0
                    child->content = element;
3304
3305
0
                    TY_(FixNodeLinks)(child);
3306
0
                    TY_(FixNodeLinks)(element);
3307
0
                }
3308
0
            }
3309
3310
0
            element->closed = yes;
3311
0
            TrimSpaces( doc, element );
3312
3313
0
            DEBUG_LOG_EXIT;
3314
0
            return NULL;
3315
0
        }
3316
3317
        /* <u>...<u>  map 2nd <u> to </u> if 1st is explicit */
3318
        /* (see additional conditions below) */
3319
        /* otherwise emphasis nesting is probably unintentional */
3320
        /* big, small, sub, sup have cumulative effect to leave them alone */
3321
0
        if ( node->type == StartTag
3322
0
             && node->tag == element->tag
3323
0
             && TY_(IsPushed)( doc, node )
3324
0
             && !node->implicit
3325
0
             && !element->implicit
3326
0
             && node->tag && (node->tag->model & CM_INLINE)
3327
0
             && !nodeIsA(node)
3328
0
             && !nodeIsFONT(node)
3329
0
             && !nodeIsBIG(node)
3330
0
             && !nodeIsSMALL(node)
3331
0
             && !nodeIsSUB(node)
3332
0
             && !nodeIsSUP(node)
3333
0
             && !nodeIsQ(node)
3334
0
             && !nodeIsSPAN(node)
3335
0
             && cfgBool(doc, TidyCoerceEndTags)
3336
0
           )
3337
0
        {
3338
            /* proceeds only if "node" does not have any attribute and
3339
               follows a text node not finishing with a space */
3340
0
            if (element->content != NULL && node->attributes == NULL
3341
0
                && TY_(nodeIsText)(element->last)
3342
0
                && !TY_(TextNodeEndWithSpace)(doc->lexer, element->last) )
3343
0
            {
3344
0
                TY_(Report)(doc, element, node, COERCE_TO_ENDTAG);
3345
0
                node->type = EndTag;
3346
0
                TY_(UngetToken)(doc);
3347
0
                continue;
3348
0
            }
3349
3350
0
            if (node->attributes == NULL || element->attributes == NULL)
3351
0
                TY_(Report)(doc, element, node, NESTED_EMPHASIS);
3352
0
        }
3353
0
        else if ( TY_(IsPushed)(doc, node) && node->type == StartTag &&
3354
0
                  nodeIsQ(node) )
3355
0
        {
3356
            /*\
3357
             * Issue #215 - such nested quotes are NOT a problem if HTML5, so
3358
             * only issue this warning if NOT HTML5 mode.
3359
            \*/
3360
0
            if (TY_(HTMLVersion)(doc) != HT50)
3361
0
            {
3362
0
                TY_(Report)(doc, element, node, NESTED_QUOTATION);
3363
0
            }
3364
0
        }
3365
3366
0
        if ( TY_(nodeIsText)(node) )
3367
0
        {
3368
            /* only called for 1st child */
3369
0
            if ( element->content == NULL && !(mode & Preformatted) )
3370
0
                TrimSpaces( doc, element );
3371
3372
0
            if ( node->start >= node->end )
3373
0
            {
3374
0
                TY_(FreeNode)( doc, node );
3375
0
                continue;
3376
0
            }
3377
3378
0
            TY_(InsertNodeAtEnd)(element, node);
3379
0
            continue;
3380
0
        }
3381
3382
        /* mixed content model so allow text */
3383
0
        if (InsertMisc(element, node))
3384
0
            continue;
3385
3386
        /* deal with HTML tags */
3387
0
        if ( nodeIsHTML(node) )
3388
0
        {
3389
0
            if ( TY_(nodeIsElement)(node) )
3390
0
            {
3391
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED );
3392
0
                TY_(FreeNode)( doc, node );
3393
0
                continue;
3394
0
            }
3395
3396
            /* otherwise infer end of inline element */
3397
0
            TY_(UngetToken)( doc );
3398
3399
0
            if (!(mode & Preformatted))
3400
0
                TrimSpaces(doc, element);
3401
3402
0
            DEBUG_LOG_EXIT;
3403
0
            return NULL;
3404
0
        }
3405
3406
        /* within <dt> or <pre> map <p> to <br> */
3407
0
        if ( nodeIsP(node) &&
3408
0
             node->type == StartTag &&
3409
0
             ( (mode & Preformatted) ||
3410
0
               nodeIsDT(element) ||
3411
0
               DescendantOf(element, TidyTag_DT )
3412
0
             )
3413
0
           )
3414
0
        {
3415
0
            node->tag = TY_(LookupTagDef)( TidyTag_BR );
3416
0
            TidyDocFree(doc, node->element);
3417
0
            node->element = TY_(tmbstrdup)(doc->allocator, "br");
3418
0
            TrimSpaces(doc, element);
3419
0
            TY_(InsertNodeAtEnd)(element, node);
3420
0
            continue;
3421
0
        }
3422
3423
        /* <p> allowed within <address> in HTML 4.01 Transitional */
3424
0
        if ( nodeIsP(node) &&
3425
0
             node->type == StartTag &&
3426
0
             nodeIsADDRESS(element) )
3427
0
        {
3428
0
            TY_(ConstrainVersion)( doc, ~VERS_HTML40_STRICT );
3429
0
            TY_(InsertNodeAtEnd)(element, node);
3430
0
            (*node->tag->parser)( doc, node, mode );
3431
0
            continue;
3432
0
        }
3433
3434
        /* ignore unknown and PARAM tags */
3435
0
        if ( node->tag == NULL || nodeIsPARAM(node) )
3436
0
        {
3437
0
            TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3438
0
            TY_(FreeNode)( doc, node );
3439
0
            continue;
3440
0
        }
3441
3442
0
        if ( nodeIsBR(node) && node->type == EndTag )
3443
0
            node->type = StartTag;
3444
3445
0
        if ( node->type == EndTag )
3446
0
        {
3447
           /* coerce </br> to <br> */
3448
0
           if ( nodeIsBR(node) )
3449
0
                node->type = StartTag;
3450
0
           else if ( nodeIsP(node) )
3451
0
           {
3452
               /* coerce unmatched </p> to <br><br> */
3453
0
                if ( !DescendantOf(element, TidyTag_P) )
3454
0
                {
3455
0
                    TY_(CoerceNode)(doc, node, TidyTag_BR, no, no);
3456
0
                    TrimSpaces( doc, element );
3457
0
                    TY_(InsertNodeAtEnd)( element, node );
3458
0
                    node = TY_(InferredTag)(doc, TidyTag_BR);
3459
0
                    TY_(InsertNodeAtEnd)( element, node ); /* todo: check this */
3460
0
                    continue;
3461
0
                }
3462
0
           }
3463
0
           else if ( TY_(nodeHasCM)(node, CM_INLINE)
3464
0
                     && !nodeIsA(node)
3465
0
                     && !TY_(nodeHasCM)(node, CM_OBJECT)
3466
0
                     && TY_(nodeHasCM)(element, CM_INLINE) )
3467
0
            {
3468
                /* allow any inline end tag to end current element */
3469
3470
                /* http://tidy.sf.net/issue/1426419 */
3471
                /* but, like the browser, retain an earlier inline element.
3472
                   This is implemented by setting the lexer into a mode
3473
                   where it gets tokens from the inline stack rather than
3474
                   from the input stream. Check if the scenerio fits. */
3475
0
                if ( !nodeIsA(element)
3476
0
                     && (node->tag != element->tag)
3477
0
                     && TY_(IsPushed)( doc, node )
3478
0
                     && TY_(IsPushed)( doc, element ) )
3479
0
                {
3480
                    /* we have something like
3481
                       <b>bold <i>bold and italic</b> italics</i> */
3482
0
                    if ( TY_(SwitchInline)( doc, element, node ) )
3483
0
                    {
3484
0
                        TY_(Report)(doc, element, node, NON_MATCHING_ENDTAG);
3485
0
                        TY_(UngetToken)( doc ); /* put this back */
3486
0
                        TY_(InlineDup1)( doc, NULL, element ); /* dupe the <i>, after </b> */
3487
0
                        if (!(mode & Preformatted))
3488
0
                            TrimSpaces( doc, element );
3489
3490
0
                        DEBUG_LOG_EXIT;
3491
0
                        return NULL; /* close <i>, but will re-open it, after </b> */
3492
0
                    }
3493
0
                }
3494
0
                TY_(PopInline)( doc, element );
3495
3496
0
                if ( !nodeIsA(element) )
3497
0
                {
3498
0
                    if ( nodeIsA(node) && node->tag != element->tag )
3499
0
                    {
3500
0
                       TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE );
3501
0
                       TY_(UngetToken)( doc );
3502
0
                    }
3503
0
                    else
3504
0
                    {
3505
0
                        TY_(Report)(doc, element, node, NON_MATCHING_ENDTAG);
3506
0
                        TY_(FreeNode)( doc, node);
3507
0
                    }
3508
3509
0
                    if (!(mode & Preformatted))
3510
0
                        TrimSpaces(doc, element);
3511
3512
0
                    DEBUG_LOG_EXIT;
3513
0
                    return NULL;
3514
0
                }
3515
3516
                /* if parent is <a> then discard unexpected inline end tag */
3517
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3518
0
                TY_(FreeNode)( doc, node);
3519
0
                continue;
3520
0
            }  /* special case </tr> etc. for stuff moved in front of table */
3521
0
            else if ( lexer->exiled
3522
0
                     && (TY_(nodeHasCM)(node, CM_TABLE) || nodeIsTABLE(node)) )
3523
0
            {
3524
0
                TY_(UngetToken)( doc );
3525
0
                TrimSpaces(doc, element);
3526
3527
0
                DEBUG_LOG_EXIT;
3528
0
                return NULL;
3529
0
            }
3530
0
        }
3531
3532
        /* allow any header tag to end current header */
3533
0
        if ( TY_(nodeHasCM)(node, CM_HEADING) && TY_(nodeHasCM)(element, CM_HEADING) )
3534
0
        {
3535
3536
0
            if ( node->tag == element->tag )
3537
0
            {
3538
0
                TY_(Report)(doc, element, node, NON_MATCHING_ENDTAG );
3539
0
                TY_(FreeNode)( doc, node);
3540
0
            }
3541
0
            else
3542
0
            {
3543
0
                TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE );
3544
0
                TY_(UngetToken)( doc );
3545
0
            }
3546
3547
0
            if (!(mode & Preformatted))
3548
0
                TrimSpaces(doc, element);
3549
3550
0
            DEBUG_LOG_EXIT;
3551
0
            return NULL;
3552
0
        }
3553
3554
        /*
3555
           an <A> tag to ends any open <A> element
3556
           but <A href=...> is mapped to </A><A href=...>
3557
        */
3558
        /* #427827 - fix by Randy Waki and Bjoern Hoehrmann 23 Aug 00 */
3559
        /* if (node->tag == doc->tags.tag_a && !node->implicit && TY_(IsPushed)(doc, node)) */
3560
0
        if ( nodeIsA(node) && !node->implicit &&
3561
0
             (nodeIsA(element) || DescendantOf(element, TidyTag_A)) )
3562
0
        {
3563
            /* coerce <a> to </a> unless it has some attributes */
3564
            /* #427827 - fix by Randy Waki and Bjoern Hoehrmann 23 Aug 00 */
3565
            /* other fixes by Dave Raggett */
3566
            /* if (node->attributes == NULL) */
3567
0
            if (node->type != EndTag && node->attributes == NULL
3568
0
                && cfgBool(doc, TidyCoerceEndTags) )
3569
0
            {
3570
0
                node->type = EndTag;
3571
0
                TY_(Report)(doc, element, node, COERCE_TO_ENDTAG);
3572
                /* TY_(PopInline)( doc, node ); */
3573
0
                TY_(UngetToken)( doc );
3574
0
                continue;
3575
0
            }
3576
3577
0
            TY_(UngetToken)( doc );
3578
0
            TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE);
3579
            /* TY_(PopInline)( doc, element ); */
3580
3581
0
            if (!(mode & Preformatted))
3582
0
                TrimSpaces(doc, element);
3583
3584
0
            DEBUG_LOG_EXIT;
3585
0
            return NULL;
3586
0
        }
3587
3588
0
        if (element->tag->model & CM_HEADING)
3589
0
        {
3590
0
            if ( nodeIsCENTER(node) || nodeIsDIV(node) )
3591
0
            {
3592
0
                if (!TY_(nodeIsElement)(node))
3593
0
                {
3594
0
                    TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3595
0
                    TY_(FreeNode)( doc, node);
3596
0
                    continue;
3597
0
                }
3598
3599
0
                TY_(Report)(doc, element, node, TAG_NOT_ALLOWED_IN);
3600
3601
                /* insert center as parent if heading is empty */
3602
0
                if (element->content == NULL)
3603
0
                {
3604
0
                    InsertNodeAsParent(element, node);
3605
0
                    continue;
3606
0
                }
3607
3608
                /* split heading and make center parent of 2nd part */
3609
0
                TY_(InsertNodeAfterElement)(element, node);
3610
3611
0
                if (!(mode & Preformatted))
3612
0
                    TrimSpaces(doc, element);
3613
3614
0
                element = TY_(CloneNode)( doc, element );
3615
0
                TY_(InsertNodeAtEnd)(node, element);
3616
0
                continue;
3617
0
            }
3618
3619
0
            if ( nodeIsHR(node) )
3620
0
            {
3621
0
                if ( !TY_(nodeIsElement)(node) )
3622
0
                {
3623
0
                    TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3624
0
                    TY_(FreeNode)( doc, node);
3625
0
                    continue;
3626
0
                }
3627
3628
0
                TY_(Report)(doc, element, node, TAG_NOT_ALLOWED_IN);
3629
3630
                /* insert hr before heading if heading is empty */
3631
0
                if (element->content == NULL)
3632
0
                {
3633
0
                    TY_(InsertNodeBeforeElement)(element, node);
3634
0
                    continue;
3635
0
                }
3636
3637
                /* split heading and insert hr before 2nd part */
3638
0
                TY_(InsertNodeAfterElement)(element, node);
3639
3640
0
                if (!(mode & Preformatted))
3641
0
                    TrimSpaces(doc, element);
3642
3643
0
                element = TY_(CloneNode)( doc, element );
3644
0
                TY_(InsertNodeAfterElement)(node, element);
3645
0
                continue;
3646
0
            }
3647
0
        }
3648
3649
0
        if ( nodeIsDT(element) )
3650
0
        {
3651
0
            if ( nodeIsHR(node) )
3652
0
            {
3653
0
                Node *dd;
3654
0
                if ( !TY_(nodeIsElement)(node) )
3655
0
                {
3656
0
                    TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3657
0
                    TY_(FreeNode)( doc, node);
3658
0
                    continue;
3659
0
                }
3660
3661
0
                TY_(Report)(doc, element, node, TAG_NOT_ALLOWED_IN);
3662
0
                dd = TY_(InferredTag)(doc, TidyTag_DD);
3663
3664
                /* insert hr within dd before dt if dt is empty */
3665
0
                if (element->content == NULL)
3666
0
                {
3667
0
                    TY_(InsertNodeBeforeElement)(element, dd);
3668
0
                    TY_(InsertNodeAtEnd)(dd, node);
3669
0
                    continue;
3670
0
                }
3671
3672
                /* split dt and insert hr within dd before 2nd part */
3673
0
                TY_(InsertNodeAfterElement)(element, dd);
3674
0
                TY_(InsertNodeAtEnd)(dd, node);
3675
3676
0
                if (!(mode & Preformatted))
3677
0
                    TrimSpaces(doc, element);
3678
3679
0
                element = TY_(CloneNode)( doc, element );
3680
0
                TY_(InsertNodeAfterElement)(dd, element);
3681
0
                continue;
3682
0
            }
3683
0
        }
3684
3685
3686
        /*
3687
          if this is the end tag for an ancestor element
3688
          then infer end tag for this element
3689
        */
3690
0
        if (node->type == EndTag)
3691
0
        {
3692
0
            for (parent = element->parent;
3693
0
                    parent != NULL; parent = parent->parent)
3694
0
            {
3695
0
                if (node->tag == parent->tag)
3696
0
                {
3697
0
                    if (!(element->tag->model & CM_OPT) && !element->implicit)
3698
0
                        TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE);
3699
3700
0
                    if( TY_(IsPushedLast)( doc, element, node ) )
3701
0
                        TY_(PopInline)( doc, element );
3702
0
                    TY_(UngetToken)( doc );
3703
3704
0
                    if (!(mode & Preformatted))
3705
0
                        TrimSpaces(doc, element);
3706
3707
0
                    DEBUG_LOG_EXIT;
3708
0
                    return NULL;
3709
0
                }
3710
0
            }
3711
0
        }
3712
3713
        /*\
3714
         *  block level tags end this element
3715
         *  Issue #333 - There seems an exception if the element is a 'span',
3716
         *  and the node just collected is a 'meta'. The 'meta' can not have
3717
         *  CM_INLINE added, nor can the 'span' have CM_MIXED added without
3718
         *  big consequences.
3719
         *  There may be other exceptions to be added...
3720
        \*/
3721
0
        if (!(node->tag->model & CM_INLINE) &&
3722
0
            !(element->tag->model & CM_MIXED) &&
3723
0
            !(nodeIsSPAN(element) && nodeIsMETA(node)) )
3724
0
        {
3725
0
            if ( !TY_(nodeIsElement)(node) )
3726
0
            {
3727
0
                TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3728
0
                TY_(FreeNode)( doc, node);
3729
0
                continue;
3730
0
            }
3731
            /* HTML5 */
3732
0
            if (nodeIsDATALIST(element)) {
3733
0
                TY_(ConstrainVersion)( doc, ~VERS_HTML5 );
3734
0
            } else
3735
0
            if (!(element->tag->model & CM_OPT))
3736
0
                TY_(Report)(doc, element, node, MISSING_ENDTAG_BEFORE);
3737
3738
0
            if (node->tag->model & CM_HEAD && !(node->tag->model & CM_BLOCK))
3739
0
            {
3740
0
                MoveToHead(doc, element, node);
3741
0
                continue;
3742
0
            }
3743
3744
            /*
3745
               prevent anchors from propagating into block tags
3746
               except for headings h1 to h6
3747
            */
3748
0
            if ( nodeIsA(element) )
3749
0
            {
3750
0
                if (node->tag && !(node->tag->model & CM_HEADING))
3751
0
                    TY_(PopInline)( doc, element );
3752
0
                else if (!(element->content))
3753
0
                {
3754
0
                    TY_(DiscardElement)( doc, element );
3755
0
                    TY_(UngetToken)( doc );
3756
3757
0
                    DEBUG_LOG_EXIT;
3758
0
                    return NULL;
3759
0
                }
3760
0
            }
3761
3762
0
            TY_(UngetToken)( doc );
3763
3764
0
            if (!(mode & Preformatted))
3765
0
                TrimSpaces(doc, element);
3766
3767
0
            DEBUG_LOG_EXIT;
3768
0
            return NULL;
3769
0
        }
3770
3771
        /* parse inline element */
3772
0
        if (TY_(nodeIsElement)(node))
3773
0
        {
3774
0
            if (node->implicit)
3775
0
                TY_(Report)(doc, element, node, INSERTING_TAG);
3776
3777
            /* trim white space before <br> */
3778
0
            if ( nodeIsBR(node) )
3779
0
                TrimSpaces(doc, element);
3780
3781
0
            TY_(InsertNodeAtEnd)(element, node);
3782
            
3783
0
            {
3784
0
                TidyParserMemory memory = {0};
3785
0
                memory.identity = TY_(ParseInline);
3786
0
                memory.original_node = element;
3787
0
                memory.reentry_node = node;
3788
0
                memory.mode = mode;
3789
0
                memory.reentry_mode = mode;
3790
0
                TY_(pushMemory)( doc, memory );
3791
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
3792
0
                return node;
3793
0
            }
3794
0
        }
3795
3796
        /* discard unexpected tags */
3797
0
        TY_(Report)(doc, element, node, DISCARDING_UNEXPECTED);
3798
0
        TY_(FreeNode)( doc, node );
3799
0
        continue;
3800
0
    }
3801
3802
0
    if (!(element->tag->model & CM_OPT))
3803
0
        TY_(Report)(doc, element, node, MISSING_ENDTAG_FOR);
3804
3805
0
    DEBUG_LOG_EXIT;
3806
0
    return NULL;
3807
0
}
3808
3809
3810
/** MARK: TY_(ParseList)
3811
 *  Parses list tags.
3812
 *
3813
 *  This is a non-recursing parser. It uses the document's parser memory stack
3814
 *  to send subsequent nodes back to the controller for dispatching to parsers.
3815
 *  This parser is also re-enterable, so that post-processing can occur after
3816
 *  such dispatching.
3817
*/
3818
Node* TY_(ParseList)( TidyDocImpl* doc, Node *list, GetTokenMode ARG_UNUSED(mode) )
3819
0
{
3820
0
    Lexer* lexer = doc->lexer;
3821
0
    Node *node = NULL;
3822
0
    Node *parent = NULL;
3823
0
    Node *lastli = NULL;;
3824
0
    Bool wasblock = no;
3825
0
    Bool nodeisOL = nodeIsOL(list);
3826
0
    DEBUG_LOG_COUNTERS;
3827
3828
0
    if ( list == NULL )
3829
0
    {
3830
0
        TidyParserMemory memory = TY_(popMemory)( doc );
3831
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
3832
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
3833
0
        list = memory.original_node;
3834
0
        DEBUG_LOG_GET_OLD_MODE;
3835
0
        mode = memory.mode;
3836
0
        DEBUG_LOG_CHANGE_MODE;
3837
0
    }
3838
0
    else
3839
0
    {
3840
0
        DEBUG_LOG_ENTER_WITH_NODE(list);
3841
3842
0
        if (list->tag->model & CM_EMPTY)
3843
0
        {
3844
0
            DEBUG_LOG_EXIT;
3845
0
            return NULL;
3846
0
        }
3847
0
    }
3848
    
3849
0
    lexer->insert = NULL;  /* defer implicit inline start tags */
3850
3851
0
    while ((node = TY_(GetToken)( doc, IgnoreWhitespace)) != NULL)
3852
0
    {
3853
0
        Bool foundLI = no;
3854
0
        if (node->tag == list->tag && node->type == EndTag)
3855
0
        {
3856
0
            TY_(FreeNode)( doc, node);
3857
0
            list->closed = yes;
3858
0
            DEBUG_LOG_EXIT;
3859
0
            return NULL;
3860
0
        }
3861
3862
        /* deal with comments etc. */
3863
0
        if (InsertMisc(list, node))
3864
0
            continue;
3865
3866
0
        if (node->type != TextNode && node->tag == NULL)
3867
0
        {
3868
0
            TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
3869
0
            TY_(FreeNode)( doc, node);
3870
0
            continue;
3871
0
        }
3872
0
        if (lexer && (node->type == TextNode))
3873
0
        {
3874
0
            uint ch, ix = node->start;
3875
            /* Issue #572 - Skip whitespace. */
3876
0
            while (ix < node->end && (ch = (lexer->lexbuf[ix] & 0xff))
3877
0
                && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'))
3878
0
                ++ix;
3879
0
            if (ix >= node->end)
3880
0
            {
3881
                /* Issue #572 - Discard if ALL whitespace. */
3882
0
                TY_(FreeNode)(doc, node);
3883
0
                continue;
3884
0
            }
3885
0
        }
3886
3887
3888
        /*
3889
          if this is the end tag for an ancestor element
3890
          then infer end tag for this element
3891
        */
3892
0
        if (node->type == EndTag)
3893
0
        {
3894
0
            if ( nodeIsFORM(node) )
3895
0
            {
3896
0
                BadForm( doc );
3897
0
                TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
3898
0
                TY_(FreeNode)( doc, node );
3899
0
                continue;
3900
0
            }
3901
3902
0
            if (TY_(nodeHasCM)(node,CM_INLINE))
3903
0
            {
3904
0
                TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
3905
0
                TY_(PopInline)( doc, node );
3906
0
                TY_(FreeNode)( doc, node);
3907
0
                continue;
3908
0
            }
3909
3910
0
            for ( parent = list->parent;
3911
0
                  parent != NULL; parent = parent->parent )
3912
0
            {
3913
               /* Do not match across BODY to avoid infinite loop
3914
                  between ParseBody and this parser,
3915
                  See http://tidy.sf.net/bug/1053626. */
3916
0
                if (nodeIsBODY(parent))
3917
0
                    break;
3918
0
                if (node->tag == parent->tag)
3919
0
                {
3920
0
                    TY_(Report)(doc, list, node, MISSING_ENDTAG_BEFORE);
3921
0
                    TY_(UngetToken)( doc );
3922
0
                    DEBUG_LOG_EXIT;
3923
0
                    return NULL;
3924
0
                }
3925
0
            }
3926
3927
0
            TY_(Report)(doc, list, node, DISCARDING_UNEXPECTED);
3928
0
            TY_(FreeNode)( doc, node);
3929
0
            continue;
3930
0
        }
3931
3932
0
        if ( !nodeIsLI(node) && nodeisOL )
3933
0
        {
3934
            /* Issue #572 - A <ol><li> can have nested <ol> elements */
3935
0
            foundLI = FindLastLI(list, &lastli); /* find last <li> */
3936
0
        }
3937
3938
0
        if ( nodeIsLI(node) || (TY_(IsHTML5Mode)(doc) && !foundLI) )
3939
0
        {
3940
            /* node is <LI> OR
3941
               Issue #396 - A <ul> can have Zero or more <li> elements
3942
             */
3943
0
            TY_(InsertNodeAtEnd)(list,node);
3944
0
        }
3945
0
        else
3946
0
        {
3947
0
            TY_(UngetToken)( doc );
3948
3949
0
            if (TY_(nodeHasCM)(node,CM_BLOCK) && lexer->excludeBlocks)
3950
0
            {
3951
0
                TY_(Report)(doc, list, node, MISSING_ENDTAG_BEFORE);
3952
0
                DEBUG_LOG_EXIT;
3953
0
                return NULL;
3954
0
            }
3955
            /* http://tidy.sf.net/issue/1316307 */
3956
            /* In exiled mode, return so table processing can continue. */
3957
0
            else if ( lexer->exiled
3958
0
                      && (TY_(nodeHasCM)(node, CM_TABLE|CM_ROWGRP|CM_ROW)
3959
0
                          || nodeIsTABLE(node)) )
3960
0
            {
3961
0
                DEBUG_LOG_EXIT;
3962
0
                return NULL;
3963
0
            }
3964
            /* http://tidy.sf.net/issue/836462
3965
               If "list" is an unordered list, insert the next tag within
3966
               the last <li> to preserve the numbering to match the visual
3967
               rendering of most browsers. */
3968
0
            if ( nodeIsOL(list) && FindLastLI(list, &lastli) )
3969
0
            {
3970
                /* Create a node for error reporting */
3971
0
                node = TY_(InferredTag)(doc, TidyTag_LI);
3972
0
                TY_(Report)(doc, list, node, MISSING_STARTTAG );
3973
0
                TY_(FreeNode)( doc, node);
3974
0
                node = lastli;
3975
0
            }
3976
0
            else
3977
0
            {
3978
                /* Add an inferred <li> */
3979
0
                wasblock = TY_(nodeHasCM)(node,CM_BLOCK);
3980
0
                node = TY_(InferredTag)(doc, TidyTag_LI);
3981
                /* Add "display: inline" to avoid a blank line after <li> with
3982
                   Internet Explorer. See http://tidy.sf.net/issue/836462 */
3983
0
                TY_(AddStyleProperty)( doc, node,
3984
0
                                       wasblock
3985
0
                                       ? "list-style: none; display: inline"
3986
0
                                       : "list-style: none"
3987
0
                                       );
3988
0
                TY_(Report)(doc, list, node, MISSING_STARTTAG );
3989
0
                TY_(InsertNodeAtEnd)(list,node);
3990
0
            }
3991
0
        }
3992
3993
0
        {
3994
0
            TidyParserMemory memory = {0};
3995
0
            memory.identity = TY_(ParseList);
3996
0
            memory.original_node = list;
3997
0
            memory.reentry_node = node;
3998
0
            memory.mode = IgnoreWhitespace;
3999
0
            TY_(pushMemory)( doc, memory );
4000
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
4001
0
            return node;
4002
0
        }
4003
0
    }
4004
4005
0
    TY_(Report)(doc, list, node, MISSING_ENDTAG_FOR);
4006
0
    DEBUG_LOG_EXIT;
4007
0
    return NULL;
4008
0
}
4009
4010
4011
/** MARK: TY_(ParseNamespace)
4012
 *  Act as a generic XML (sub)tree parser: collect each node and add it
4013
 *  to the DOM, without any further validation. It's useful for tags that
4014
 *  have XML-like content, such as `svg` and `math`.
4015
 *
4016
 *  @note Perhaps this is poorly named, as we're not parsing the namespace
4017
 *    of a particular tag, but a tag with XML-like content.
4018
 *
4019
 *  @todo Add schema- or other-hierarchy-definition-based validation
4020
 *    of the subtree here.
4021
 *
4022
 *  This is a non-recursing parser. It uses the document's parser memory stack
4023
 *  to send subsequent nodes back to the controller for dispatching to parsers.
4024
 *  This parser is also re-enterable, so that post-processing can occur after
4025
 *  such dispatching.
4026
*/
4027
Node* TY_(ParseNamespace)( TidyDocImpl* doc, Node *basenode, GetTokenMode mode )
4028
0
{
4029
0
    Lexer* lexer = doc->lexer;
4030
0
    Node *node;
4031
0
    Node *parent = basenode;
4032
0
    uint istackbase;
4033
0
    AttVal* av; /* #130 MathML attr and entity fix! */
4034
4035
    /* a la <table>: defer popping elements off the inline stack */
4036
0
    TY_(DeferDup)( doc );
4037
0
    istackbase = lexer->istackbase;
4038
0
    lexer->istackbase = lexer->istacksize;
4039
4040
0
    mode = OtherNamespace; /* Preformatted; IgnoreWhitespace; */
4041
4042
0
    while ((node = TY_(GetToken)(doc, mode)) != NULL)
4043
0
    {
4044
        /*
4045
        fix check to skip action in InsertMisc for regular/empty
4046
        nodes, which we don't want here...
4047
4048
        The way we do it here is by checking and processing everything
4049
        and only what remains goes into InsertMisc()
4050
        */
4051
4052
        /* is this a close tag? And does it match the current parent node? */
4053
0
        if (node->type == EndTag)
4054
0
        {
4055
            /*
4056
            to prevent end tags flowing from one 'alternate namespace' we
4057
            check this in two phases: first we check if the tag is a
4058
            descendant of the current node, and when it is, we check whether
4059
            it is the end tag for a node /within/ or /outside/ the basenode.
4060
            */
4061
0
            Bool outside;
4062
0
            Node *mp = FindMatchingDescendant(parent, node, basenode, &outside);
4063
4064
0
            if (mp != NULL)
4065
0
            {
4066
                /*
4067
                when mp != parent as we might expect,
4068
                infer end tags until we 'hit' the matched
4069
                parent or the basenode
4070
                */
4071
0
                Node *n;
4072
4073
0
                for (n = parent;
4074
0
                     n != NULL && n != basenode->parent && n != mp;
4075
0
                     n = n->parent)
4076
0
                {
4077
                    /* n->implicit = yes; */
4078
0
                    n->closed = yes;
4079
0
                    TY_(Report)(doc, n->parent, n, MISSING_ENDTAG_BEFORE);
4080
0
                }
4081
4082
                /* Issue #369 - Since 'assert' is DEBUG only, and there are
4083
                   simple cases where these can be fired, removing them
4084
                   pending feedback from the original author!
4085
                   assert(outside == no ? n == mp : 1);
4086
                   assert(outside == yes ? n == basenode->parent : 1);
4087
                   =================================================== */
4088
4089
0
                if (outside == no)
4090
0
                {
4091
                    /* EndTag for a node within the basenode subtree. Roll on... */
4092
0
                    if (n)
4093
0
                        n->closed = yes;
4094
0
                    TY_(FreeNode)(doc, node);
4095
4096
0
                    node = n;
4097
0
                    parent = node ? node->parent : NULL;
4098
0
                }
4099
0
                else
4100
0
                {
4101
                    /* EndTag for a node outside the basenode subtree: let the caller handle that. */
4102
0
                    TY_(UngetToken)( doc );
4103
0
                    node = basenode;
4104
0
                    parent = node->parent;
4105
0
                }
4106
4107
                /* when we've arrived at the end-node for the base node, it's quitting time */
4108
0
                if (node == basenode)
4109
0
                {
4110
0
                    lexer->istackbase = istackbase;
4111
0
                    assert(basenode && basenode->closed == yes);
4112
0
                    return NULL;
4113
0
                }
4114
0
            }
4115
0
            else
4116
0
            {
4117
                /* unmatched close tag: report an error and discard */
4118
                /* TY_(Report)(doc, parent, node, NON_MATCHING_ENDTAG); Issue #308 - Seems wrong warning! */
4119
0
                TY_(Report)(doc, parent, node, DISCARDING_UNEXPECTED);
4120
0
                assert(parent);
4121
                /* assert(parent->tag != node->tag); Issue #308 - Seems would always be true! */
4122
0
                TY_(FreeNode)( doc, node); /* Issue #308 - Discard unexpected end tag memory */
4123
0
            }
4124
0
        }
4125
0
        else if (node->type == StartTag)
4126
0
        {
4127
            /* #130 MathML attr and entity fix!
4128
               care if it has attributes, and 'accidently' any of those attributes match known */
4129
0
            for ( av = node->attributes; av; av = av->next )
4130
0
            {
4131
0
                av->dict = 0; /* does something need to be freed? */
4132
0
            }
4133
            /* add another child to the current parent */
4134
0
            TY_(InsertNodeAtEnd)(parent, node);
4135
0
            parent = node;
4136
0
        }
4137
0
        else
4138
0
        {
4139
            /* #130 MathML attr and entity fix!
4140
               care if it has attributes, and 'accidently' any of those attributes match known */
4141
0
            for ( av = node->attributes; av; av = av->next )
4142
0
            {
4143
0
                av->dict = 0; /* does something need to be freed? */
4144
0
            }
4145
0
            TY_(InsertNodeAtEnd)(parent, node);
4146
0
        }
4147
0
    }
4148
4149
0
    TY_(Report)(doc, basenode->parent, basenode, MISSING_ENDTAG_FOR);
4150
0
    return NULL;
4151
0
}
4152
4153
4154
/** MARK: TY_(ParseNoFrames)
4155
 *  Parses the `noframes` tag.
4156
 *
4157
 *  This is a non-recursing parser. It uses the document's parser memory stack
4158
 *  to send subsequent nodes back to the controller for dispatching to parsers.
4159
 *  This parser is also re-enterable, so that post-processing can occur after
4160
 *  such dispatching.
4161
 */
4162
Node* TY_(ParseNoFrames)( TidyDocImpl* doc, Node *noframes, GetTokenMode mode )
4163
0
{
4164
0
    Lexer* lexer = doc->lexer;
4165
0
    Node *node = NULL;
4166
0
    Bool body_seen = no;
4167
0
    DEBUG_LOG_COUNTERS;
4168
4169
0
    enum parserState {
4170
0
        STATE_INITIAL,                /* This is the initial state for every parser. */
4171
0
        STATE_POST_NODEISBODY,        /* To-do after re-entering after checks. */
4172
0
        STATE_COMPLETE,               /* Done with the switch. */
4173
0
    } state = STATE_INITIAL;
4174
4175
    /*
4176
     If we're re-entering, then we need to setup from a previous state,
4177
     instead of starting fresh. We can pull what we need from the document's
4178
     stack.
4179
     */
4180
0
    if ( noframes == NULL )
4181
0
    {
4182
0
        TidyParserMemory memory = TY_(popMemory)( doc );
4183
0
        node = memory.reentry_node; /* Throwaway, because we replace it entering the loop anyway.*/
4184
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
4185
0
        noframes = memory.original_node;
4186
0
        state = memory.reentry_state;
4187
0
        body_seen = memory.register_1;
4188
0
        DEBUG_LOG_GET_OLD_MODE;
4189
0
        mode = memory.mode;
4190
0
        DEBUG_LOG_CHANGE_MODE;
4191
0
    }
4192
0
    else
4193
0
    {
4194
0
        DEBUG_LOG_ENTER_WITH_NODE(noframes);
4195
0
        if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
4196
0
        {
4197
0
            doc->badAccess |=  BA_USING_NOFRAMES;
4198
0
        }
4199
0
    }
4200
4201
0
    mode = IgnoreWhitespace;
4202
4203
0
    while ( state != STATE_COMPLETE )
4204
0
    {
4205
0
        if ( state == STATE_INITIAL )
4206
0
        {
4207
0
            node = TY_(GetToken)(doc, mode);
4208
0
            DEBUG_LOG_GOT_TOKEN(node);
4209
0
        }
4210
        
4211
0
        switch ( state )
4212
0
        {
4213
0
            case STATE_INITIAL:
4214
0
            {
4215
0
                if ( node == NULL )
4216
0
                {
4217
0
                    state = STATE_COMPLETE;
4218
0
                    continue;
4219
0
                }
4220
                
4221
0
                if ( node->tag == noframes->tag && node->type == EndTag )
4222
0
                {
4223
0
                    TY_(FreeNode)( doc, node);
4224
0
                    noframes->closed = yes;
4225
0
                    TrimSpaces(doc, noframes);
4226
0
                    DEBUG_LOG_EXIT;
4227
0
                    return NULL;
4228
0
                }
4229
4230
0
                if ( nodeIsFRAME(node) || nodeIsFRAMESET(node) )
4231
0
                {
4232
0
                    TrimSpaces(doc, noframes);
4233
0
                    if (node->type == EndTag)
4234
0
                    {
4235
0
                        TY_(Report)(doc, noframes, node, DISCARDING_UNEXPECTED);
4236
0
                        TY_(FreeNode)( doc, node);       /* Throw it away */
4237
0
                    }
4238
0
                    else
4239
0
                    {
4240
0
                        TY_(Report)(doc, noframes, node, MISSING_ENDTAG_BEFORE);
4241
0
                        TY_(UngetToken)( doc );
4242
0
                    }
4243
0
                    DEBUG_LOG_EXIT;
4244
0
                    return NULL;
4245
0
                }
4246
4247
0
                if ( nodeIsHTML(node) )
4248
0
                {
4249
0
                    if (TY_(nodeIsElement)(node))
4250
0
                        TY_(Report)(doc, noframes, node, DISCARDING_UNEXPECTED);
4251
4252
0
                    TY_(FreeNode)( doc, node);
4253
0
                    continue;
4254
0
                }
4255
4256
                /* deal with comments etc. */
4257
0
                if (InsertMisc(noframes, node))
4258
0
                    continue;
4259
4260
0
                if ( nodeIsBODY(node) && node->type == StartTag )
4261
0
                {
4262
0
                    TidyParserMemory memory = {0};
4263
0
                    memory.identity = TY_(ParseNoFrames);
4264
0
                    memory.original_node = noframes;
4265
0
                    memory.reentry_node = node;
4266
0
                    memory.reentry_state = STATE_POST_NODEISBODY;
4267
0
                    memory.register_1 = lexer->seenEndBody;
4268
0
                    memory.mode = IgnoreWhitespace;
4269
4270
0
                    TY_(InsertNodeAtEnd)(noframes, node);
4271
0
                    TY_(pushMemory)( doc, memory );
4272
0
                    DEBUG_LOG_EXIT_WITH_NODE(node);
4273
0
                    return node;
4274
0
                }
4275
4276
                /* implicit body element inferred */
4277
0
                if (TY_(nodeIsText)(node) || (node->tag && node->type != EndTag))
4278
0
                {
4279
0
                    Node *body = TY_(FindBody)( doc );
4280
0
                    if ( body || lexer->seenEndBody )
4281
0
                    {
4282
0
                        if ( body == NULL )
4283
0
                        {
4284
0
                            TY_(Report)(doc, noframes, node, DISCARDING_UNEXPECTED);
4285
0
                            TY_(FreeNode)( doc, node);
4286
0
                            continue;
4287
0
                        }
4288
0
                        if ( TY_(nodeIsText)(node) )
4289
0
                        {
4290
0
                            TY_(UngetToken)( doc );
4291
0
                            node = TY_(InferredTag)(doc, TidyTag_P);
4292
0
                            TY_(Report)(doc, noframes, node, CONTENT_AFTER_BODY );
4293
0
                        }
4294
0
                        TY_(InsertNodeAtEnd)( body, node );
4295
0
                    }
4296
0
                    else
4297
0
                    {
4298
0
                        TY_(UngetToken)( doc );
4299
0
                        node = TY_(InferredTag)(doc, TidyTag_BODY);
4300
0
                        if ( cfgBool(doc, TidyXmlOut) )
4301
0
                            TY_(Report)(doc, noframes, node, INSERTING_TAG);
4302
0
                        TY_(InsertNodeAtEnd)( noframes, node );
4303
0
                    }
4304
4305
0
                    {
4306
0
                        TidyParserMemory memory = {0};
4307
0
                        memory.identity = TY_(ParseNoFrames);
4308
0
                        memory.original_node = noframes;
4309
0
                        memory.reentry_node = node;
4310
0
                        memory.mode = IgnoreWhitespace; /*MixedContent*/
4311
0
                        memory.reentry_state = STATE_INITIAL;
4312
0
                        TY_(pushMemory)( doc, memory );
4313
0
                        DEBUG_LOG_EXIT_WITH_NODE(node);
4314
0
                        return node;
4315
0
                    }
4316
0
                }
4317
4318
                /* discard unexpected end tags */
4319
0
                TY_(Report)(doc, noframes, node, DISCARDING_UNEXPECTED);
4320
0
                TY_(FreeNode)( doc, node);
4321
0
            } break;
4322
                
4323
                
4324
0
            case STATE_POST_NODEISBODY:
4325
0
            {
4326
                /* fix for bug http://tidy.sf.net/bug/887259 */
4327
0
                if (body_seen && TY_(FindBody)(doc) != node)
4328
0
                {
4329
0
                    TY_(CoerceNode)(doc, node, TidyTag_DIV, no, no);
4330
0
                    MoveNodeToBody(doc, node);
4331
0
                }
4332
0
                state = STATE_INITIAL;
4333
0
                continue;
4334
4335
0
            } break;
4336
                
4337
                
4338
0
            default:
4339
0
                break;
4340
0
        } /* switch */
4341
0
    } /* while */
4342
4343
0
    TY_(Report)(doc, noframes, node, MISSING_ENDTAG_FOR);
4344
0
    DEBUG_LOG_EXIT;
4345
0
    return NULL;
4346
0
}
4347
4348
4349
/** MARK: TY_(ParseOptGroup)
4350
 *  Parses the `optgroup` tag.
4351
 *
4352
 *  This is a non-recursing parser. It uses the document's parser memory stack
4353
 *  to send subsequent nodes back to the controller for dispatching to parsers.
4354
 *  This parser is also re-enterable, so that post-processing can occur after
4355
 *  such dispatching.
4356
 */
4357
Node* TY_(ParseOptGroup)( TidyDocImpl* doc, Node *field, GetTokenMode ARG_UNUSED(mode) )
4358
0
{
4359
0
    Lexer* lexer = doc->lexer;
4360
0
    Node *node;
4361
0
    DEBUG_LOG_COUNTERS;
4362
    
4363
0
    if ( field == NULL )
4364
0
    {
4365
0
        TidyParserMemory memory = TY_(popMemory)( doc );
4366
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
4367
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
4368
0
        field = memory.original_node;
4369
0
        DEBUG_LOG_GET_OLD_MODE;
4370
0
        mode = memory.mode;
4371
0
        DEBUG_LOG_CHANGE_MODE;
4372
0
    }
4373
0
    else
4374
0
    {
4375
0
        DEBUG_LOG_ENTER_WITH_NODE(field);
4376
0
    }
4377
    
4378
0
    lexer->insert = NULL;  /* defer implicit inline start tags */
4379
4380
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
4381
0
    {
4382
0
        if (node->tag == field->tag && node->type == EndTag)
4383
0
        {
4384
0
            TY_(FreeNode)( doc, node);
4385
0
            field->closed = yes;
4386
0
            TrimSpaces(doc, field);
4387
0
            DEBUG_LOG_EXIT;
4388
0
            return NULL;
4389
0
        }
4390
4391
        /* deal with comments etc. */
4392
0
        if (InsertMisc(field, node))
4393
0
            continue;
4394
4395
0
        if ( node->type == StartTag &&
4396
0
             (nodeIsOPTION(node) || nodeIsOPTGROUP(node)) )
4397
0
        {
4398
0
            TidyParserMemory memory = {0};
4399
4400
0
            if ( nodeIsOPTGROUP(node) )
4401
0
                TY_(Report)(doc, field, node, CANT_BE_NESTED);
4402
4403
0
            TY_(InsertNodeAtEnd)(field, node);
4404
4405
0
            memory.identity = TY_(ParseOptGroup);
4406
0
            memory.original_node = field;
4407
0
            memory.reentry_node = node;
4408
0
            TY_(pushMemory)( doc, memory );
4409
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
4410
0
            return node;
4411
0
        }
4412
4413
        /* discard unexpected tags */
4414
0
        TY_(Report)(doc, field, node, DISCARDING_UNEXPECTED );
4415
0
        TY_(FreeNode)( doc, node);
4416
0
    }
4417
0
    DEBUG_LOG_EXIT;
4418
0
    return NULL;
4419
0
}
4420
4421
4422
/** MARK: TY_(ParsePre)
4423
 *  Parses the `pre` tag.
4424
 *
4425
 *  This is a non-recursing parser. It uses the document's parser memory stack
4426
 *  to send subsequent nodes back to the controller for dispatching to parsers.
4427
 *  This parser is also re-enterable, so that post-processing can occur after
4428
 *  such dispatching.
4429
 */
4430
Node* TY_(ParsePre)( TidyDocImpl* doc, Node *pre, GetTokenMode ARG_UNUSED(mode) )
4431
0
{
4432
0
    Node *node = NULL;
4433
0
    DEBUG_LOG_COUNTERS;
4434
4435
0
    enum parserState {
4436
0
        STATE_INITIAL,                /* This is the initial state for every parser. */
4437
0
        STATE_RENTRY_ACTION,          /* To-do after re-entering after checks. */
4438
0
        STATE_COMPLETE,               /* Done with the switch. */
4439
0
    } state = STATE_INITIAL;
4440
4441
4442
0
    if ( pre == NULL )
4443
0
    {
4444
0
        TidyParserMemory memory = TY_(popMemory)( doc );
4445
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
4446
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
4447
0
        pre = memory.original_node;
4448
0
        state = memory.reentry_state;
4449
0
        DEBUG_LOG_GET_OLD_MODE;
4450
0
        mode = memory.mode;
4451
0
        DEBUG_LOG_CHANGE_MODE;
4452
0
    }
4453
0
    else
4454
0
    {
4455
0
        DEBUG_LOG_ENTER_WITH_NODE(pre);
4456
0
        if (pre->tag->model & CM_EMPTY)
4457
0
        {
4458
0
            DEBUG_LOG_EXIT;
4459
0
            return NULL;
4460
0
        }
4461
0
    }
4462
4463
0
    TY_(InlineDup)( doc, NULL ); /* tell lexer to insert inlines if needed */
4464
4465
0
    while ( state != STATE_COMPLETE )
4466
0
    {
4467
0
        if ( state == STATE_INITIAL )
4468
0
            node = TY_(GetToken)(doc, Preformatted);
4469
        
4470
0
        switch ( state )
4471
0
        {
4472
0
            case STATE_INITIAL:
4473
0
            {
4474
0
                if ( node == NULL )
4475
0
                {
4476
0
                    state = STATE_COMPLETE;
4477
0
                    continue;
4478
0
                }
4479
                
4480
0
                if ( node->type == EndTag &&
4481
0
                     (node->tag == pre->tag || DescendantOf(pre, TagId(node))) )
4482
0
                {
4483
0
                    if (nodeIsBODY(node) || nodeIsHTML(node))
4484
0
                    {
4485
0
                        TY_(Report)(doc, pre, node, DISCARDING_UNEXPECTED);
4486
0
                        TY_(FreeNode)(doc, node);
4487
0
                        continue;
4488
0
                    }
4489
0
                    if (node->tag == pre->tag)
4490
0
                    {
4491
0
                        TY_(FreeNode)(doc, node);
4492
0
                    }
4493
0
                    else
4494
0
                    {
4495
0
                        TY_(Report)(doc, pre, node, MISSING_ENDTAG_BEFORE );
4496
0
                        TY_(UngetToken)( doc );
4497
0
                    }
4498
0
                    pre->closed = yes;
4499
0
                    TrimSpaces(doc, pre);
4500
0
                    DEBUG_LOG_EXIT;
4501
0
                    return NULL;
4502
0
                }
4503
4504
0
                if (TY_(nodeIsText)(node))
4505
0
                {
4506
0
                    TY_(InsertNodeAtEnd)(pre, node);
4507
0
                    continue;
4508
0
                }
4509
4510
                /* deal with comments etc. */
4511
0
                if (InsertMisc(pre, node))
4512
0
                    continue;
4513
4514
0
                if (node->tag == NULL)
4515
0
                {
4516
0
                    TY_(Report)(doc, pre, node, DISCARDING_UNEXPECTED);
4517
0
                    TY_(FreeNode)(doc, node);
4518
0
                    continue;
4519
0
                }
4520
4521
                /* strip unexpected tags */
4522
0
                if ( !PreContent(doc, node) )
4523
0
                {
4524
                    /* fix for http://tidy.sf.net/bug/772205 */
4525
0
                    if (node->type == EndTag)
4526
0
                    {
4527
                        /* http://tidy.sf.net/issue/1590220 */
4528
0
                       if ( doc->lexer->exiled
4529
0
                           && (TY_(nodeHasCM)(node, CM_TABLE) || nodeIsTABLE(node)) )
4530
0
                       {
4531
0
                          TY_(UngetToken)(doc);
4532
0
                          TrimSpaces(doc, pre);
4533
0
                           DEBUG_LOG_EXIT;
4534
0
                          return NULL;
4535
0
                       }
4536
4537
0
                       TY_(Report)(doc, pre, node, DISCARDING_UNEXPECTED);
4538
0
                       TY_(FreeNode)(doc, node);
4539
0
                       continue;
4540
0
                    }
4541
                    /* http://tidy.sf.net/issue/1590220 */
4542
0
                    else if (TY_(nodeHasCM)(node, CM_TABLE|CM_ROW)
4543
0
                             || nodeIsTABLE(node) )
4544
0
                    {
4545
0
                        if (!doc->lexer->exiled)
4546
                            /* No missing close warning if exiled. */
4547
0
                            TY_(Report)(doc, pre, node, MISSING_ENDTAG_BEFORE);
4548
4549
0
                        TY_(UngetToken)(doc);
4550
0
                        DEBUG_LOG_EXIT;
4551
0
                        return NULL;
4552
0
                    }
4553
4554
                    /*
4555
                      This is basically what Tidy 04 August 2000 did and far more accurate
4556
                      with respect to browser behaivour than the code commented out above.
4557
                      Tidy could try to propagate the <pre> into each disallowed child where
4558
                      <pre> is allowed in order to replicate some browsers behaivour, but
4559
                      there are a lot of exceptions, e.g. Internet Explorer does not propagate
4560
                      <pre> into table cells while Mozilla does. Opera 6 never propagates
4561
                      <pre> into blocklevel elements while Opera 7 behaves much like Mozilla.
4562
4563
                      Tidy behaves thus mostly like Opera 6 except for nested <pre> elements
4564
                      which are handled like Mozilla takes them (Opera6 closes all <pre> after
4565
                      the first </pre>).
4566
4567
                      There are similar issues like replacing <p> in <pre> with <br>, for
4568
                      example
4569
4570
                        <pre>...<p>...</pre>                 (Input)
4571
                        <pre>...<br>...</pre>                (Tidy)
4572
                        <pre>...<br>...</pre>                (Opera 7 and Internet Explorer)
4573
                        <pre>...<br><br>...</pre>            (Opera 6 and Mozilla)
4574
4575
                        <pre>...<p>...</p>...</pre>          (Input)
4576
                        <pre>...<br>......</pre>             (Tidy, BUG!)
4577
                        <pre>...<br>...<br>...</pre>         (Internet Explorer)
4578
                        <pre>...<br><br>...<br><br>...</pre> (Mozilla, Opera 6)
4579
                        <pre>...<br>...<br><br>...</pre>     (Opera 7)
4580
4581
                      or something similar, they could also be closing the <pre> and propagate
4582
                      the <pre> into the newly opened <p>.
4583
4584
                      Todo: IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, and BASEFONT are
4585
                      disallowed in <pre>, Tidy neither detects this nor does it perform any
4586
                      cleanup operation. Tidy should at least issue a warning if it encounters
4587
                      such constructs.
4588
4589
                      Todo: discarding </p> is abviously a bug, it should be replaced by <br>.
4590
                    */
4591
0
                    TY_(InsertNodeAfterElement)(pre, node);
4592
0
                    TY_(Report)(doc, pre, node, MISSING_ENDTAG_BEFORE);
4593
                    
4594
0
                    {
4595
0
                        TidyParserMemory memory = {0};
4596
0
                        memory.identity = TY_(ParsePre);
4597
0
                        memory.original_node = pre;
4598
0
                        memory.reentry_node = node;
4599
0
                        memory.reentry_state = STATE_RENTRY_ACTION;
4600
0
                        TY_(pushMemory)( doc, memory );
4601
0
                        DEBUG_LOG_EXIT_WITH_NODE(node);
4602
0
                        return node;
4603
0
                    }
4604
0
                }
4605
4606
0
                if ( nodeIsP(node) )
4607
0
                {
4608
0
                    if (node->type == StartTag)
4609
0
                    {
4610
0
                        TY_(Report)(doc, pre, node, USING_BR_INPLACE_OF);
4611
4612
                        /* trim white space before <p> in <pre>*/
4613
0
                        TrimSpaces(doc, pre);
4614
4615
                        /* coerce both <p> and </p> to <br> */
4616
0
                        TY_(CoerceNode)(doc, node, TidyTag_BR, no, no);
4617
0
                        TY_(FreeAttrs)( doc, node ); /* discard align attribute etc. */
4618
0
                        TY_(InsertNodeAtEnd)( pre, node );
4619
0
                    }
4620
0
                    else
4621
0
                    {
4622
0
                        TY_(Report)(doc, pre, node, DISCARDING_UNEXPECTED);
4623
0
                        TY_(FreeNode)( doc, node);
4624
0
                    }
4625
0
                    continue;
4626
0
                }
4627
4628
0
                if ( TY_(nodeIsElement)(node) )
4629
0
                {
4630
                    /* trim white space before <br> */
4631
0
                    if ( nodeIsBR(node) )
4632
0
                        TrimSpaces(doc, pre);
4633
4634
0
                    TY_(InsertNodeAtEnd)(pre, node);
4635
                    
4636
0
                    {
4637
0
                        TidyParserMemory memory = {0};
4638
0
                        memory.identity = TY_(ParsePre);
4639
0
                        memory.original_node = pre;
4640
0
                        memory.reentry_node = node;
4641
0
                        memory.reentry_state = STATE_INITIAL;
4642
0
                        TY_(pushMemory)( doc, memory );
4643
0
                        DEBUG_LOG_EXIT_WITH_NODE(node);
4644
0
                        return node;
4645
0
                    }
4646
0
                }
4647
4648
                /* discard unexpected tags */
4649
0
                TY_(Report)(doc, pre, node, DISCARDING_UNEXPECTED);
4650
0
                TY_(FreeNode)( doc, node);
4651
0
            } break;
4652
                
4653
0
            case STATE_RENTRY_ACTION:
4654
0
            {
4655
0
                Node* newnode = TY_(InferredTag)(doc, TidyTag_PRE);
4656
0
                TY_(Report)(doc, pre, newnode, INSERTING_TAG);
4657
0
                pre = newnode;
4658
0
                TY_(InsertNodeAfterElement)(node, pre);
4659
0
                state = STATE_INITIAL;
4660
0
                continue;
4661
0
            } break;
4662
            
4663
0
            default:
4664
0
                break;
4665
4666
0
        } /* switch */
4667
0
    } /* while */
4668
4669
0
    TY_(Report)(doc, pre, node, MISSING_ENDTAG_FOR);
4670
0
    DEBUG_LOG_EXIT;
4671
0
    return NULL;
4672
0
}
4673
4674
4675
/** MARK: TY_(ParseRow)
4676
 *  Parses the `row` tag.
4677
 *
4678
 *  This is a non-recursing parser. It uses the document's parser memory stack
4679
 *  to send subsequent nodes back to the controller for dispatching to parsers.
4680
 *  This parser is also re-enterable, so that post-processing can occur after
4681
 *  such dispatching.
4682
 */
4683
Node* TY_(ParseRow)( TidyDocImpl* doc, Node *row, GetTokenMode ARG_UNUSED(mode) )
4684
0
{
4685
0
    Lexer* lexer = doc->lexer;
4686
0
    Node *node = NULL;
4687
0
    Bool exclude_state = no;
4688
0
    DEBUG_LOG_COUNTERS;
4689
4690
0
    enum parserState {
4691
0
        STATE_INITIAL,                /* This is the initial state for every parser. */
4692
0
        STATE_POST_NOT_ENDTAG,        /* To-do after re-entering after !EndTag checks. */
4693
0
        STATE_POST_TD_TH,             /* To-do after re-entering after TD/TH checks. */
4694
0
        STATE_COMPLETE,               /* Done with the switch. */
4695
0
    } state = STATE_INITIAL;
4696
4697
0
    if ( row == NULL )
4698
0
    {
4699
0
        TidyParserMemory memory = TY_(popMemory)( doc );
4700
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
4701
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
4702
0
        row = memory.original_node;
4703
0
        state = memory.reentry_state;
4704
0
        exclude_state = memory.register_1;
4705
0
        DEBUG_LOG_GET_OLD_MODE;
4706
0
        mode = memory.mode;
4707
0
        DEBUG_LOG_CHANGE_MODE;
4708
0
    }
4709
0
    else
4710
0
    {
4711
0
        DEBUG_LOG_ENTER_WITH_NODE(row);
4712
4713
0
        if (row->tag->model & CM_EMPTY)
4714
0
            return NULL;
4715
0
    }
4716
4717
0
    while ( state != STATE_COMPLETE )
4718
0
    {
4719
0
        if ( state == STATE_INITIAL )
4720
0
        {
4721
0
            node = TY_(GetToken)( doc, IgnoreWhitespace );
4722
0
            DEBUG_LOG_GOT_TOKEN(node);
4723
0
        }
4724
    
4725
0
        switch (state)
4726
0
        {
4727
0
            case STATE_INITIAL:
4728
0
            {
4729
0
                if ( node == NULL)
4730
0
                {
4731
0
                    state = STATE_COMPLETE;
4732
0
                    continue;
4733
0
                }
4734
                
4735
0
                if (node->tag == row->tag)
4736
0
                {
4737
0
                    if (node->type == EndTag)
4738
0
                    {
4739
0
                        TY_(FreeNode)( doc, node);
4740
0
                        row->closed = yes;
4741
0
                        FixEmptyRow( doc, row);
4742
0
                        DEBUG_LOG_EXIT;
4743
0
                        return NULL;
4744
0
                    }
4745
4746
                    /* New row start implies end of current row */
4747
0
                    TY_(UngetToken)( doc );
4748
0
                    FixEmptyRow( doc, row);
4749
0
                    DEBUG_LOG_EXIT;
4750
0
                    return NULL;
4751
0
                }
4752
4753
                /*
4754
                  if this is the end tag for an ancestor element
4755
                  then infer end tag for this element
4756
                */
4757
0
                if ( node->type == EndTag )
4758
0
                {
4759
0
                    if ( (TY_(nodeHasCM)(node, CM_HTML|CM_TABLE) || nodeIsTABLE(node))
4760
0
                         && DescendantOf(row, TagId(node)) )
4761
0
                    {
4762
0
                        TY_(UngetToken)( doc );
4763
0
                        DEBUG_LOG_EXIT;
4764
0
                        return NULL;
4765
0
                    }
4766
4767
0
                    if ( nodeIsFORM(node) || TY_(nodeHasCM)(node, CM_BLOCK|CM_INLINE) )
4768
0
                    {
4769
0
                        if ( nodeIsFORM(node) )
4770
0
                            BadForm( doc );
4771
4772
0
                        TY_(Report)(doc, row, node, DISCARDING_UNEXPECTED);
4773
0
                        TY_(FreeNode)( doc, node);
4774
0
                        continue;
4775
0
                    }
4776
4777
0
                    if ( nodeIsTD(node) || nodeIsTH(node) )
4778
0
                    {
4779
0
                        TY_(Report)(doc, row, node, DISCARDING_UNEXPECTED);
4780
0
                        TY_(FreeNode)( doc, node);
4781
0
                        continue;
4782
0
                    }
4783
0
                }
4784
4785
                /* deal with comments etc. */
4786
0
                if (InsertMisc(row, node))
4787
0
                    continue;
4788
4789
                /* discard unknown tags */
4790
0
                if (node->tag == NULL && node->type != TextNode)
4791
0
                {
4792
0
                    TY_(Report)(doc, row, node, DISCARDING_UNEXPECTED);
4793
0
                    TY_(FreeNode)( doc, node);
4794
0
                    continue;
4795
0
                }
4796
4797
                /* discard unexpected <table> element */
4798
0
                if ( nodeIsTABLE(node) )
4799
0
                {
4800
0
                    TY_(Report)(doc, row, node, DISCARDING_UNEXPECTED);
4801
0
                    TY_(FreeNode)( doc, node);
4802
0
                    continue;
4803
0
                }
4804
4805
                /* THEAD, TFOOT or TBODY */
4806
0
                if ( TY_(nodeHasCM)(node, CM_ROWGRP) )
4807
0
                {
4808
0
                    TY_(UngetToken)( doc );
4809
0
                    DEBUG_LOG_EXIT;
4810
0
                    return NULL;
4811
0
                }
4812
4813
0
                if (node->type == EndTag)
4814
0
                {
4815
0
                    TY_(Report)(doc, row, node, DISCARDING_UNEXPECTED);
4816
0
                    TY_(FreeNode)( doc, node);
4817
0
                    continue;
4818
0
                }
4819
4820
                /*
4821
                  if text or inline or block move before table
4822
                  if head content move to head
4823
                */
4824
4825
0
                if (node->type != EndTag)
4826
0
                {
4827
0
                    if ( nodeIsFORM(node) )
4828
0
                    {
4829
0
                        TY_(UngetToken)( doc );
4830
0
                        node = TY_(InferredTag)(doc, TidyTag_TD);
4831
0
                        TY_(Report)(doc, row, node, MISSING_STARTTAG);
4832
0
                    }
4833
0
                    else if ( TY_(nodeIsText)(node)
4834
0
                              || TY_(nodeHasCM)(node, CM_BLOCK | CM_INLINE) )
4835
0
                    {
4836
0
                        MoveBeforeTable( doc, row, node );
4837
0
                        TY_(Report)(doc, row, node, TAG_NOT_ALLOWED_IN);
4838
0
                        lexer->exiled = yes;
4839
0
                        exclude_state = lexer->excludeBlocks;
4840
0
                        lexer->excludeBlocks = no;
4841
4842
0
                        if (node->type != TextNode)
4843
0
                        {
4844
0
                            TidyParserMemory memory = {0};
4845
0
                            memory.identity = TY_(ParseRow);
4846
0
                            memory.original_node = row;
4847
0
                            memory.reentry_node = node;
4848
0
                            memory.reentry_state = STATE_POST_NOT_ENDTAG;
4849
0
                            memory.register_1 = exclude_state;
4850
0
                            TY_(pushMemory)( doc, memory );
4851
0
                            DEBUG_LOG_EXIT_WITH_NODE(node);
4852
0
                            return node;
4853
0
                        }
4854
                        
4855
0
                        lexer->exiled = no;
4856
0
                        lexer->excludeBlocks = exclude_state;
4857
0
                        continue;
4858
0
                    }
4859
0
                    else if (node->tag->model & CM_HEAD)
4860
0
                    {
4861
0
                        TY_(Report)(doc, row, node, TAG_NOT_ALLOWED_IN);
4862
0
                        MoveToHead( doc, row, node);
4863
0
                        continue;
4864
0
                    }
4865
0
                }
4866
4867
0
                if ( !(nodeIsTD(node) || nodeIsTH(node)) )
4868
0
                {
4869
0
                    TY_(Report)(doc, row, node, TAG_NOT_ALLOWED_IN);
4870
0
                    TY_(FreeNode)( doc, node);
4871
0
                    continue;
4872
0
                }
4873
4874
                /* node should be <TD> or <TH> */
4875
0
                TY_(InsertNodeAtEnd)(row, node);
4876
0
                exclude_state = lexer->excludeBlocks;
4877
0
                lexer->excludeBlocks = no;
4878
0
                {
4879
0
                    TidyParserMemory memory = {0};
4880
0
                    memory.identity = TY_(ParseRow);
4881
0
                    memory.original_node = row;
4882
0
                    memory.reentry_node = node;
4883
0
                    memory.reentry_state = STATE_POST_TD_TH;
4884
0
                    memory.register_1 = exclude_state;
4885
0
                    TY_(pushMemory)( doc, memory );
4886
0
                    DEBUG_LOG_EXIT_WITH_NODE(node);
4887
0
                    return node;
4888
0
                }
4889
0
            } break;
4890
                
4891
                
4892
0
            case STATE_POST_NOT_ENDTAG:
4893
0
            {
4894
0
                lexer->exiled = no;
4895
0
                lexer->excludeBlocks = exclude_state; /* capture this in stack. */
4896
0
                state = STATE_INITIAL;
4897
0
                continue;
4898
0
            } break;
4899
                
4900
                
4901
0
            case STATE_POST_TD_TH:
4902
0
            {
4903
0
                lexer->excludeBlocks = exclude_state; /* capture this in stack. */
4904
4905
                /* pop inline stack */
4906
0
                while ( lexer->istacksize > lexer->istackbase )
4907
0
                    TY_(PopInline)( doc, NULL );
4908
                
4909
0
                state = STATE_INITIAL;
4910
0
                continue;
4911
0
            } break;
4912
                
4913
                
4914
0
            default:
4915
0
                break;
4916
                
4917
0
        } /* switch */
4918
0
    } /* while */
4919
0
    DEBUG_LOG_EXIT;
4920
0
    return NULL;
4921
0
}
4922
4923
4924
/** MARK: TY_(ParseRowGroup)
4925
 *  Parses the `rowgroup` tag.
4926
 *
4927
 *  This is a non-recursing parser. It uses the document's parser memory stack
4928
 *  to send subsequent nodes back to the controller for dispatching to parsers.
4929
 *  This parser is also re-enterable, so that post-processing can occur after
4930
 *  such dispatching.
4931
 */
4932
Node* TY_(ParseRowGroup)( TidyDocImpl* doc, Node *rowgroup, GetTokenMode ARG_UNUSED(mode) )
4933
0
{
4934
0
    Lexer* lexer = doc->lexer;
4935
0
    Node *node = NULL;
4936
0
    Node *parent = NULL;
4937
0
    DEBUG_LOG_COUNTERS;
4938
4939
0
    enum parserState {
4940
0
        STATE_INITIAL,                /* This is the initial state for every parser. */
4941
0
        STATE_POST_NOT_TEXTNODE,      /* To-do after re-entering after checks. */
4942
0
        STATE_COMPLETE,               /* Done with the switch. */
4943
0
    } state = STATE_INITIAL;
4944
4945
0
    if ( rowgroup == NULL )
4946
0
    {
4947
0
        TidyParserMemory memory = TY_(popMemory)( doc );
4948
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
4949
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
4950
0
        rowgroup = memory.original_node;
4951
0
        state = memory.reentry_state;
4952
0
        DEBUG_LOG_GET_OLD_MODE;
4953
0
        mode = memory.mode;
4954
0
        DEBUG_LOG_CHANGE_MODE;
4955
0
    }
4956
0
    else
4957
0
    {
4958
0
        DEBUG_LOG_ENTER_WITH_NODE(rowgroup);
4959
0
        if (rowgroup->tag->model & CM_EMPTY)
4960
0
        {
4961
0
            DEBUG_LOG_EXIT;
4962
0
            return NULL;
4963
0
        }
4964
0
    }
4965
4966
0
    while ( state != STATE_COMPLETE )
4967
0
    {
4968
0
        if ( state == STATE_INITIAL )
4969
0
            node = TY_(GetToken)(doc, IgnoreWhitespace);
4970
        
4971
0
        switch (state)
4972
0
        {
4973
0
            case STATE_INITIAL:
4974
0
            {
4975
0
                TidyParserMemory memory = {0};
4976
4977
0
                if (node == NULL)
4978
0
                {
4979
0
                    state = STATE_COMPLETE;
4980
0
                    continue;
4981
0
                }
4982
                
4983
0
                if (node->tag == rowgroup->tag)
4984
0
                {
4985
0
                    if (node->type == EndTag)
4986
0
                    {
4987
0
                        rowgroup->closed = yes;
4988
0
                        TY_(FreeNode)( doc, node);
4989
0
                        DEBUG_LOG_EXIT;
4990
0
                        return NULL;
4991
0
                    }
4992
4993
0
                    TY_(UngetToken)( doc );
4994
0
                    DEBUG_LOG_EXIT;
4995
0
                    return NULL;
4996
0
                }
4997
4998
                /* if </table> infer end tag */
4999
0
                if ( nodeIsTABLE(node) && node->type == EndTag )
5000
0
                {
5001
0
                    TY_(UngetToken)( doc );
5002
0
                    DEBUG_LOG_EXIT;
5003
0
                    return NULL;
5004
0
                }
5005
5006
                /* deal with comments etc. */
5007
0
                if (InsertMisc(rowgroup, node))
5008
0
                    continue;
5009
5010
                /* discard unknown tags */
5011
0
                if (node->tag == NULL && node->type != TextNode)
5012
0
                {
5013
0
                    TY_(Report)(doc, rowgroup, node, DISCARDING_UNEXPECTED);
5014
0
                    TY_(FreeNode)( doc, node);
5015
0
                    continue;
5016
0
                }
5017
5018
                /*
5019
                  if TD or TH then infer <TR>
5020
                  if text or inline or block move before table
5021
                  if head content move to head
5022
                */
5023
5024
0
                if (node->type != EndTag)
5025
0
                {
5026
0
                    if ( nodeIsTD(node) || nodeIsTH(node) )
5027
0
                    {
5028
0
                        TY_(UngetToken)( doc );
5029
0
                        node = TY_(InferredTag)(doc, TidyTag_TR);
5030
0
                        TY_(Report)(doc, rowgroup, node, MISSING_STARTTAG);
5031
0
                    }
5032
0
                    else if ( TY_(nodeIsText)(node)
5033
0
                              || TY_(nodeHasCM)(node, CM_BLOCK|CM_INLINE) )
5034
0
                    {
5035
0
                        MoveBeforeTable( doc, rowgroup, node );
5036
0
                        TY_(Report)(doc, rowgroup, node, TAG_NOT_ALLOWED_IN);
5037
0
                        lexer->exiled = yes;
5038
5039
0
                        if (node->type != TextNode)
5040
0
                        {
5041
0
                            memory.identity = TY_(ParseRowGroup);
5042
0
                            memory.original_node = rowgroup;
5043
0
                            memory.reentry_node = node;
5044
0
                            memory.reentry_state = STATE_POST_NOT_TEXTNODE;
5045
0
                            TY_(pushMemory)( doc, memory );
5046
0
                            DEBUG_LOG_EXIT_WITH_NODE(node);
5047
0
                            return node;
5048
0
                        }
5049
                        
5050
0
                        state = STATE_POST_NOT_TEXTNODE;
5051
0
                        continue;
5052
0
                    }
5053
0
                    else if (node->tag->model & CM_HEAD)
5054
0
                    {
5055
0
                        TY_(Report)(doc, rowgroup, node, TAG_NOT_ALLOWED_IN);
5056
0
                        MoveToHead(doc, rowgroup, node);
5057
0
                        continue;
5058
0
                    }
5059
0
                }
5060
5061
                /*
5062
                  if this is the end tag for ancestor element
5063
                  then infer end tag for this element
5064
                */
5065
0
                if (node->type == EndTag)
5066
0
                {
5067
0
                    if ( nodeIsFORM(node) || TY_(nodeHasCM)(node, CM_BLOCK|CM_INLINE) )
5068
0
                    {
5069
0
                        if ( nodeIsFORM(node) )
5070
0
                            BadForm( doc );
5071
5072
0
                        TY_(Report)(doc, rowgroup, node, DISCARDING_UNEXPECTED);
5073
0
                        TY_(FreeNode)( doc, node);
5074
0
                        continue;
5075
0
                    }
5076
5077
0
                    if ( nodeIsTR(node) || nodeIsTD(node) || nodeIsTH(node) )
5078
0
                    {
5079
0
                        TY_(Report)(doc, rowgroup, node, DISCARDING_UNEXPECTED);
5080
0
                        TY_(FreeNode)( doc, node);
5081
0
                        continue;
5082
0
                    }
5083
5084
0
                    for ( parent = rowgroup->parent;
5085
0
                          parent != NULL;
5086
0
                          parent = parent->parent )
5087
0
                    {
5088
0
                        if (node->tag == parent->tag)
5089
0
                        {
5090
0
                            TY_(UngetToken)( doc );
5091
0
                            DEBUG_LOG_EXIT;
5092
0
                            return NULL;
5093
0
                        }
5094
0
                    }
5095
0
                }
5096
5097
                /*
5098
                  if THEAD, TFOOT or TBODY then implied end tag
5099
5100
                */
5101
0
                if (node->tag->model & CM_ROWGRP)
5102
0
                {
5103
0
                    if (node->type != EndTag)
5104
0
                    {
5105
0
                        TY_(UngetToken)( doc );
5106
0
                        DEBUG_LOG_EXIT;
5107
0
                        return NULL;
5108
0
                    }
5109
0
                }
5110
5111
0
                if (node->type == EndTag)
5112
0
                {
5113
0
                    TY_(Report)(doc, rowgroup, node, DISCARDING_UNEXPECTED);
5114
0
                    TY_(FreeNode)( doc, node);
5115
0
                    continue;
5116
0
                }
5117
5118
0
                if ( !nodeIsTR(node) )
5119
0
                {
5120
0
                    node = TY_(InferredTag)(doc, TidyTag_TR);
5121
0
                    TY_(Report)(doc, rowgroup, node, MISSING_STARTTAG);
5122
0
                    TY_(UngetToken)( doc );
5123
0
                }
5124
5125
               /* node should be <TR> */
5126
0
                TY_(InsertNodeAtEnd)(rowgroup, node);
5127
0
                memory.identity = TY_(ParseRowGroup);
5128
0
                memory.original_node = rowgroup;
5129
0
                memory.reentry_node = node;
5130
0
                memory.reentry_state = STATE_INITIAL;
5131
0
                TY_(pushMemory)( doc, memory );
5132
0
                DEBUG_LOG_EXIT_WITH_NODE(node);
5133
0
                return node;
5134
0
            } break;
5135
                
5136
                
5137
0
            case STATE_POST_NOT_TEXTNODE:
5138
0
            {
5139
0
                lexer->exiled = no;
5140
0
                state = STATE_INITIAL;
5141
0
                continue;
5142
0
            } break;
5143
5144
                
5145
0
            default:
5146
0
                break;
5147
0
        } /* switch */
5148
0
    } /* while */
5149
0
    DEBUG_LOG_EXIT;
5150
0
    return NULL;
5151
0
}
5152
5153
5154
/** MARK: TY_(ParseScript)
5155
 *  Parses the `script` tag.
5156
 *
5157
 *  @todo This isn't quite right for CDATA content as it recognises tags
5158
 *  within the content and parses them accordingly. This will unfortunately
5159
 *  screw up scripts which include:
5160
 *    < + letter
5161
 *    < + !
5162
 *    < + ?
5163
 *    < + / + letter
5164
 *
5165
 *  This is a non-recursing parser. It uses the document's parser memory stack
5166
 *  to send subsequent nodes back to the controller for dispatching to parsers.
5167
 *  This parser is also re-enterable, so that post-processing can occur after
5168
 *  such dispatching.
5169
 */
5170
Node* TY_(ParseScript)( TidyDocImpl* doc, Node *script, GetTokenMode ARG_UNUSED(mode) )
5171
0
{
5172
0
    Node *node = NULL;
5173
#if defined(ENABLE_DEBUG_LOG)
5174
    static int depth_parser = 0;
5175
    static int count_parser = 0;
5176
#endif
5177
    
5178
0
    DEBUG_LOG_ENTER_WITH_NODE(script);
5179
    
5180
0
    doc->lexer->parent = script;
5181
0
    node = TY_(GetToken)(doc, CdataContent);
5182
0
    doc->lexer->parent = NULL;
5183
5184
0
    if (node)
5185
0
    {
5186
0
        TY_(InsertNodeAtEnd)(script, node);
5187
0
    }
5188
0
    else
5189
0
    {
5190
        /* handle e.g. a document like "<script>" */
5191
0
        TY_(Report)(doc, script, NULL, MISSING_ENDTAG_FOR);
5192
0
        DEBUG_LOG_EXIT;
5193
0
        return NULL;
5194
0
    }
5195
5196
0
    node = TY_(GetToken)(doc, IgnoreWhitespace);
5197
0
    DEBUG_LOG_GOT_TOKEN(node);
5198
5199
0
    if (!(node && node->type == EndTag && node->tag &&
5200
0
        node->tag->id == script->tag->id))
5201
0
    {
5202
0
        TY_(Report)(doc, script, node, MISSING_ENDTAG_FOR);
5203
5204
0
        if (node)
5205
0
            TY_(UngetToken)(doc);
5206
0
    }
5207
0
    else
5208
0
    {
5209
0
        TY_(FreeNode)(doc, node);
5210
0
    }
5211
0
    DEBUG_LOG_EXIT;
5212
0
    return NULL;
5213
0
}
5214
5215
5216
/** MARK: TY_(ParseSelect)
5217
 *  Parses the `select` tag.
5218
 *
5219
 *  This is a non-recursing parser. It uses the document's parser memory stack
5220
 *  to send subsequent nodes back to the controller for dispatching to parsers.
5221
 *  This parser is also re-enterable, so that post-processing can occur after
5222
 *  such dispatching.
5223
 */
5224
Node* TY_(ParseSelect)( TidyDocImpl* doc, Node *field, GetTokenMode ARG_UNUSED(mode) )
5225
0
{
5226
0
    Lexer* lexer = doc->lexer;
5227
0
    Node *node;
5228
0
    DEBUG_LOG_COUNTERS;
5229
5230
0
    if ( field == NULL )
5231
0
    {
5232
0
        TidyParserMemory memory = TY_(popMemory)( doc );
5233
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
5234
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
5235
0
        field = memory.original_node;
5236
0
        DEBUG_LOG_GET_OLD_MODE;
5237
0
        mode = memory.mode;
5238
0
        DEBUG_LOG_CHANGE_MODE;
5239
0
    }
5240
0
    else
5241
0
    {
5242
0
        DEBUG_LOG_ENTER_WITH_NODE(field);
5243
0
    }
5244
    
5245
0
    lexer->insert = NULL;  /* defer implicit inline start tags */
5246
5247
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
5248
0
    {
5249
0
        if (node->tag == field->tag && node->type == EndTag)
5250
0
        {
5251
0
            TY_(FreeNode)( doc, node);
5252
0
            field->closed = yes;
5253
0
            TrimSpaces(doc, field);
5254
5255
0
            DEBUG_LOG_EXIT;
5256
0
            return NULL;
5257
0
        }
5258
5259
        /* deal with comments etc. */
5260
0
        if (InsertMisc(field, node))
5261
0
            continue;
5262
5263
0
        if ( node->type == StartTag &&
5264
0
             ( nodeIsOPTION(node)   ||
5265
0
               nodeIsOPTGROUP(node) ||
5266
0
               nodeIsDATALIST(node) ||
5267
0
               nodeIsSCRIPT(node))
5268
0
           )
5269
0
        {
5270
0
            TidyParserMemory memory = {0};
5271
0
            memory.identity = TY_(ParseSelect);
5272
0
            memory.original_node = field;
5273
0
            memory.reentry_node = node;
5274
5275
0
            TY_(InsertNodeAtEnd)(field, node);
5276
0
            TY_(pushMemory)( doc, memory );
5277
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
5278
0
            return node;
5279
0
        }
5280
5281
        /* discard unexpected tags */
5282
0
        TY_(Report)(doc, field, node, DISCARDING_UNEXPECTED);
5283
0
        TY_(FreeNode)( doc, node);
5284
0
    }
5285
5286
0
    TY_(Report)(doc, field, node, MISSING_ENDTAG_FOR);
5287
5288
0
    DEBUG_LOG_EXIT;
5289
0
    return NULL;
5290
0
}
5291
5292
5293
/** MARK: TY_(ParseTableTag)
5294
 *  Parses the `table` tag.
5295
 *
5296
 *  This is a non-recursing parser. It uses the document's parser memory stack
5297
 *  to send subsequent nodes back to the controller for dispatching to parsers.
5298
 *  This parser is also re-enterable, so that post-processing can occur after
5299
 *  such dispatching.
5300
 */
5301
Node* TY_(ParseTableTag)( TidyDocImpl* doc, Node *table, GetTokenMode ARG_UNUSED(mode) )
5302
0
{
5303
0
    Lexer* lexer = doc->lexer;
5304
0
    Node *node, *parent;
5305
0
    uint istackbase;
5306
0
    DEBUG_LOG_COUNTERS;
5307
5308
0
    if ( table == NULL )
5309
0
    {
5310
0
        TidyParserMemory memory = TY_(popMemory)( doc );
5311
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
5312
0
        DEBUG_LOG_REENTER_WITH_NODE(node);
5313
0
        table = memory.original_node;
5314
0
        lexer->exiled = memory.register_1;
5315
0
        DEBUG_LOG_GET_OLD_MODE;
5316
0
        mode = memory.mode;
5317
0
        DEBUG_LOG_CHANGE_MODE;
5318
0
    }
5319
0
    else
5320
0
    {
5321
0
        DEBUG_LOG_ENTER_WITH_NODE(table);
5322
0
        TY_(DeferDup)( doc );
5323
0
    }
5324
5325
0
    istackbase = lexer->istackbase;
5326
0
    lexer->istackbase = lexer->istacksize;
5327
5328
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
5329
0
    {
5330
0
        DEBUG_LOG_GOT_TOKEN(node);
5331
0
        if (node->tag == table->tag )
5332
0
        {
5333
0
            if (node->type == EndTag)
5334
0
            {
5335
0
                TY_(FreeNode)(doc, node);
5336
0
            }
5337
0
            else
5338
0
            {
5339
                /* Issue #498 - If a <table> in a <table>
5340
                 * just close the current table, and issue a
5341
                 * warning. The previous action was to discard
5342
                 * this second <table>
5343
                 */
5344
0
                TY_(UngetToken)(doc);
5345
0
                TY_(Report)(doc, table, node, TAG_NOT_ALLOWED_IN);
5346
0
            }
5347
0
            lexer->istackbase = istackbase;
5348
0
            table->closed = yes;
5349
5350
0
            DEBUG_LOG_EXIT;
5351
0
            return NULL;
5352
0
        }
5353
5354
        /* deal with comments etc. */
5355
0
        if (InsertMisc(table, node))
5356
0
            continue;
5357
5358
        /* discard unknown tags */
5359
0
        if (node->tag == NULL && node->type != TextNode)
5360
0
        {
5361
0
            TY_(Report)(doc, table, node, DISCARDING_UNEXPECTED);
5362
0
            TY_(FreeNode)( doc, node);
5363
0
            continue;
5364
0
        }
5365
5366
        /* if TD or TH or text or inline or block then infer <TR> */
5367
5368
0
        if (node->type != EndTag)
5369
0
        {
5370
0
            if ( nodeIsTD(node) || nodeIsTH(node) || nodeIsTABLE(node) )
5371
0
            {
5372
0
                TY_(UngetToken)( doc );
5373
0
                node = TY_(InferredTag)(doc, TidyTag_TR);
5374
0
                TY_(Report)(doc, table, node, MISSING_STARTTAG);
5375
0
            }
5376
0
            else if ( TY_(nodeIsText)(node) ||TY_(nodeHasCM)(node,CM_BLOCK|CM_INLINE) )
5377
0
            {
5378
0
                TY_(InsertNodeBeforeElement)(table, node);
5379
0
                TY_(Report)(doc, table, node, TAG_NOT_ALLOWED_IN);
5380
0
                lexer->exiled = yes;
5381
5382
0
                if (node->type != TextNode)
5383
0
                {
5384
0
                    TidyParserMemory memory = {0};
5385
0
                    memory.identity = TY_(ParseTableTag);
5386
0
                    memory.original_node = table;
5387
0
                    memory.reentry_node = node;
5388
0
                    memory.register_1 = no; /* later, lexer->exiled = no */
5389
0
                    memory.mode = IgnoreWhitespace;
5390
0
                    TY_(pushMemory)( doc, memory );
5391
0
                    DEBUG_LOG_EXIT_WITH_NODE(node);
5392
0
                    return node;
5393
0
                }
5394
5395
0
                lexer->exiled = no;
5396
0
                continue;
5397
0
            }
5398
0
            else if (node->tag->model & CM_HEAD)
5399
0
            {
5400
0
                MoveToHead(doc, table, node);
5401
0
                continue;
5402
0
            }
5403
0
        }
5404
5405
        /*
5406
          if this is the end tag for an ancestor element
5407
          then infer end tag for this element
5408
        */
5409
0
        if (node->type == EndTag)
5410
0
        {
5411
0
            if ( nodeIsFORM(node) )
5412
0
            {
5413
0
                BadForm( doc );
5414
0
                TY_(Report)(doc, table, node, DISCARDING_UNEXPECTED);
5415
0
                TY_(FreeNode)( doc, node);
5416
0
                continue;
5417
0
            }
5418
5419
            /* best to discard unexpected block/inline end tags */
5420
0
            if ( TY_(nodeHasCM)(node, CM_TABLE|CM_ROW) ||
5421
0
                 TY_(nodeHasCM)(node, CM_BLOCK|CM_INLINE) )
5422
0
            {
5423
0
                TY_(Report)(doc, table, node, DISCARDING_UNEXPECTED);
5424
0
                TY_(FreeNode)( doc, node);
5425
0
                continue;
5426
0
            }
5427
5428
0
            for ( parent = table->parent;
5429
0
                  parent != NULL;
5430
0
                  parent = parent->parent )
5431
0
            {
5432
0
                if (node->tag == parent->tag)
5433
0
                {
5434
0
                    TY_(Report)(doc, table, node, MISSING_ENDTAG_BEFORE );
5435
0
                    TY_(UngetToken)( doc );
5436
0
                    lexer->istackbase = istackbase;
5437
5438
0
                    DEBUG_LOG_EXIT;
5439
0
                    return NULL;
5440
0
                }
5441
0
            }
5442
0
        }
5443
5444
0
        if (!(node->tag->model & CM_TABLE))
5445
0
        {
5446
0
            TY_(UngetToken)( doc );
5447
0
            TY_(Report)(doc, table, node, TAG_NOT_ALLOWED_IN);
5448
0
            lexer->istackbase = istackbase;
5449
5450
0
            DEBUG_LOG_EXIT;
5451
0
            return NULL;
5452
0
        }
5453
5454
0
        if (TY_(nodeIsElement)(node))
5455
0
        {
5456
0
            TidyParserMemory memory = {0};
5457
0
            TY_(InsertNodeAtEnd)(table, node);
5458
0
            memory.identity = TY_(ParseTableTag);
5459
0
            memory.original_node = table;
5460
0
            memory.reentry_node = node;
5461
0
            memory.register_1 = lexer->exiled;
5462
0
            TY_(pushMemory)( doc, memory );
5463
0
            DEBUG_LOG_EXIT_WITH_NODE(node);
5464
0
            return node;
5465
0
        }
5466
5467
        /* discard unexpected text nodes and end tags */
5468
0
        TY_(Report)(doc, table, node, DISCARDING_UNEXPECTED);
5469
0
        TY_(FreeNode)( doc, node);
5470
0
    }
5471
5472
0
    TY_(Report)(doc, table, node, MISSING_ENDTAG_FOR);
5473
0
    lexer->istackbase = istackbase;
5474
5475
0
    DEBUG_LOG_EXIT;
5476
0
    return NULL;
5477
0
}
5478
5479
5480
/** MARK: TY_(ParseText)
5481
 *  Parses the `option` and `textarea` tags.
5482
 *
5483
 *  This is a non-recursing parser. It uses the document's parser memory stack
5484
 *  to send subsequent nodes back to the controller for dispatching to parsers.
5485
 *  This parser is also re-enterable, so that post-processing can occur after
5486
 *  such dispatching.
5487
 */
5488
Node* TY_(ParseText)( TidyDocImpl* doc, Node *field, GetTokenMode mode )
5489
0
{
5490
0
    Lexer* lexer = doc->lexer;
5491
0
    Node *node;
5492
0
    DEBUG_LOG_COUNTERS;
5493
    
5494
0
    DEBUG_LOG_ENTER_WITH_NODE(field);
5495
5496
0
    lexer->insert = NULL;  /* defer implicit inline start tags */
5497
5498
0
    DEBUG_LOG_GET_OLD_MODE;
5499
0
    if ( nodeIsTEXTAREA(field) )
5500
0
        mode = Preformatted;
5501
0
    else
5502
0
        mode = MixedContent;  /* kludge for font tags */
5503
0
    DEBUG_LOG_CHANGE_MODE;
5504
5505
0
    while ((node = TY_(GetToken)(doc, mode)) != NULL)
5506
0
    {
5507
0
        if (node->tag == field->tag && node->type == EndTag)
5508
0
        {
5509
0
            TY_(FreeNode)( doc, node);
5510
0
            field->closed = yes;
5511
0
            TrimSpaces(doc, field);
5512
0
            DEBUG_LOG_EXIT;
5513
0
            return NULL;
5514
0
        }
5515
5516
        /* deal with comments etc. */
5517
0
        if (InsertMisc(field, node))
5518
0
            continue;
5519
5520
0
        if (TY_(nodeIsText)(node))
5521
0
        {
5522
            /* only called for 1st child */
5523
0
            if (field->content == NULL && !(mode & Preformatted))
5524
0
                TrimSpaces(doc, field);
5525
5526
0
            if (node->start >= node->end)
5527
0
            {
5528
0
                TY_(FreeNode)( doc, node);
5529
0
                continue;
5530
0
            }
5531
5532
0
            TY_(InsertNodeAtEnd)(field, node);
5533
0
            continue;
5534
0
        }
5535
5536
        /* for textarea should all cases of < and & be escaped? */
5537
5538
        /* discard inline tags e.g. font */
5539
0
        if (   node->tag
5540
0
            && node->tag->model & CM_INLINE
5541
0
            && !(node->tag->model & CM_FIELD)) /* #487283 - fix by Lee Passey 25 Jan 02 */
5542
0
        {
5543
0
            TY_(Report)(doc, field, node, DISCARDING_UNEXPECTED);
5544
0
            TY_(FreeNode)( doc, node);
5545
0
            continue;
5546
0
        }
5547
5548
        /* terminate element on other tags */
5549
0
        if (!(field->tag->model & CM_OPT))
5550
0
            TY_(Report)(doc, field, node, MISSING_ENDTAG_BEFORE);
5551
5552
0
        TY_(UngetToken)( doc );
5553
0
        TrimSpaces(doc, field);
5554
0
        DEBUG_LOG_EXIT;
5555
0
        return NULL;
5556
0
    }
5557
5558
0
    if (!(field->tag->model & CM_OPT))
5559
0
        TY_(Report)(doc, field, node, MISSING_ENDTAG_FOR);
5560
0
    DEBUG_LOG_EXIT;
5561
0
    return NULL;
5562
0
}
5563
5564
    
5565
/** MARK: TY_(ParseTitle)
5566
 *  Parses the `title` tag.
5567
 *
5568
 *  This is a non-recursing parser. It uses the document's parser memory stack
5569
 *  to send subsequent nodes back to the controller for dispatching to parsers.
5570
 *  This parser is also re-enterable, so that post-processing can occur after
5571
 *  such dispatching.
5572
 */
5573
Node* TY_(ParseTitle)( TidyDocImpl* doc, Node *title, GetTokenMode ARG_UNUSED(mode) )
5574
0
{
5575
0
    Node *node;
5576
0
    while ((node = TY_(GetToken)(doc, MixedContent)) != NULL)
5577
0
    {
5578
0
        if (node->tag == title->tag && node->type == StartTag
5579
0
            && cfgBool(doc, TidyCoerceEndTags) )
5580
0
        {
5581
0
            TY_(Report)(doc, title, node, COERCE_TO_ENDTAG);
5582
0
            node->type = EndTag;
5583
0
            TY_(UngetToken)( doc );
5584
0
            continue;
5585
0
        }
5586
0
        else if (node->tag == title->tag && node->type == EndTag)
5587
0
        {
5588
0
            TY_(FreeNode)( doc, node);
5589
0
            title->closed = yes;
5590
0
            TrimSpaces(doc, title);
5591
0
            return NULL;
5592
0
        }
5593
5594
0
        if (TY_(nodeIsText)(node))
5595
0
        {
5596
            /* only called for 1st child */
5597
0
            if (title->content == NULL)
5598
0
                TrimInitialSpace(doc, title, node);
5599
5600
0
            if (node->start >= node->end)
5601
0
            {
5602
0
                TY_(FreeNode)( doc, node);
5603
0
                continue;
5604
0
            }
5605
5606
0
            TY_(InsertNodeAtEnd)(title, node);
5607
0
            continue;
5608
0
        }
5609
5610
        /* deal with comments etc. */
5611
0
        if (InsertMisc(title, node))
5612
0
            continue;
5613
5614
        /* discard unknown tags */
5615
0
        if (node->tag == NULL)
5616
0
        {
5617
0
            TY_(Report)(doc, title, node, DISCARDING_UNEXPECTED);
5618
0
            TY_(FreeNode)( doc, node);
5619
0
            continue;
5620
0
        }
5621
5622
        /* pushback unexpected tokens */
5623
0
        TY_(Report)(doc, title, node, MISSING_ENDTAG_BEFORE);
5624
0
        TY_(UngetToken)( doc );
5625
0
        TrimSpaces(doc, title);
5626
0
        return NULL;
5627
0
    }
5628
5629
0
    TY_(Report)(doc, title, node, MISSING_ENDTAG_FOR);
5630
0
    return NULL;
5631
0
}
5632
5633
5634
/** MARK: ParseXMLElement
5635
 *  Parses the given XML element.
5636
 */
5637
static Node* ParseXMLElement(TidyDocImpl* doc, Node *element, GetTokenMode mode)
5638
0
{
5639
0
    Lexer* lexer = doc->lexer;
5640
0
    Node *node;
5641
5642
0
    if ( element == NULL )
5643
0
    {
5644
0
        TidyParserMemory memory = TY_(popMemory)( doc );
5645
0
        element = memory.original_node;
5646
0
        node = memory.reentry_node; /* Throwaway, as main loop overrwrites anyway. */
5647
0
        mode = memory.reentry_mode;
5648
0
        TY_(InsertNodeAtEnd)(element, node); /* The only re-entry action needed. */
5649
0
    }
5650
0
    else
5651
0
    {
5652
        /* if node is pre or has xml:space="preserve" then do so */
5653
0
        if ( TY_(XMLPreserveWhiteSpace)(doc, element) )
5654
0
            mode = Preformatted;
5655
5656
        /* deal with comments etc. */
5657
0
        InsertMisc( &doc->root, element);
5658
        
5659
        /* we shouldn't have plain text at this point. */
5660
0
        if (TY_(nodeIsText)(element))
5661
0
        {
5662
0
            TY_(Report)(doc, &doc->root, element, DISCARDING_UNEXPECTED);
5663
0
            TY_(FreeNode)( doc, element);
5664
0
            return NULL;
5665
0
        }
5666
0
    }
5667
0
    while ((node = TY_(GetToken)(doc, mode)) != NULL)
5668
0
    {
5669
0
        if (node->type == EndTag &&
5670
0
           node->element && element->element &&
5671
0
           TY_(tmbstrcmp)(node->element, element->element) == 0)
5672
0
        {
5673
0
            TY_(FreeNode)( doc, node);
5674
0
            element->closed = yes;
5675
0
            break;
5676
0
        }
5677
5678
        /* discard unexpected end tags */
5679
0
        if (node->type == EndTag)
5680
0
        {
5681
0
            if (element)
5682
0
                TY_(Report)(doc, element, node, UNEXPECTED_ENDTAG_IN);
5683
0
            else
5684
0
                TY_(Report)(doc, element, node, UNEXPECTED_ENDTAG_ERR);
5685
5686
0
            TY_(FreeNode)( doc, node);
5687
0
            continue;
5688
0
        }
5689
5690
        /* parse content on seeing start tag */
5691
0
        if (node->type == StartTag)
5692
0
        {
5693
0
            TidyParserMemory memory = {0};
5694
0
            memory.identity = ParseXMLElement;
5695
0
            memory.original_node = element;
5696
0
            memory.reentry_node = node;
5697
0
            memory.reentry_mode = mode;
5698
0
            TY_(pushMemory)( doc, memory );
5699
0
            return node;
5700
0
        }
5701
5702
0
        TY_(InsertNodeAtEnd)(element, node);
5703
0
    } /* while */
5704
5705
    /*
5706
     if first child is text then trim initial space and
5707
     delete text node if it is empty.
5708
    */
5709
5710
0
    node = element->content;
5711
5712
0
    if (TY_(nodeIsText)(node) && mode != Preformatted)
5713
0
    {
5714
0
        if ( lexer->lexbuf[node->start] == ' ' )
5715
0
        {
5716
0
            node->start++;
5717
5718
0
            if (node->start >= node->end)
5719
0
                TY_(DiscardElement)( doc, node );
5720
0
        }
5721
0
    }
5722
5723
    /*
5724
     if last child is text then trim final space and
5725
     delete the text node if it is empty
5726
    */
5727
5728
0
    node = element->last;
5729
5730
0
    if (TY_(nodeIsText)(node) && mode != Preformatted)
5731
0
    {
5732
0
        if ( lexer->lexbuf[node->end - 1] == ' ' )
5733
0
        {
5734
0
            node->end--;
5735
5736
0
            if (node->start >= node->end)
5737
0
                TY_(DiscardElement)( doc, node );
5738
0
        }
5739
0
    }
5740
0
    return NULL;
5741
0
}
5742
5743
5744
/***************************************************************************//*
5745
 ** MARK: - Post-Parse Operations
5746
 ***************************************************************************/
5747
5748
5749
/**
5750
 *  Performs checking of all attributes recursively starting at `node`.
5751
 */
5752
static void AttributeChecks(TidyDocImpl* doc, Node* node)
5753
0
{
5754
0
    Node *next;
5755
5756
0
    while (node)
5757
0
    {
5758
0
        next = node->next;
5759
5760
0
        if (TY_(nodeIsElement)(node))
5761
0
        {
5762
0
            if (node->tag && node->tag->chkattrs) /* [i_a]2 fix crash after adding SVG support with alt/unknown tag subtree insertion there */
5763
0
                node->tag->chkattrs(doc, node);
5764
0
            else
5765
0
                TY_(CheckAttributes)(doc, node);
5766
0
        }
5767
5768
0
        if (node->content)
5769
0
            AttributeChecks(doc, node->content);
5770
5771
0
        assert( next != node ); /* http://tidy.sf.net/issue/1603538 */
5772
0
        node = next;
5773
0
    }
5774
0
}
5775
5776
5777
/**
5778
 *  Encloses naked text in certain elements within `p` tags.
5779
 *
5780
 *  <form>, <blockquote>, and <noscript> do not allow #PCDATA in
5781
 *  HTML 4.01 Strict (%block; model instead of %flow;).
5782
 */
5783
static void EncloseBlockText(TidyDocImpl* doc, Node* node)
5784
0
{
5785
0
    Node *next;
5786
0
    Node *block;
5787
5788
0
    while (node)
5789
0
    {
5790
0
        next = node->next;
5791
5792
0
        if (node->content)
5793
0
            EncloseBlockText(doc, node->content);
5794
5795
0
        if (!(nodeIsFORM(node) || nodeIsNOSCRIPT(node) ||
5796
0
              nodeIsBLOCKQUOTE(node))
5797
0
            || !node->content)
5798
0
        {
5799
0
            node = next;
5800
0
            continue;
5801
0
        }
5802
5803
0
        block = node->content;
5804
5805
0
        if ((TY_(nodeIsText)(block) && !TY_(IsBlank)(doc->lexer, block)) ||
5806
0
            (TY_(nodeIsElement)(block) && nodeCMIsOnlyInline(block)))
5807
0
        {
5808
0
            Node* p = TY_(InferredTag)(doc, TidyTag_P);
5809
0
            TY_(InsertNodeBeforeElement)(block, p);
5810
0
            while (block &&
5811
0
                   (!TY_(nodeIsElement)(block) || nodeCMIsOnlyInline(block)))
5812
0
            {
5813
0
                Node* tempNext = block->next;
5814
0
                TY_(RemoveNode)(block);
5815
0
                TY_(InsertNodeAtEnd)(p, block);
5816
0
                block = tempNext;
5817
0
            }
5818
0
            TrimSpaces(doc, p);
5819
0
            continue;
5820
0
        }
5821
5822
0
        node = next;
5823
0
    }
5824
0
}
5825
5826
5827
/**
5828
 *  Encloses all naked body text within `p` tags.
5829
 */
5830
static void EncloseBodyText(TidyDocImpl* doc)
5831
0
{
5832
0
    Node* node;
5833
0
    Node* body = TY_(FindBody)(doc);
5834
5835
0
    if (!body)
5836
0
        return;
5837
5838
0
    node = body->content;
5839
5840
0
    while (node)
5841
0
    {
5842
0
        if ((TY_(nodeIsText)(node) && !TY_(IsBlank)(doc->lexer, node)) ||
5843
0
            (TY_(nodeIsElement)(node) && nodeCMIsOnlyInline(node)))
5844
0
        {
5845
0
            Node* p = TY_(InferredTag)(doc, TidyTag_P);
5846
0
            TY_(InsertNodeBeforeElement)(node, p);
5847
0
            while (node && (!TY_(nodeIsElement)(node) || nodeCMIsOnlyInline(node)))
5848
0
            {
5849
0
                Node* next = node->next;
5850
0
                TY_(RemoveNode)(node);
5851
0
                TY_(InsertNodeAtEnd)(p, node);
5852
0
                node = next;
5853
0
            }
5854
0
            TrimSpaces(doc, p);
5855
0
            continue;
5856
0
        }
5857
0
        node = node->next;
5858
0
    }
5859
0
}
5860
5861
5862
/**
5863
 *  Replaces elements that are obsolete with appropriate substitute tags.
5864
 */
5865
static void ReplaceObsoleteElements(TidyDocImpl* doc, Node* node)
5866
0
{
5867
0
    Node *next;
5868
5869
0
    while (node)
5870
0
    {
5871
0
        next = node->next;
5872
5873
        /* if (nodeIsDIR(node) || nodeIsMENU(node)) */
5874
        /* HTML5 - <menu ... > is no longer obsolete */
5875
0
        if (nodeIsDIR(node))
5876
0
            TY_(CoerceNode)(doc, node, TidyTag_UL, yes, yes);
5877
5878
0
        if (nodeIsXMP(node) || nodeIsLISTING(node) ||
5879
0
            (node->tag && node->tag->id == TidyTag_PLAINTEXT))
5880
0
            TY_(CoerceNode)(doc, node, TidyTag_PRE, yes, yes);
5881
5882
0
        if (node->content)
5883
0
            ReplaceObsoleteElements(doc, node->content);
5884
5885
0
        node = next;
5886
0
    }
5887
0
}
5888
5889
5890
/***************************************************************************//*
5891
 ** MARK: - Internal API Implementation
5892
 ***************************************************************************/
5893
5894
5895
/** MARK: TY_(CheckNodeIntegrity)
5896
 *  Is used to perform a node integrity check after parsing an HTML or XML
5897
 *  document.
5898
 *  @note Actual performance of this check can be disabled by defining the
5899
 *  macro NO_NODE_INTEGRITY_CHECK.
5900
 */
5901
Bool TY_(CheckNodeIntegrity)(Node *node)
5902
0
{
5903
0
#ifndef NO_NODE_INTEGRITY_CHECK
5904
0
    Node *child;
5905
5906
0
    if (node->prev)
5907
0
    {
5908
0
        if (node->prev->next != node)
5909
0
            return no;
5910
0
    }
5911
5912
0
    if (node->next)
5913
0
    {
5914
0
        if (node->next == node || node->next->prev != node)
5915
0
            return no;
5916
0
    }
5917
5918
0
    if (node->parent)
5919
0
    {
5920
0
        if (node->prev == NULL && node->parent->content != node)
5921
0
            return no;
5922
5923
0
        if (node->next == NULL && node->parent->last != node)
5924
0
            return no;
5925
0
    }
5926
5927
0
    for (child = node->content; child; child = child->next)
5928
0
        if ( child->parent != node || !TY_(CheckNodeIntegrity)(child) )
5929
0
            return no;
5930
5931
0
#endif
5932
0
    return yes;
5933
0
}
5934
5935
5936
/** MARK: TY_(CoerceNode)
5937
 *  Transforms a given node to another element, for example, from a <p>
5938
 *  to a <br>.
5939
 */
5940
void TY_(CoerceNode)(TidyDocImpl* doc, Node *node, TidyTagId tid, Bool obsolete, Bool unexpected)
5941
0
{
5942
0
    const Dict* tag = TY_(LookupTagDef)(tid);
5943
0
    Node* tmp = TY_(InferredTag)(doc, tag->id);
5944
5945
0
    if (obsolete)
5946
0
        TY_(Report)(doc, node, tmp, OBSOLETE_ELEMENT);
5947
0
    else if (unexpected)
5948
0
        TY_(Report)(doc, node, tmp, REPLACING_UNEX_ELEMENT);
5949
0
    else
5950
0
        TY_(Report)(doc, node, tmp, REPLACING_ELEMENT);
5951
5952
0
    TidyDocFree(doc, tmp->element);
5953
0
    TidyDocFree(doc, tmp);
5954
5955
0
    node->was = node->tag;
5956
0
    node->tag = tag;
5957
0
    node->type = StartTag;
5958
0
    node->implicit = yes;
5959
0
    TidyDocFree(doc, node->element);
5960
0
    node->element = TY_(tmbstrdup)(doc->allocator, tag->name);
5961
0
}
5962
5963
5964
/** MARK: TY_(DiscardElement)
5965
 *  Remove node from markup tree and discard it.
5966
 */
5967
Node *TY_(DiscardElement)( TidyDocImpl* doc, Node *element )
5968
0
{
5969
0
    Node *next = NULL;
5970
5971
0
    if (element)
5972
0
    {
5973
0
        next = element->next;
5974
0
        TY_(RemoveNode)(element);
5975
0
        TY_(FreeNode)( doc, element);
5976
0
    }
5977
5978
0
    return next;
5979
0
}
5980
5981
5982
/** MARK: TY_(DropEmptyElements)
5983
 *  Trims a tree of empty elements recursively, returning the next node.
5984
 */
5985
Node* TY_(DropEmptyElements)(TidyDocImpl* doc, Node* node)
5986
0
{
5987
0
    Node* next;
5988
5989
0
    while (node)
5990
0
    {
5991
0
        next = node->next;
5992
5993
0
        if (node->content)
5994
0
            TY_(DropEmptyElements)(doc, node->content);
5995
5996
0
        if (!TY_(nodeIsElement)(node) &&
5997
0
            !(TY_(nodeIsText)(node) && !(node->start < node->end)))
5998
0
        {
5999
0
            node = next;
6000
0
            continue;
6001
0
        }
6002
6003
0
        next = TY_(TrimEmptyElement)(doc, node);
6004
0
        node = next;
6005
0
    }
6006
6007
0
    return node;
6008
0
}
6009
6010
6011
/** MARK: TY_(InsertNodeAtStart)
6012
 *  Insert node into markup tree as the first element of content of element.
6013
 */
6014
void TY_(InsertNodeAtStart)(Node *element, Node *node)
6015
0
{
6016
0
    node->parent = element;
6017
6018
0
    if (element->content == NULL)
6019
0
        element->last = node;
6020
0
    else
6021
0
        element->content->prev = node;
6022
6023
0
    node->next = element->content;
6024
0
    node->prev = NULL;
6025
0
    element->content = node;
6026
0
}
6027
6028
6029
/** MARK: TY_(InsertNodeAtEnd)
6030
 *  Insert node into markup tree as the last element of content of element.
6031
 */
6032
void TY_(InsertNodeAtEnd)(Node *element, Node *node)
6033
0
{
6034
0
    node->parent = element;
6035
0
    node->prev = element ? element->last : NULL;
6036
6037
0
    if (element && element->last != NULL)
6038
0
        element->last->next = node;
6039
0
    else
6040
0
        if (element)
6041
0
            element->content = node;
6042
6043
0
    if (element)
6044
0
        element->last = node;
6045
0
}
6046
6047
6048
/** MARK: TY_(InsertNodeBeforeElement)
6049
 *  Insert node into markup tree before element.
6050
 */
6051
void TY_(InsertNodeBeforeElement)(Node *element, Node *node)
6052
0
{
6053
0
    Node *parent;
6054
6055
0
    parent = element ? element->parent : NULL;
6056
0
    node->parent = parent;
6057
0
    node->next = element;
6058
0
    node->prev = element ? element->prev : NULL;
6059
0
    if (element)
6060
0
        element->prev = node;
6061
6062
0
    if (node->prev)
6063
0
        node->prev->next = node;
6064
6065
0
    if (parent && parent->content == element)
6066
0
        parent->content = node;
6067
0
}
6068
6069
6070
/** MARK: TY_(InsertNodeAfterElement)
6071
 *  Insert node into markup tree after element.
6072
 */
6073
void TY_(InsertNodeAfterElement)(Node *element, Node *node)
6074
0
{
6075
0
    Node *parent;
6076
6077
0
    parent = element->parent;
6078
0
    node->parent = parent;
6079
6080
    /* AQ - 13 Jan 2000 fix for parent == NULL */
6081
0
    if (parent != NULL && parent->last == element)
6082
0
        parent->last = node;
6083
0
    else
6084
0
    {
6085
0
        node->next = element->next;
6086
        /* AQ - 13 Jan 2000 fix for node->next == NULL */
6087
0
        if (node->next != NULL)
6088
0
            node->next->prev = node;
6089
0
    }
6090
6091
0
    element->next = node;
6092
0
    node->prev = element;
6093
0
}
6094
6095
6096
/** MARK: TY_(IsBlank)
6097
 *  Indicates whether or not a text node is blank, meaning that it consists
6098
 *  of nothing, or a single space.
6099
 */
6100
Bool TY_(IsBlank)(Lexer *lexer, Node *node)
6101
0
{
6102
0
    Bool isBlank = TY_(nodeIsText)(node);
6103
0
    if ( isBlank )
6104
0
        isBlank = ( node->end == node->start ||       /* Zero length */
6105
0
                   ( node->end == node->start+1      /* or one blank. */
6106
0
                    && lexer->lexbuf[node->start] == ' ' ) );
6107
    
6108
0
    return isBlank;
6109
0
}
6110
6111
6112
/** MARK: TY_(IsJavaScript)
6113
 *  Indicates whether or not a node is declared as containing javascript
6114
 *  code.
6115
 */
6116
Bool TY_(IsJavaScript)(Node *node)
6117
0
{
6118
0
    Bool result = no;
6119
0
    AttVal *attr;
6120
6121
0
    if (node->attributes == NULL)
6122
0
        return yes;
6123
6124
0
    for (attr = node->attributes; attr; attr = attr->next)
6125
0
    {
6126
0
        if ( (attrIsLANGUAGE(attr) || attrIsTYPE(attr))
6127
0
             && AttrContains(attr, "javascript") )
6128
0
        {
6129
0
            result = yes;
6130
0
            break;
6131
0
        }
6132
0
    }
6133
6134
0
    return result;
6135
0
}
6136
6137
6138
/** MARK: TY_(IsNewNode)
6139
 *  Used to check if a node uses CM_NEW, which determines how attributes
6140
 *  without values should be printed. This was introduced to deal with
6141
 *  user-defined tags e.g. ColdFusion.
6142
 */
6143
Bool TY_(IsNewNode)(Node *node)
6144
0
{
6145
0
    if (node && node->tag)
6146
0
    {
6147
0
        return (node->tag->model & CM_NEW);
6148
0
    }
6149
0
    return yes;
6150
0
}
6151
6152
6153
/** MARK: TY_(RemoveNode)
6154
 *  Extract a node and its children from a markup tree
6155
 */
6156
Node *TY_(RemoveNode)(Node *node)
6157
0
{
6158
0
    if (node->prev)
6159
0
        node->prev->next = node->next;
6160
6161
0
    if (node->next)
6162
0
        node->next->prev = node->prev;
6163
6164
0
    if (node->parent)
6165
0
    {
6166
0
        if (node->parent->content == node)
6167
0
            node->parent->content = node->next;
6168
6169
0
        if (node->parent->last == node)
6170
0
            node->parent->last = node->prev;
6171
0
    }
6172
6173
0
    node->parent = node->prev = node->next = NULL;
6174
0
    return node;
6175
0
}
6176
6177
6178
/** MARK: TY_(TrimEmptyElement)
6179
 *  Trims a single, empty element, returning the next node.
6180
 */
6181
Node *TY_(TrimEmptyElement)( TidyDocImpl* doc, Node *element )
6182
0
{
6183
0
    if ( CanPrune(doc, element) )
6184
0
    {
6185
0
        if (element->type != TextNode)
6186
0
        {
6187
0
            doc->footnotes |= FN_TRIM_EMPTY_ELEMENT;
6188
0
            TY_(Report)(doc, element, NULL, TRIM_EMPTY_ELEMENT);
6189
0
        }
6190
6191
0
        return TY_(DiscardElement)(doc, element);
6192
0
    }
6193
0
    return element->next;
6194
0
}
6195
6196
6197
/** MARK: TY_(XMLPreserveWhiteSpace)
6198
 *  Indicates whether or not whitespace is to be preserved in XHTML/XML
6199
 *  documents.
6200
 */
6201
Bool TY_(XMLPreserveWhiteSpace)( TidyDocImpl* doc, Node *element)
6202
0
{
6203
0
    AttVal *attribute;
6204
6205
    /* search attributes for xml:space */
6206
0
    for (attribute = element->attributes; attribute; attribute = attribute->next)
6207
0
    {
6208
0
        if (attrIsXML_SPACE(attribute))
6209
0
        {
6210
0
            if (AttrValueIs(attribute, "preserve"))
6211
0
                return yes;
6212
6213
0
            return no;
6214
0
        }
6215
0
    }
6216
6217
0
    if (element->element == NULL)
6218
0
        return no;
6219
        
6220
    /* kludge for html docs without explicit xml:space attribute */
6221
0
    if (nodeIsPRE(element)    ||
6222
0
        nodeIsSCRIPT(element) ||
6223
0
        nodeIsSTYLE(element)  ||
6224
0
        TY_(FindParser)(doc, element) == TY_(ParsePre))
6225
0
        return yes;
6226
6227
    /* kludge for XSL docs */
6228
0
    if ( TY_(tmbstrcasecmp)(element->element, "xsl:text") == 0 )
6229
0
        return yes;
6230
6231
0
    return no;
6232
0
}
6233
6234
6235
/***************************************************************************//*
6236
 ** MARK: - Internal API Implementation - Main Parsers
6237
 ***************************************************************************/
6238
6239
6240
/** MARK: TY_(ParseDocument)
6241
 *  Parses an HTML document after lexing. It begins by properly configuring
6242
 *  the overall HTML structure, and subsequently processes all remaining
6243
 *  nodes.
6244
 */
6245
void TY_(ParseDocument)(TidyDocImpl* doc)
6246
0
{
6247
0
    Node *node, *html, *doctype = NULL;
6248
6249
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
6250
0
    {
6251
0
        if (node->type == XmlDecl)
6252
0
        {
6253
0
            doc->xmlDetected = yes;
6254
6255
0
            if (TY_(FindXmlDecl)(doc) && doc->root.content)
6256
0
            {
6257
0
                TY_(Report)(doc, &doc->root, node, DISCARDING_UNEXPECTED);
6258
0
                TY_(FreeNode)(doc, node);
6259
0
                continue;
6260
0
            }
6261
0
            if (node->line > 1 || node->column != 1)
6262
0
            {
6263
0
                TY_(Report)(doc, &doc->root, node, SPACE_PRECEDING_XMLDECL);
6264
0
            }
6265
0
        }
6266
6267
        /* deal with comments etc. */
6268
0
        if (InsertMisc( &doc->root, node ))
6269
0
            continue;
6270
6271
0
        if (node->type == DocTypeTag)
6272
0
        {
6273
0
            if (doctype == NULL)
6274
0
            {
6275
0
                TY_(InsertNodeAtEnd)( &doc->root, node);
6276
0
                doctype = node;
6277
0
            }
6278
0
            else
6279
0
            {
6280
0
                TY_(Report)(doc, &doc->root, node, DISCARDING_UNEXPECTED);
6281
0
                TY_(FreeNode)( doc, node);
6282
0
            }
6283
0
            continue;
6284
0
        }
6285
6286
0
        if (node->type == EndTag)
6287
0
        {
6288
0
            TY_(Report)(doc, &doc->root, node, DISCARDING_UNEXPECTED);
6289
0
            TY_(FreeNode)( doc, node);
6290
0
            continue;
6291
0
        }
6292
6293
0
        if (node->type == StartTag && nodeIsHTML(node))
6294
0
        {
6295
0
            AttVal *xmlns = TY_(AttrGetById)(node, TidyAttr_XMLNS);
6296
6297
0
            if (AttrValueIs(xmlns, XHTML_NAMESPACE))
6298
0
            {
6299
0
                Bool htmlOut = cfgBool( doc, TidyHtmlOut );
6300
0
                doc->lexer->isvoyager = yes;                  /* Unless plain HTML */
6301
0
                TY_(SetOptionBool)( doc, TidyXhtmlOut, !htmlOut ); /* is specified, output*/
6302
0
                TY_(SetOptionBool)( doc, TidyXmlOut, !htmlOut );   /* will be XHTML. */
6303
6304
                /* adjust other config options, just as in config.c */
6305
0
                if ( !htmlOut )
6306
0
                {
6307
0
                    TY_(SetOptionBool)( doc, TidyUpperCaseTags, no );
6308
0
                    TY_(SetOptionInt)( doc, TidyUpperCaseAttrs, no );
6309
0
                }
6310
0
            }
6311
0
        }
6312
6313
0
        if ( node->type != StartTag || !nodeIsHTML(node) )
6314
0
        {
6315
0
            TY_(UngetToken)( doc );
6316
0
            html = TY_(InferredTag)(doc, TidyTag_HTML);
6317
0
        }
6318
0
        else
6319
0
            html = node;
6320
6321
        /*\
6322
         *  #72, avoid MISSING_DOCTYPE if show-body-only.
6323
         *  #191, also if --doctype omit, that is TidyDoctypeOmit
6324
         *  #342, adjust tags to html4-- if not 'auto' or 'html5'
6325
        \*/
6326
0
        if (!TY_(FindDocType)(doc))
6327
0
        {
6328
0
            ulong dtmode = cfg( doc, TidyDoctypeMode );
6329
0
            if ((dtmode != TidyDoctypeOmit) && !showingBodyOnly(doc))
6330
0
                TY_(Report)(doc, NULL, NULL, MISSING_DOCTYPE);
6331
0
            if ((dtmode != TidyDoctypeAuto) && (dtmode != TidyDoctypeHtml5))
6332
0
            {
6333
                /*\
6334
                 *  Issue #342 - if not doctype 'auto', or 'html5'
6335
                 *  then reset mode htm4-- parsing
6336
                \*/
6337
0
                TY_(AdjustTags)(doc); /* Dynamically modify the tags table to html4-- mode */
6338
0
            }
6339
0
        }
6340
0
        TY_(InsertNodeAtEnd)( &doc->root, html);
6341
0
        ParseHTMLWithNode( doc, html );
6342
0
        break;
6343
0
    }
6344
6345
    /* do this before any more document fixes */
6346
0
    if ( cfg( doc, TidyAccessibilityCheckLevel ) > 0 )
6347
0
        TY_(AccessibilityChecks)( doc );
6348
6349
0
    if (!TY_(FindHTML)(doc))
6350
0
    {
6351
        /* a later check should complain if <body> is empty */
6352
0
        html = TY_(InferredTag)(doc, TidyTag_HTML);
6353
0
        TY_(InsertNodeAtEnd)( &doc->root, html);
6354
0
        ParseHTMLWithNode( doc, html );
6355
0
    }
6356
6357
0
    node = TY_(FindTITLE)(doc);
6358
0
    if (!node)
6359
0
    {
6360
0
        Node* head = TY_(FindHEAD)(doc);
6361
        /* #72, avoid MISSING_TITLE_ELEMENT if show-body-only (but allow InsertNodeAtEnd to avoid new warning) */
6362
0
        if (!showingBodyOnly(doc))
6363
0
        {
6364
0
            TY_(Report)(doc, head, NULL, MISSING_TITLE_ELEMENT);
6365
0
        }
6366
0
        TY_(InsertNodeAtEnd)(head, TY_(InferredTag)(doc, TidyTag_TITLE));
6367
0
    }
6368
0
    else if (!node->content && !showingBodyOnly(doc))
6369
0
    {
6370
        /* Is #839 - warn node is blank in HTML5 */
6371
0
        if (TY_(IsHTML5Mode)(doc))
6372
0
        {
6373
0
            TY_(Report)(doc, node, NULL, BLANK_TITLE_ELEMENT);
6374
0
        }
6375
0
    }
6376
6377
0
    AttributeChecks(doc, &doc->root);
6378
0
    ReplaceObsoleteElements(doc, &doc->root);
6379
0
    TY_(DropEmptyElements)(doc, &doc->root);
6380
0
    CleanSpaces(doc, &doc->root);
6381
6382
0
    if (cfgBool(doc, TidyEncloseBodyText))
6383
0
        EncloseBodyText(doc);
6384
0
    if (cfgBool(doc, TidyEncloseBlockText))
6385
0
        EncloseBlockText(doc, &doc->root);
6386
0
}
6387
6388
6389
/** MARK: TY_(ParseXMLDocument)
6390
 *  Parses the document using Tidy's XML parser.
6391
 */
6392
void TY_(ParseXMLDocument)(TidyDocImpl* doc)
6393
0
{
6394
0
    Node *node, *doctype = NULL;
6395
6396
0
    TY_(SetOptionBool)( doc, TidyXmlTags, yes );
6397
6398
0
    doc->xmlDetected = yes;
6399
6400
0
    while ((node = TY_(GetToken)(doc, IgnoreWhitespace)) != NULL)
6401
0
    {
6402
        /* discard unexpected end tags */
6403
0
        if (node->type == EndTag)
6404
0
        {
6405
0
            TY_(Report)(doc, NULL, node, UNEXPECTED_ENDTAG);
6406
0
            TY_(FreeNode)( doc, node);
6407
0
            continue;
6408
0
        }
6409
6410
         /* deal with comments etc. */
6411
0
        if (InsertMisc( &doc->root, node))
6412
0
            continue;
6413
6414
0
        if (node->type == DocTypeTag)
6415
0
        {
6416
0
            if (doctype == NULL)
6417
0
            {
6418
0
                TY_(InsertNodeAtEnd)( &doc->root, node);
6419
0
                doctype = node;
6420
0
            }
6421
0
            else
6422
0
            {
6423
0
                TY_(Report)(doc, &doc->root, node, DISCARDING_UNEXPECTED);
6424
0
                TY_(FreeNode)( doc, node);
6425
0
            }
6426
0
            continue;
6427
0
        }
6428
6429
0
        if (node->type == StartEndTag)
6430
0
        {
6431
0
            TY_(InsertNodeAtEnd)( &doc->root, node);
6432
0
            continue;
6433
0
        }
6434
6435
       /* if start tag then parse element's content */
6436
0
        if (node->type == StartTag)
6437
0
        {
6438
0
            TY_(InsertNodeAtEnd)( &doc->root, node );
6439
0
            ParseHTMLWithNode( doc, node );
6440
0
            continue;
6441
0
        }
6442
6443
0
        TY_(Report)(doc, &doc->root, node, DISCARDING_UNEXPECTED);
6444
0
        TY_(FreeNode)( doc, node);
6445
0
    }
6446
6447
    /* ensure presence of initial <?xml version="1.0"?> */
6448
0
    if ( cfgBool(doc, TidyXmlDecl) )
6449
0
        TY_(FixXmlDecl)( doc );
6450
0
}