OSDN Git Service

設定変更。
[wordring-tm/wordring-tm.git] / third-party / tidy-html5-master / src / lexer.c
1 /* lexer.c -- Lexer for html parser
2   
3   (c) 1998-2008 (W3C) MIT, ERCIM, Keio University
4   See tidy.h for the copyright notice.
5
6 */
7
8 /*
9   Given a file stream fp it returns a sequence of tokens.
10
11      GetToken(fp) gets the next token
12      UngetToken(fp) provides one level undo
13
14   The tags include an attribute list:
15
16     - linked list of attribute/value nodes
17     - each node has 2 NULL-terminated strings.
18     - entities are replaced in attribute values
19
20   white space is compacted if not in preformatted mode
21   If not in preformatted mode then leading white space
22   is discarded and subsequent white space sequences
23   compacted to single space characters.
24
25   If XmlTags is no then Tag names are folded to upper
26   case and attribute names to lower case.
27
28  Not yet done:
29     -   Doctype subset and marked sections
30 */
31
32 #include "tidy-int.h"
33 #include "lexer.h"
34 #include "parser.h"
35 #include "entities.h"
36 #include "streamio.h"
37 #include "message.h"
38 #include "tmbstr.h"
39 #include "clean.h"
40 #include "utf8.h"
41 #include "streamio.h"
42 #ifdef _MSC_VER
43 #include "sprtf.h"
44 #endif
45
46 #ifndef SPRTF
47 #define SPRTF printf
48 #endif
49
50 #if !defined(NDEBUG) && defined(_MSC_VER)
51 static Bool show_attrs = yes;
52 #define MX_TXT 5
53 static char buffer[MX_TXT+8]; /* NOTE extra for '...'\0 tail */
54 static tmbstr get_text_string(Lexer* lexer, Node *node)
55 {
56     uint len = node->end - node->start;
57     tmbstr cp = lexer->lexbuf + node->start;
58     tmbstr end = lexer->lexbuf + node->end;
59     uint i = 0;
60     buffer[0] = (char)0;
61     while (cp < end ) {
62         buffer[i++] = *cp;
63         cp++;
64         if (i >= MX_TXT)
65             break;
66     }
67     if (i < len) {
68         buffer[i++] = '.';
69         if (i < len) {
70             buffer[i++] = '.';
71             if (i < len) {
72                 buffer[i++] = '.';
73             }
74         }
75     }
76     buffer[i] = 0;
77     return buffer;
78 }
79 static void Show_Node( TidyDocImpl* doc, const char *msg, Node *node )
80 {
81     Lexer* lexer = doc->lexer;
82     Bool lex = ((msg[0] == 'l')&&(msg[1] == 'e')) ? yes : no;
83     int line = ( doc->lexer ? doc->lexer->lines : 0 );
84     int col  = ( doc->lexer ? doc->lexer->columns : 0 );
85     SPRTF("R=%d C=%d: ", line, col );
86     if (lexer && lexer->token && (lexer->token->type == TextNode)) {
87         if (show_attrs) {
88             uint len = node->end - node->start;
89             tmbstr cp = get_text_string( lexer, node );
90             SPRTF("Returning %s TextNode [%s]%u %s\n", msg, cp, len,
91                 lex ? "lexer" : "stream");
92         } else {
93             SPRTF("Returning %s TextNode %p... %s\n", msg, node,
94                 lex ? "lexer" : "stream");
95         }
96     } else {
97         if (show_attrs) {
98             AttVal* av;
99             tmbstr name = node->element ? node->element : "blank";
100             SPRTF("Returning %s node <%s", msg, name);
101             for (av = node->attributes; av; av = av->next) {
102                 name = av->attribute;
103                 if (name) {
104                     SPRTF(" %s",name);
105                     if (av->value) {
106                         SPRTF("=\"%s\"", av->value);
107                     }
108                 }
109             }
110             SPRTF("> %s\n", lex ? "lexer" : "stream");
111         } else {
112             SPRTF("Returning %s node %p <%s>... %s\n", msg, node,
113                 node->element ? node->element : "blank",
114                 lex ? "lexer" : "stream");
115         }
116     }
117 }
118 #define GTDBG(a,b,c) Show_Node(a,b,c)
119 #else
120 #define GTDBG(a,b,c)
121 #endif
122
123 /* Forward references
124 */
125 /* swallows closing '>' */
126 static AttVal *ParseAttrs( TidyDocImpl* doc, Bool *isempty );
127
128 static tmbstr ParseAttribute( TidyDocImpl* doc, Bool* isempty, 
129                              Node **asp, Node **php );
130
131 static tmbstr ParseValue( TidyDocImpl* doc, ctmbstr name, Bool foldCase,
132                          Bool *isempty, int *pdelim );
133
134 static Node *ParseDocTypeDecl(TidyDocImpl* doc);
135
136 static void AddAttrToList( AttVal** list, AttVal* av );
137
138 /* used to classify characters for lexical purposes */
139 #define MAP(c) ((unsigned)c < 128 ? lexmap[(unsigned)c] : 0)
140 static uint lexmap[128];
141
142 #define IsValidXMLAttrName(name) TY_(IsValidXMLID)(name)
143 #define IsValidXMLElemName(name) TY_(IsValidXMLID)(name)
144
145 static struct _doctypes
146 {
147     uint score;
148     uint vers;
149     ctmbstr name;
150     ctmbstr fpi;
151     ctmbstr si;
152 } const W3C_Doctypes[] =
153 {
154   {  2, HT20, "HTML 2.0",               "-//IETF//DTD HTML 2.0//EN",              NULL,                                                       },
155   {  2, HT20, "HTML 2.0",               "-//IETF//DTD HTML//EN",                  NULL,                                                       },
156   {  2, HT20, "HTML 2.0",               "-//W3C//DTD HTML 2.0//EN",               NULL,                                                       },
157   {  1, HT32, "HTML 3.2",               "-//W3C//DTD HTML 3.2//EN",               NULL,                                                       },
158   {  1, HT32, "HTML 3.2",               "-//W3C//DTD HTML 3.2 Final//EN",         NULL,                                                       },
159   {  1, HT32, "HTML 3.2",               "-//W3C//DTD HTML 3.2 Draft//EN",         NULL,                                                       },
160   {  6, H40S, "HTML 4.0 Strict",        "-//W3C//DTD HTML 4.0//EN",               "http://www.w3.org/TR/REC-html40/strict.dtd"                },
161   {  8, H40T, "HTML 4.0 Transitional",  "-//W3C//DTD HTML 4.0 Transitional//EN",  "http://www.w3.org/TR/REC-html40/loose.dtd"                 },
162   {  7, H40F, "HTML 4.0 Frameset",      "-//W3C//DTD HTML 4.0 Frameset//EN",      "http://www.w3.org/TR/REC-html40/frameset.dtd"              },
163   {  3, H41S, "HTML 4.01 Strict",       "-//W3C//DTD HTML 4.01//EN",              "http://www.w3.org/TR/html4/strict.dtd"                     },
164   {  5, H41T, "HTML 4.01 Transitional", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd"                      },
165   {  4, H41F, "HTML 4.01 Frameset",     "-//W3C//DTD HTML 4.01 Frameset//EN",     "http://www.w3.org/TR/html4/frameset.dtd"                   },
166   {  9, X10S, "XHTML 1.0 Strict",       "-//W3C//DTD XHTML 1.0 Strict//EN",       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"         },
167   { 11, X10T, "XHTML 1.0 Transitional", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"   },
168   { 10, X10F, "XHTML 1.0 Frameset",     "-//W3C//DTD XHTML 1.0 Frameset//EN",     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"       },
169   { 12, XH11, "XHTML 1.1",              "-//W3C//DTD XHTML 1.1//EN",              "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"              },
170   { 13, XB10, "XHTML Basic 1.0",        "-//W3C//DTD XHTML Basic 1.0//EN",        "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd"        },
171
172   { 20, HT50, "HTML5",                  NULL,                                     NULL                                                        },
173   { 21, XH50, "XHTML5",                 NULL,                                     NULL                                                        },
174
175   /* reminder to add XHTML Print 1.0 support, see http://www.w3.org/TR/xhtml-print */
176 #if 0
177   { 14, XP10, "XHTML Print 1.0",        "-//W3C//DTD XHTML-Print 1.0//EN",         "http://www.w3.org/MarkUp/DTD/xhtml-print10.dtd"           },
178   { 14, XP10, "XHTML Print 1.0",        "-//PWG//DTD XHTML-Print 1.0//EN",         "http://www.xhtml-print.org/xhtml-print/xhtml-print10.dtd" },
179 #endif
180   /* final entry */
181   {  0,    0, NULL,                     NULL,                                     NULL                                                        }
182 };
183
184 int TY_(HTMLVersion)(TidyDocImpl* doc)
185 {
186     uint i;
187     uint j = 0;
188     uint score = 0;
189     uint vers = doc->lexer->versions;
190     uint dtver = doc->lexer->doctype;
191     TidyDoctypeModes dtmode = (TidyDoctypeModes)cfg(doc, TidyDoctypeMode);
192     Bool xhtml = (cfgBool(doc, TidyXmlOut) || doc->lexer->isvoyager) &&
193                  !cfgBool(doc, TidyHtmlOut);
194     Bool html4 = dtmode == TidyDoctypeStrict || dtmode == TidyDoctypeLoose || VERS_FROM40 & dtver;
195
196     if (xhtml && dtver == VERS_UNKNOWN) return XH50;
197     if (dtver == VERS_UNKNOWN) return HT50;
198     /* Issue #167 - if NOT XHTML, and doctype is default VERS_HTML5, then return HT50 */
199     if (!xhtml && (dtver == VERS_HTML5)) return HT50;
200
201     for (i = 0; W3C_Doctypes[i].name; ++i)
202     {
203         if ((xhtml && !(VERS_XHTML & W3C_Doctypes[i].vers)) ||
204             (html4 && !(VERS_FROM40 & W3C_Doctypes[i].vers)))
205             continue;
206
207         if (vers & W3C_Doctypes[i].vers &&
208             (W3C_Doctypes[i].score < score || !score))
209         {
210             score = W3C_Doctypes[i].score;
211             j = i;
212         }
213     }
214
215     if (score)
216         return W3C_Doctypes[j].vers;
217
218     return VERS_UNKNOWN;
219 }
220
221 static ctmbstr GetFPIFromVers(uint vers)
222 {
223     uint i;
224
225     for (i = 0; W3C_Doctypes[i].name; ++i)
226         if (W3C_Doctypes[i].vers == vers)
227             return W3C_Doctypes[i].fpi;
228
229     return NULL;
230 }
231
232 static ctmbstr GetSIFromVers(uint vers)
233 {
234     uint i;
235
236     for (i = 0; W3C_Doctypes[i].name; ++i)
237         if (W3C_Doctypes[i].vers == vers)
238             return W3C_Doctypes[i].si;
239
240     return NULL;
241 }
242
243 static ctmbstr GetNameFromVers(uint vers)
244 {
245     uint i;
246
247     for (i = 0; W3C_Doctypes[i].name; ++i)
248         if (W3C_Doctypes[i].vers == vers)
249             return W3C_Doctypes[i].name;
250
251     return NULL;
252 }
253
254 static uint GetVersFromFPI(ctmbstr fpi)
255 {
256     uint i;
257
258     for (i = 0; W3C_Doctypes[i].name; ++i)
259         if (W3C_Doctypes[i].fpi != NULL && TY_(tmbstrcasecmp)(W3C_Doctypes[i].fpi, fpi) == 0)
260             return W3C_Doctypes[i].vers;
261
262     return 0;
263 }
264
265 /* everything is allowed in proprietary version of HTML */
266 /* this is handled here rather than in the tag/attr dicts */
267 void TY_(ConstrainVersion)(TidyDocImpl* doc, uint vers)
268 {
269     doc->lexer->versions &= (vers | VERS_PROPRIETARY);
270 }
271
272 Bool TY_(IsWhite)(uint c)
273 {
274     uint map = MAP(c);
275
276     return (map & white)!=0;
277 }
278
279 Bool TY_(IsNewline)(uint c)
280 {
281     uint map = MAP(c);
282     return (map & newline)!=0;
283 }
284
285 Bool TY_(IsDigit)(uint c)
286 {
287     uint map;
288
289     map = MAP(c);
290
291     return (map & digit)!=0;
292 }
293
294 static Bool IsDigitHex(uint c)
295 {
296     uint map;
297
298     map = MAP(c);
299
300     return (map & digithex)!=0;
301 }
302
303 Bool TY_(IsLetter)(uint c)
304 {
305     uint map;
306
307     map = MAP(c);
308
309     return (map & letter)!=0;
310 }
311
312 Bool TY_(IsHTMLSpace)(uint c)
313 {
314     return c == 0x020 || c == 0x009 || c == 0x00a || c == 0x00c || c == 0x00d;
315 }
316
317 Bool TY_(IsNamechar)(uint c)
318 {
319     uint map = MAP(c);
320     return (map & namechar)!=0;
321 }
322
323 Bool TY_(IsXMLLetter)(uint c)
324 {
325     return ((c >= 0x41 && c <= 0x5a) ||
326         (c >= 0x61 && c <= 0x7a) ||
327         (c >= 0xc0 && c <= 0xd6) ||
328         (c >= 0xd8 && c <= 0xf6) ||
329         (c >= 0xf8 && c <= 0xff) ||
330         (c >= 0x100 && c <= 0x131) ||
331         (c >= 0x134 && c <= 0x13e) ||
332         (c >= 0x141 && c <= 0x148) ||
333         (c >= 0x14a && c <= 0x17e) ||
334         (c >= 0x180 && c <= 0x1c3) ||
335         (c >= 0x1cd && c <= 0x1f0) ||
336         (c >= 0x1f4 && c <= 0x1f5) ||
337         (c >= 0x1fa && c <= 0x217) ||
338         (c >= 0x250 && c <= 0x2a8) ||
339         (c >= 0x2bb && c <= 0x2c1) ||
340         c == 0x386 ||
341         (c >= 0x388 && c <= 0x38a) ||
342         c == 0x38c ||
343         (c >= 0x38e && c <= 0x3a1) ||
344         (c >= 0x3a3 && c <= 0x3ce) ||
345         (c >= 0x3d0 && c <= 0x3d6) ||
346         c == 0x3da ||
347         c == 0x3dc ||
348         c == 0x3de ||
349         c == 0x3e0 ||
350         (c >= 0x3e2 && c <= 0x3f3) ||
351         (c >= 0x401 && c <= 0x40c) ||
352         (c >= 0x40e && c <= 0x44f) ||
353         (c >= 0x451 && c <= 0x45c) ||
354         (c >= 0x45e && c <= 0x481) ||
355         (c >= 0x490 && c <= 0x4c4) ||
356         (c >= 0x4c7 && c <= 0x4c8) ||
357         (c >= 0x4cb && c <= 0x4cc) ||
358         (c >= 0x4d0 && c <= 0x4eb) ||
359         (c >= 0x4ee && c <= 0x4f5) ||
360         (c >= 0x4f8 && c <= 0x4f9) ||
361         (c >= 0x531 && c <= 0x556) ||
362         c == 0x559 ||
363         (c >= 0x561 && c <= 0x586) ||
364         (c >= 0x5d0 && c <= 0x5ea) ||
365         (c >= 0x5f0 && c <= 0x5f2) ||
366         (c >= 0x621 && c <= 0x63a) ||
367         (c >= 0x641 && c <= 0x64a) ||
368         (c >= 0x671 && c <= 0x6b7) ||
369         (c >= 0x6ba && c <= 0x6be) ||
370         (c >= 0x6c0 && c <= 0x6ce) ||
371         (c >= 0x6d0 && c <= 0x6d3) ||
372         c == 0x6d5 ||
373         (c >= 0x6e5 && c <= 0x6e6) ||
374         (c >= 0x905 && c <= 0x939) ||
375         c == 0x93d ||
376         (c >= 0x958 && c <= 0x961) ||
377         (c >= 0x985 && c <= 0x98c) ||
378         (c >= 0x98f && c <= 0x990) ||
379         (c >= 0x993 && c <= 0x9a8) ||
380         (c >= 0x9aa && c <= 0x9b0) ||
381         c == 0x9b2 ||
382         (c >= 0x9b6 && c <= 0x9b9) ||
383         (c >= 0x9dc && c <= 0x9dd) ||
384         (c >= 0x9df && c <= 0x9e1) ||
385         (c >= 0x9f0 && c <= 0x9f1) ||
386         (c >= 0xa05 && c <= 0xa0a) ||
387         (c >= 0xa0f && c <= 0xa10) ||
388         (c >= 0xa13 && c <= 0xa28) ||
389         (c >= 0xa2a && c <= 0xa30) ||
390         (c >= 0xa32 && c <= 0xa33) ||
391         (c >= 0xa35 && c <= 0xa36) ||
392         (c >= 0xa38 && c <= 0xa39) ||
393         (c >= 0xa59 && c <= 0xa5c) ||
394         c == 0xa5e ||
395         (c >= 0xa72 && c <= 0xa74) ||
396         (c >= 0xa85 && c <= 0xa8b) ||
397         c == 0xa8d ||
398         (c >= 0xa8f && c <= 0xa91) ||
399         (c >= 0xa93 && c <= 0xaa8) ||
400         (c >= 0xaaa && c <= 0xab0) ||
401         (c >= 0xab2 && c <= 0xab3) ||
402         (c >= 0xab5 && c <= 0xab9) ||
403         c == 0xabd ||
404         c == 0xae0 ||
405         (c >= 0xb05 && c <= 0xb0c) ||
406         (c >= 0xb0f && c <= 0xb10) ||
407         (c >= 0xb13 && c <= 0xb28) ||
408         (c >= 0xb2a && c <= 0xb30) ||
409         (c >= 0xb32 && c <= 0xb33) ||
410         (c >= 0xb36 && c <= 0xb39) ||
411         c == 0xb3d ||
412         (c >= 0xb5c && c <= 0xb5d) ||
413         (c >= 0xb5f && c <= 0xb61) ||
414         (c >= 0xb85 && c <= 0xb8a) ||
415         (c >= 0xb8e && c <= 0xb90) ||
416         (c >= 0xb92 && c <= 0xb95) ||
417         (c >= 0xb99 && c <= 0xb9a) ||
418         c == 0xb9c ||
419         (c >= 0xb9e && c <= 0xb9f) ||
420         (c >= 0xba3 && c <= 0xba4) ||
421         (c >= 0xba8 && c <= 0xbaa) ||
422         (c >= 0xbae && c <= 0xbb5) ||
423         (c >= 0xbb7 && c <= 0xbb9) ||
424         (c >= 0xc05 && c <= 0xc0c) ||
425         (c >= 0xc0e && c <= 0xc10) ||
426         (c >= 0xc12 && c <= 0xc28) ||
427         (c >= 0xc2a && c <= 0xc33) ||
428         (c >= 0xc35 && c <= 0xc39) ||
429         (c >= 0xc60 && c <= 0xc61) ||
430         (c >= 0xc85 && c <= 0xc8c) ||
431         (c >= 0xc8e && c <= 0xc90) ||
432         (c >= 0xc92 && c <= 0xca8) ||
433         (c >= 0xcaa && c <= 0xcb3) ||
434         (c >= 0xcb5 && c <= 0xcb9) ||
435         c == 0xcde ||
436         (c >= 0xce0 && c <= 0xce1) ||
437         (c >= 0xd05 && c <= 0xd0c) ||
438         (c >= 0xd0e && c <= 0xd10) ||
439         (c >= 0xd12 && c <= 0xd28) ||
440         (c >= 0xd2a && c <= 0xd39) ||
441         (c >= 0xd60 && c <= 0xd61) ||
442         (c >= 0xe01 && c <= 0xe2e) ||
443         c == 0xe30 ||
444         (c >= 0xe32 && c <= 0xe33) ||
445         (c >= 0xe40 && c <= 0xe45) ||
446         (c >= 0xe81 && c <= 0xe82) ||
447         c == 0xe84 ||
448         (c >= 0xe87 && c <= 0xe88) ||
449         c == 0xe8a ||
450         c == 0xe8d ||
451         (c >= 0xe94 && c <= 0xe97) ||
452         (c >= 0xe99 && c <= 0xe9f) ||
453         (c >= 0xea1 && c <= 0xea3) ||
454         c == 0xea5 ||
455         c == 0xea7 ||
456         (c >= 0xeaa && c <= 0xeab) ||
457         (c >= 0xead && c <= 0xeae) ||
458         c == 0xeb0 ||
459         (c >= 0xeb2 && c <= 0xeb3) ||
460         c == 0xebd ||
461         (c >= 0xec0 && c <= 0xec4) ||
462         (c >= 0xf40 && c <= 0xf47) ||
463         (c >= 0xf49 && c <= 0xf69) ||
464         (c >= 0x10a0 && c <= 0x10c5) ||
465         (c >= 0x10d0 && c <= 0x10f6) ||
466         c == 0x1100 ||
467         (c >= 0x1102 && c <= 0x1103) ||
468         (c >= 0x1105 && c <= 0x1107) ||
469         c == 0x1109 ||
470         (c >= 0x110b && c <= 0x110c) ||
471         (c >= 0x110e && c <= 0x1112) ||
472         c == 0x113c ||
473         c == 0x113e ||
474         c == 0x1140 ||
475         c == 0x114c ||
476         c == 0x114e ||
477         c == 0x1150 ||
478         (c >= 0x1154 && c <= 0x1155) ||
479         c == 0x1159 ||
480         (c >= 0x115f && c <= 0x1161) ||
481         c == 0x1163 ||
482         c == 0x1165 ||
483         c == 0x1167 ||
484         c == 0x1169 ||
485         (c >= 0x116d && c <= 0x116e) ||
486         (c >= 0x1172 && c <= 0x1173) ||
487         c == 0x1175 ||
488         c == 0x119e ||
489         c == 0x11a8 ||
490         c == 0x11ab ||
491         (c >= 0x11ae && c <= 0x11af) ||
492         (c >= 0x11b7 && c <= 0x11b8) ||
493         c == 0x11ba ||
494         (c >= 0x11bc && c <= 0x11c2) ||
495         c == 0x11eb ||
496         c == 0x11f0 ||
497         c == 0x11f9 ||
498         (c >= 0x1e00 && c <= 0x1e9b) ||
499         (c >= 0x1ea0 && c <= 0x1ef9) ||
500         (c >= 0x1f00 && c <= 0x1f15) ||
501         (c >= 0x1f18 && c <= 0x1f1d) ||
502         (c >= 0x1f20 && c <= 0x1f45) ||
503         (c >= 0x1f48 && c <= 0x1f4d) ||
504         (c >= 0x1f50 && c <= 0x1f57) ||
505         c == 0x1f59 ||
506         c == 0x1f5b ||
507         c == 0x1f5d ||
508         (c >= 0x1f5f && c <= 0x1f7d) ||
509         (c >= 0x1f80 && c <= 0x1fb4) ||
510         (c >= 0x1fb6 && c <= 0x1fbc) ||
511         c == 0x1fbe ||
512         (c >= 0x1fc2 && c <= 0x1fc4) ||
513         (c >= 0x1fc6 && c <= 0x1fcc) ||
514         (c >= 0x1fd0 && c <= 0x1fd3) ||
515         (c >= 0x1fd6 && c <= 0x1fdb) ||
516         (c >= 0x1fe0 && c <= 0x1fec) ||
517         (c >= 0x1ff2 && c <= 0x1ff4) ||
518         (c >= 0x1ff6 && c <= 0x1ffc) ||
519         c == 0x2126 ||
520         (c >= 0x212a && c <= 0x212b) ||
521         c == 0x212e ||
522         (c >= 0x2180 && c <= 0x2182) ||
523         (c >= 0x3041 && c <= 0x3094) ||
524         (c >= 0x30a1 && c <= 0x30fa) ||
525         (c >= 0x3105 && c <= 0x312c) ||
526         (c >= 0xac00 && c <= 0xd7a3) ||
527         (c >= 0x4e00 && c <= 0x9fa5) ||
528         c == 0x3007 ||
529         (c >= 0x3021 && c <= 0x3029) ||
530         (c >= 0x4e00 && c <= 0x9fa5) ||
531         c == 0x3007 ||
532         (c >= 0x3021 && c <= 0x3029));
533 }
534
535 Bool TY_(IsXMLNamechar)(uint c)
536 {
537     return (TY_(IsXMLLetter)(c) ||
538         c == '.' || c == '_' ||
539         c == ':' || c == '-' ||
540         (c >= 0x300 && c <= 0x345) ||
541         (c >= 0x360 && c <= 0x361) ||
542         (c >= 0x483 && c <= 0x486) ||
543         (c >= 0x591 && c <= 0x5a1) ||
544         (c >= 0x5a3 && c <= 0x5b9) ||
545         (c >= 0x5bb && c <= 0x5bd) ||
546         c == 0x5bf ||
547         (c >= 0x5c1 && c <= 0x5c2) ||
548         c == 0x5c4 ||
549         (c >= 0x64b && c <= 0x652) ||
550         c == 0x670 ||
551         (c >= 0x6d6 && c <= 0x6dc) ||
552         (c >= 0x6dd && c <= 0x6df) ||
553         (c >= 0x6e0 && c <= 0x6e4) ||
554         (c >= 0x6e7 && c <= 0x6e8) ||
555         (c >= 0x6ea && c <= 0x6ed) ||
556         (c >= 0x901 && c <= 0x903) ||
557         c == 0x93c ||
558         (c >= 0x93e && c <= 0x94c) ||
559         c == 0x94d ||
560         (c >= 0x951 && c <= 0x954) ||
561         (c >= 0x962 && c <= 0x963) ||
562         (c >= 0x981 && c <= 0x983) ||
563         c == 0x9bc ||
564         c == 0x9be ||
565         c == 0x9bf ||
566         (c >= 0x9c0 && c <= 0x9c4) ||
567         (c >= 0x9c7 && c <= 0x9c8) ||
568         (c >= 0x9cb && c <= 0x9cd) ||
569         c == 0x9d7 ||
570         (c >= 0x9e2 && c <= 0x9e3) ||
571         c == 0xa02 ||
572         c == 0xa3c ||
573         c == 0xa3e ||
574         c == 0xa3f ||
575         (c >= 0xa40 && c <= 0xa42) ||
576         (c >= 0xa47 && c <= 0xa48) ||
577         (c >= 0xa4b && c <= 0xa4d) ||
578         (c >= 0xa70 && c <= 0xa71) ||
579         (c >= 0xa81 && c <= 0xa83) ||
580         c == 0xabc ||
581         (c >= 0xabe && c <= 0xac5) ||
582         (c >= 0xac7 && c <= 0xac9) ||
583         (c >= 0xacb && c <= 0xacd) ||
584         (c >= 0xb01 && c <= 0xb03) ||
585         c == 0xb3c ||
586         (c >= 0xb3e && c <= 0xb43) ||
587         (c >= 0xb47 && c <= 0xb48) ||
588         (c >= 0xb4b && c <= 0xb4d) ||
589         (c >= 0xb56 && c <= 0xb57) ||
590         (c >= 0xb82 && c <= 0xb83) ||
591         (c >= 0xbbe && c <= 0xbc2) ||
592         (c >= 0xbc6 && c <= 0xbc8) ||
593         (c >= 0xbca && c <= 0xbcd) ||
594         c == 0xbd7 ||
595         (c >= 0xc01 && c <= 0xc03) ||
596         (c >= 0xc3e && c <= 0xc44) ||
597         (c >= 0xc46 && c <= 0xc48) ||
598         (c >= 0xc4a && c <= 0xc4d) ||
599         (c >= 0xc55 && c <= 0xc56) ||
600         (c >= 0xc82 && c <= 0xc83) ||
601         (c >= 0xcbe && c <= 0xcc4) ||
602         (c >= 0xcc6 && c <= 0xcc8) ||
603         (c >= 0xcca && c <= 0xccd) ||
604         (c >= 0xcd5 && c <= 0xcd6) ||
605         (c >= 0xd02 && c <= 0xd03) ||
606         (c >= 0xd3e && c <= 0xd43) ||
607         (c >= 0xd46 && c <= 0xd48) ||
608         (c >= 0xd4a && c <= 0xd4d) ||
609         c == 0xd57 ||
610         c == 0xe31 ||
611         (c >= 0xe34 && c <= 0xe3a) ||
612         (c >= 0xe47 && c <= 0xe4e) ||
613         c == 0xeb1 ||
614         (c >= 0xeb4 && c <= 0xeb9) ||
615         (c >= 0xebb && c <= 0xebc) ||
616         (c >= 0xec8 && c <= 0xecd) ||
617         (c >= 0xf18 && c <= 0xf19) ||
618         c == 0xf35 ||
619         c == 0xf37 ||
620         c == 0xf39 ||
621         c == 0xf3e ||
622         c == 0xf3f ||
623         (c >= 0xf71 && c <= 0xf84) ||
624         (c >= 0xf86 && c <= 0xf8b) ||
625         (c >= 0xf90 && c <= 0xf95) ||
626         c == 0xf97 ||
627         (c >= 0xf99 && c <= 0xfad) ||
628         (c >= 0xfb1 && c <= 0xfb7) ||
629         c == 0xfb9 ||
630         (c >= 0x20d0 && c <= 0x20dc) ||
631         c == 0x20e1 ||
632         (c >= 0x302a && c <= 0x302f) ||
633         c == 0x3099 ||
634         c == 0x309a ||
635         (c >= 0x30 && c <= 0x39) ||
636         (c >= 0x660 && c <= 0x669) ||
637         (c >= 0x6f0 && c <= 0x6f9) ||
638         (c >= 0x966 && c <= 0x96f) ||
639         (c >= 0x9e6 && c <= 0x9ef) ||
640         (c >= 0xa66 && c <= 0xa6f) ||
641         (c >= 0xae6 && c <= 0xaef) ||
642         (c >= 0xb66 && c <= 0xb6f) ||
643         (c >= 0xbe7 && c <= 0xbef) ||
644         (c >= 0xc66 && c <= 0xc6f) ||
645         (c >= 0xce6 && c <= 0xcef) ||
646         (c >= 0xd66 && c <= 0xd6f) ||
647         (c >= 0xe50 && c <= 0xe59) ||
648         (c >= 0xed0 && c <= 0xed9) ||
649         (c >= 0xf20 && c <= 0xf29) ||
650         c == 0xb7 ||
651         c == 0x2d0 ||
652         c == 0x2d1 ||
653         c == 0x387 ||
654         c == 0x640 ||
655         c == 0xe46 ||
656         c == 0xec6 ||
657         c == 0x3005 ||
658         (c >= 0x3031 && c <= 0x3035) ||
659         (c >= 0x309d && c <= 0x309e) ||
660         (c >= 0x30fc && c <= 0x30fe));
661 }
662
663 #if 0
664 Bool IsLower(uint c)
665 {
666     uint map = MAP(c);
667
668     return (map & lowercase)!=0;
669 }
670 #endif
671
672 Bool TY_(IsUpper)(uint c)
673 {
674     uint map = MAP(c);
675
676     return (map & uppercase)!=0;
677 }
678
679 uint TY_(ToLower)(uint c)
680 {
681     uint map = MAP(c);
682
683     if (map & uppercase)
684         c += 'a' - 'A';
685
686     return c;
687 }
688
689 uint TY_(ToUpper)(uint c)
690 {
691     uint map = MAP(c);
692
693     if (map & lowercase)
694         c += (uint) ('A' - 'a' );
695
696     return c;
697 }
698
699 #if 0
700 char FoldCase( TidyDocImpl* doc, tmbchar c, Bool tocaps )
701 {
702     if ( !cfgBool(doc, TidyXmlTags) )
703     {
704         if ( tocaps )
705         {
706             c = (tmbchar) ToUpper(c);
707         }
708         else /* force to lower case */
709         {
710             c = (tmbchar) ToLower(c);
711         }
712     }
713     return c;
714 }
715 #endif
716
717 /*
718  return last character in string
719  this is useful when trailing quotemark
720  is missing on an attribute
721 */
722 static tmbchar LastChar( tmbstr str )
723 {
724     if ( str && *str )
725     {
726         int n = TY_(tmbstrlen)(str);
727         return str[n-1];
728     }
729     return 0;
730 }
731
732 /*
733    node->type is one of these:
734
735     #define TextNode    1
736     #define StartTag    2
737     #define EndTag      3
738     #define StartEndTag 4
739 */
740
741 Lexer* TY_(NewLexer)( TidyDocImpl* doc )
742 {
743     Lexer* lexer = (Lexer*) TidyDocAlloc( doc, sizeof(Lexer) );
744
745     if ( lexer != NULL )
746     {
747         TidyClearMemory( lexer, sizeof(Lexer) );
748
749         lexer->allocator = doc->allocator;
750         lexer->lines = 1;
751         lexer->columns = 1;
752         lexer->state = LEX_CONTENT;
753
754         lexer->versions = (VERS_ALL|VERS_PROPRIETARY);
755         lexer->doctype = VERS_UNKNOWN;
756         lexer->root = &doc->root;
757     }
758     return lexer;
759 }
760
761 static Bool EndOfInput( TidyDocImpl* doc )
762 {
763     assert( doc->docIn != NULL );
764     return ( !doc->docIn->pushed && TY_(IsEOF)(doc->docIn) );
765 }
766
767 void TY_(FreeLexer)( TidyDocImpl* doc )
768 {
769     Lexer *lexer = doc->lexer;
770     if ( lexer )
771     {
772         TY_(FreeStyles)( doc );
773
774         /* See GetToken() */
775         if ( lexer->pushed || lexer->itoken )
776         {
777             if (lexer->pushed)
778                 TY_(FreeNode)( doc, lexer->itoken );
779             TY_(FreeNode)( doc, lexer->token );
780         }
781
782         while ( lexer->istacksize > 0 )
783             TY_(PopInline)( doc, NULL );
784
785         TidyDocFree( doc, lexer->istack );
786         TidyDocFree( doc, lexer->lexbuf );
787         TidyDocFree( doc, lexer );
788         doc->lexer = NULL;
789     }
790 }
791
792 /* Lexer uses bigger memory chunks than pprint as
793 ** it must hold the entire input document. not just
794 ** the last line or three.
795 */
796 static void AddByte( Lexer *lexer, tmbchar ch )
797 {
798     if ( lexer->lexsize + 2 >= lexer->lexlength )
799     {
800         tmbstr buf = NULL;
801         uint allocAmt = lexer->lexlength;
802         while ( lexer->lexsize + 2 >= allocAmt )
803         {
804             if ( allocAmt == 0 )
805                 allocAmt = 8192;
806             else
807                 allocAmt *= 2;
808         }
809         buf = (tmbstr) TidyRealloc( lexer->allocator, lexer->lexbuf, allocAmt );
810         if ( buf )
811         {
812           TidyClearMemory( buf + lexer->lexlength, 
813                            allocAmt - lexer->lexlength );
814           lexer->lexbuf = buf;
815           lexer->lexlength = allocAmt;
816         }
817     }
818
819     lexer->lexbuf[ lexer->lexsize++ ] = ch;
820     lexer->lexbuf[ lexer->lexsize ]   = '\0';  /* debug */
821 }
822
823 static void ChangeChar( Lexer *lexer, tmbchar c )
824 {
825     if ( lexer->lexsize > 0 )
826     {
827         lexer->lexbuf[ lexer->lexsize-1 ] = c;
828     }
829 }
830
831 /* store character c as UTF-8 encoded byte stream */
832 void TY_(AddCharToLexer)( Lexer *lexer, uint c )
833 {
834     int i, err, count = 0;
835     tmbchar buf[10] = {0};
836     
837     err = TY_(EncodeCharToUTF8Bytes)( c, buf, NULL, &count );
838     if (err)
839     {
840 #if 0 && defined(_DEBUG)
841         fprintf( stderr, "lexer UTF-8 encoding error for U+%x : ", c );
842 #endif
843         /* replacement character 0xFFFD encoded as UTF-8 */
844         buf[0] = (byte) 0xEF;
845         buf[1] = (byte) 0xBF;
846         buf[2] = (byte) 0xBD;
847         count = 3;
848     }
849     
850     for ( i = 0; i < count; ++i )
851         AddByte( lexer, buf[i] );
852 }
853
854 static void AddStringToLexer( Lexer *lexer, ctmbstr str )
855 {
856     uint c;
857
858     /*  Many (all?) compilers will sign-extend signed chars (the default) when
859     **  converting them to unsigned integer values.  We must cast our char to
860     **  unsigned char before assigning it to prevent this from happening.
861     */
862     while( 0 != (c = (unsigned char) *str++ ))
863         TY_(AddCharToLexer)( lexer, c );
864 }
865
866
867 static void SetLexerLocus( TidyDocImpl* doc, Lexer *lexer )
868 {
869     lexer->lines = doc->docIn->curline;
870     lexer->columns = doc->docIn->curcol;
871 }
872
873 /*
874   No longer attempts to insert missing ';' for unknown
875   enitities unless one was present already, since this
876   gives unexpected results.
877
878   For example:   <a href="something.htm?foo&bar&fred">
879   was tidied to: <a href="something.htm?foo&amp;bar;&amp;fred;">
880   rather than:   <a href="something.htm?foo&amp;bar&amp;fred">
881
882   My thanks for Maurice Buxton for spotting this.
883
884   Also Randy Waki pointed out the following case for the
885   04 Aug 00 version (bug #433012):
886   
887   For example:   <a href="something.htm?id=1&lang=en">
888   was tidied to: <a href="something.htm?id=1&lang;=en">
889   rather than:   <a href="something.htm?id=1&amp;lang=en">
890   
891   where "lang" is a known entity (#9001), but browsers would
892   misinterpret "&lang;" because it had a value > 256.
893   
894   So the case of an apparently known entity with a value > 256 and
895   missing a semicolon is handled specially.
896   
897   "ParseEntity" is also a bit of a misnomer - it handles entities and
898   numeric character references. Invalid NCR's are now reported.
899 */
900 static void ParseEntity( TidyDocImpl* doc, GetTokenMode mode )
901 {
902     typedef enum
903     {
904         ENT_default,
905         ENT_numdec,
906         ENT_numhex
907     } ENTState;
908     
909     typedef Bool (*ENTfn)(uint);
910     const ENTfn entFn[] = {
911         TY_(IsNamechar),
912         TY_(IsDigit),
913         IsDigitHex
914     };
915     uint start;
916     ENTState entState = ENT_default;
917     uint charRead = 0;
918     Bool semicolon = no, found = no;
919     Bool isXml = cfgBool( doc, TidyXmlTags );
920     Bool preserveEntities = cfgBool( doc, TidyPreserveEntities );
921     uint c, ch, startcol, entver = 0;
922     Lexer* lexer = doc->lexer;
923
924     start = lexer->lexsize - 1;  /* to start at "&" */
925     startcol = doc->docIn->curcol - 1;
926
927     while ( (c = TY_(ReadChar)(doc->docIn)) != EndOfStream )
928     {
929         if ( c == ';' )
930         {
931             semicolon = yes;
932             break;
933         }
934         ++charRead;
935
936         if (charRead == 1 && c == '#')
937         {
938 #if SUPPORT_ASIAN_ENCODINGS
939             if ( !cfgBool(doc, TidyNCR) || 
940                  cfg(doc, TidyInCharEncoding) == BIG5 ||
941                  cfg(doc, TidyInCharEncoding) == SHIFTJIS )
942             {
943                 TY_(UngetChar)('#', doc->docIn);
944                 return;
945             }
946 #endif
947             TY_(AddCharToLexer)( lexer, c );
948             entState = ENT_numdec;
949             continue;
950         }
951         else if (charRead == 2 && entState == ENT_numdec
952                  && (c == 'x' || (!isXml && c == 'X')) )
953         {
954             TY_(AddCharToLexer)( lexer, c );
955             entState = ENT_numhex;
956             continue;
957         }
958
959         if ( entFn[entState](c) )
960         {
961             TY_(AddCharToLexer)( lexer, c );
962             continue;
963         }
964
965         /* otherwise put it back */
966         TY_(UngetChar)( c, doc->docIn );
967         break;
968     }
969
970     /* make sure entity is NULL terminated */
971     lexer->lexbuf[lexer->lexsize] = '\0';
972
973     /* Should contrain version to XML/XHTML if &apos; 
974     ** is encountered.  But this is not possible with
975     ** Tidy's content model bit mask.
976     */
977     if ( TY_(tmbstrcmp)(lexer->lexbuf+start, "&apos") == 0
978          && !cfgBool(doc, TidyXmlOut)
979          && !lexer->isvoyager
980          && !cfgBool(doc, TidyXhtmlOut) )
981         TY_(ReportEntityError)( doc, APOS_UNDEFINED, lexer->lexbuf+start, 39 );
982
983     if (( mode == OtherNamespace ) && ( c == ';' ))
984     {
985         /* #130 MathML attr and entity fix! */
986         found = yes;
987         ch = 255;
988         entver = XH50|HT50;
989         preserveEntities = yes;
990     }
991     else
992     {
993         /* Lookup entity code and version
994         */
995         found = TY_(EntityInfo)( lexer->lexbuf+start, isXml, &ch, &entver );
996     }
997
998     /* deal with unrecognized or invalid entities */
999     /* #433012 - fix by Randy Waki 17 Feb 01 */
1000     /* report invalid NCR's - Terry Teague 01 Sep 01 */
1001     if ( !found || (ch >= 128 && ch <= 159) || (ch >= 256 && c != ';') )
1002     {
1003         /* set error position just before offending character */
1004         SetLexerLocus( doc, lexer );
1005         lexer->columns = startcol;
1006
1007         if (lexer->lexsize > start + 1)
1008         {
1009             if (ch >= 128 && ch <= 159)
1010             {
1011                 /* invalid numeric character reference */
1012                 
1013                 uint c1 = 0;
1014                 int replaceMode = DISCARDED_CHAR;
1015             
1016                 if ( TY_(ReplacementCharEncoding) == WIN1252 )
1017                     c1 = TY_(DecodeWin1252)( ch );
1018                 else if ( TY_(ReplacementCharEncoding) == MACROMAN )
1019                     c1 = TY_(DecodeMacRoman)( ch );
1020
1021                 if ( c1 )
1022                     replaceMode = REPLACED_CHAR;
1023                 
1024                 if ( c != ';' )  /* issue warning if not terminated by ';' */
1025                     TY_(ReportEntityError)( doc, MISSING_SEMICOLON_NCR,
1026                                             lexer->lexbuf+start, c );
1027  
1028                 TY_(ReportEncodingError)(doc, INVALID_NCR, ch, replaceMode == DISCARDED_CHAR);
1029                 
1030                 if ( c1 )
1031                 {
1032                     /* make the replacement */
1033                     lexer->lexsize = start;
1034                     TY_(AddCharToLexer)( lexer, c1 );
1035                     semicolon = no;
1036                 }
1037                 else
1038                 {
1039                     /* discard */
1040                     lexer->lexsize = start;
1041                     semicolon = no;
1042                }
1043                
1044             }
1045             else
1046                 TY_(ReportEntityError)( doc, UNKNOWN_ENTITY,
1047                                         lexer->lexbuf+start, ch );
1048
1049             if (semicolon)
1050                 TY_(AddCharToLexer)( lexer, ';' );
1051         }
1052         else /* naked & */
1053             TY_(ReportEntityError)( doc, UNESCAPED_AMPERSAND,
1054                                     lexer->lexbuf+start, ch );
1055     }
1056     else
1057     {
1058         if ( c != ';' )    /* issue warning if not terminated by ';' */
1059         {
1060             /* set error position just before offending chararcter */
1061             SetLexerLocus( doc, lexer );
1062             lexer->columns = startcol;
1063             TY_(ReportEntityError)( doc, MISSING_SEMICOLON, lexer->lexbuf+start, c );
1064         }
1065
1066         if (preserveEntities)
1067             TY_(AddCharToLexer)( lexer, ';' );
1068         else
1069         {
1070             lexer->lexsize = start;
1071             if ( ch == 160 && (mode == Preformatted) )
1072                 ch = ' ';
1073             TY_(AddCharToLexer)( lexer, ch );
1074
1075             if ( ch == '&' && !cfgBool(doc, TidyQuoteAmpersand) )
1076                 AddStringToLexer( lexer, "amp;" );
1077         }
1078
1079         /* Detect extended vs. basic entities */
1080         TY_(ConstrainVersion)( doc, entver );
1081     }
1082 }
1083
1084 static tmbchar ParseTagName( TidyDocImpl* doc )
1085 {
1086     Lexer *lexer = doc->lexer;
1087     uint c = lexer->lexbuf[ lexer->txtstart ];
1088     Bool xml = cfgBool(doc, TidyXmlTags);
1089
1090     /* fold case of first character in buffer */
1091     if (!xml && TY_(IsUpper)(c))
1092         lexer->lexbuf[lexer->txtstart] = (tmbchar) TY_(ToLower)(c);
1093
1094     while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
1095     {
1096         if ((!xml && !TY_(IsNamechar)(c)) ||
1097             (xml && !TY_(IsXMLNamechar)(c)))
1098             break;
1099
1100         /* fold case of subsequent characters */
1101         if (!xml && TY_(IsUpper)(c))
1102              c = TY_(ToLower)(c);
1103
1104         TY_(AddCharToLexer)(lexer, c);
1105     }
1106
1107     lexer->txtend = lexer->lexsize;
1108     return (tmbchar) c;
1109 }
1110
1111 /*
1112   Used for elements and text nodes
1113   element name is NULL for text nodes
1114   start and end are offsets into lexbuf
1115   which contains the textual content of
1116   all elements in the parse tree.
1117
1118   parent and content allow traversal
1119   of the parse tree in any direction.
1120   attributes are represented as a linked
1121   list of AttVal nodes which hold the
1122   strings for attribute/value pairs.
1123 */
1124
1125
1126 Node *TY_(NewNode)(TidyAllocator* allocator, Lexer *lexer)
1127 {
1128     Node* node = (Node*) TidyAlloc( allocator, sizeof(Node) );
1129     TidyClearMemory( node, sizeof(Node) );
1130     if ( lexer )
1131     {
1132         node->line = lexer->lines;
1133         node->column = lexer->columns;
1134     }
1135     node->type = TextNode;
1136 #if !defined(NDEBUG) && defined(_MSC_VER) && defined(DEBUG_ALLOCATION)
1137     SPRTF("Allocated node %p\n", node );
1138 #endif
1139     return node;
1140 }
1141
1142 /* used to clone heading nodes when split by an <HR> */
1143 Node *TY_(CloneNode)( TidyDocImpl* doc, Node *element )
1144 {
1145     Lexer* lexer = doc->lexer;
1146     Node *node = TY_(NewNode)( lexer->allocator, lexer );
1147
1148     node->start = lexer->lexsize;
1149     node->end   = lexer->lexsize;
1150
1151     if ( element )
1152     {
1153         node->parent     = element->parent;
1154         node->type       = element->type;
1155         node->closed     = element->closed;
1156         node->implicit   = element->implicit;
1157         node->tag        = element->tag;
1158         node->element    = TY_(tmbstrdup)( doc->allocator, element->element );
1159         node->attributes = TY_(DupAttrs)( doc, element->attributes );
1160     }
1161     return node;
1162 }
1163
1164 /* free node's attributes */
1165 void TY_(FreeAttrs)( TidyDocImpl* doc, Node *node )
1166 {
1167     while ( node->attributes )
1168     {
1169         AttVal *av = node->attributes;
1170
1171         if ( av->attribute )
1172         {
1173             if ( (attrIsID(av) || attrIsNAME(av)) &&
1174                  TY_(IsAnchorElement)(doc, node) )
1175             {
1176                 TY_(RemoveAnchorByNode)( doc, av->value, node );
1177             }
1178         }
1179
1180         node->attributes = av->next;
1181         TY_(FreeAttribute)( doc, av );
1182     }
1183 }
1184
1185 /* doesn't repair attribute list linkage */
1186 void TY_(FreeAttribute)( TidyDocImpl* doc, AttVal *av )
1187 {
1188     TY_(FreeNode)( doc, av->asp );
1189     TY_(FreeNode)( doc, av->php );
1190     TidyDocFree( doc, av->attribute );
1191     TidyDocFree( doc, av->value );
1192     TidyDocFree( doc, av );
1193 }
1194
1195 /* detach attribute from node
1196 */
1197 void TY_(DetachAttribute)( Node *node, AttVal *attr )
1198 {
1199     AttVal *av, *prev = NULL;
1200
1201     for ( av = node->attributes; av; av = av->next )
1202     {
1203         if ( av == attr )
1204         {
1205             if ( prev )
1206                 prev->next = attr->next;
1207             else
1208                 node->attributes = attr->next;
1209             break;
1210         }
1211         prev = av;
1212     }
1213 }
1214
1215 /* detach attribute from node then free it
1216 */
1217 void TY_(RemoveAttribute)( TidyDocImpl* doc, Node *node, AttVal *attr )
1218 {
1219     TY_(DetachAttribute)( node, attr );
1220     TY_(FreeAttribute)( doc, attr );
1221 }
1222
1223 /*
1224   Free document nodes by iterating through peers and recursing
1225   through children. Set next to NULL before calling TY_(FreeNode)()
1226   to avoid freeing peer nodes. Doesn't patch up prev/next links.
1227  */
1228 void TY_(FreeNode)( TidyDocImpl* doc, Node *node )
1229 {
1230 #if !defined(NDEBUG) && defined(_MSC_VER) && defined(DEBUG_ALLOCATION)
1231     if (node) SPRTF("Free node %p\n", node );
1232 #endif
1233     /* this is no good ;=((
1234     if (node && doc && doc->lexer) {
1235         if (node == doc->lexer->token) {
1236             doc->lexer->token = NULL; // TY_(NewNode)( doc->lexer->allocator, doc->lexer );
1237         }
1238     }
1239       ----------------- */
1240     while ( node )
1241     {
1242         Node* next = node->next;
1243
1244         TY_(FreeAttrs)( doc, node );
1245         TY_(FreeNode)( doc, node->content );
1246         TidyDocFree( doc, node->element );
1247 #ifdef TIDY_STORE_ORIGINAL_TEXT
1248         if (node->otext)
1249             TidyDocFree(doc, node->otext);
1250 #endif
1251         if (RootNode != node->type)
1252             TidyDocFree( doc, node );
1253         else
1254             node->content = NULL;
1255
1256         node = next;
1257     }
1258 }
1259
1260 #ifdef TIDY_STORE_ORIGINAL_TEXT
1261 void StoreOriginalTextInToken(TidyDocImpl* doc, Node* node, uint count)
1262 {
1263     if (!doc->storeText)
1264         return;
1265
1266     if (count >= doc->docIn->otextlen)
1267         return;
1268
1269     if (!doc->docIn->otextsize)
1270         return;
1271
1272     if (count == 0)
1273     {
1274         node->otext = doc->docIn->otextbuf;
1275         doc->docIn->otextbuf = NULL;
1276         doc->docIn->otextlen = 0;
1277         doc->docIn->otextsize = 0;
1278     }
1279     else
1280     {
1281         uint len = doc->docIn->otextlen;
1282         tmbstr buf1 = (tmbstr)TidyDocAlloc(doc, len - count + 1);
1283         tmbstr buf2 = (tmbstr)TidyDocAlloc(doc, count + 1);
1284         uint i, j;
1285
1286         /* strncpy? */
1287
1288         for (i = 0; i < len - count; ++i)
1289             buf1[i] = doc->docIn->otextbuf[i];
1290
1291         buf1[i] = 0;
1292
1293         for (j = 0; j + i < len; ++j)
1294             buf2[j] = doc->docIn->otextbuf[j + i];
1295
1296         buf2[j] = 0;
1297
1298         TidyDocFree(doc, doc->docIn->otextbuf);
1299         node->otext = buf1;
1300         doc->docIn->otextbuf = buf2;
1301         doc->docIn->otextlen = count;
1302         doc->docIn->otextsize = count + 1;
1303     }
1304 }
1305 #endif
1306
1307 Node* TY_(TextToken)( Lexer *lexer )
1308 {
1309     Node *node = TY_(NewNode)( lexer->allocator, lexer );
1310     node->start = lexer->txtstart;
1311     node->end = lexer->txtend;
1312     return node;
1313 }
1314
1315 /* used for creating preformatted text from Word2000 */
1316 Node *TY_(NewLineNode)( Lexer *lexer )
1317 {
1318     Node *node = TY_(NewNode)( lexer->allocator, lexer );
1319     node->start = lexer->lexsize;
1320     TY_(AddCharToLexer)( lexer, (uint)'\n' );
1321     node->end = lexer->lexsize;
1322     return node;
1323 }
1324
1325 /* used for adding a &nbsp; for Word2000 */
1326 Node* TY_(NewLiteralTextNode)( Lexer *lexer, ctmbstr txt )
1327 {
1328     Node *node = TY_(NewNode)( lexer->allocator, lexer );
1329     node->start = lexer->lexsize;
1330     AddStringToLexer( lexer, txt );
1331     node->end = lexer->lexsize;
1332     return node;
1333 }
1334
1335 static Node* TagToken( TidyDocImpl* doc, NodeType type )
1336 {
1337     Lexer* lexer = doc->lexer;
1338     Node* node = TY_(NewNode)( lexer->allocator, lexer );
1339     node->type = type;
1340     node->element = TY_(tmbstrndup)( doc->allocator,
1341                                      lexer->lexbuf + lexer->txtstart,
1342                                      lexer->txtend - lexer->txtstart );
1343     node->start = lexer->txtstart;
1344     node->end = lexer->txtstart;
1345
1346     if ( type == StartTag || type == StartEndTag || type == EndTag )
1347         TY_(FindTag)(doc, node);
1348
1349     return node;
1350 }
1351
1352 static Node* NewToken(TidyDocImpl* doc, NodeType type)
1353 {
1354     Lexer* lexer = doc->lexer;
1355     Node* node = TY_(NewNode)(lexer->allocator, lexer);
1356     node->type = type;
1357     node->start = lexer->txtstart;
1358     node->end = lexer->txtend;
1359 #ifdef TIDY_STORE_ORIGINAL_TEXT
1360     StoreOriginalTextInToken(doc, node, 0);
1361 #endif
1362     return node;
1363 }
1364
1365 #define CommentToken(doc) NewToken(doc, CommentTag)
1366 #define DocTypeToken(doc) NewToken(doc, DocTypeTag)
1367 #define PIToken(doc)      NewToken(doc, ProcInsTag)
1368 #define AspToken(doc)     NewToken(doc, AspTag)
1369 #define JsteToken(doc)    NewToken(doc, JsteTag)
1370 #define PhpToken(doc)     NewToken(doc, PhpTag)
1371 #define XmlDeclToken(doc) NewToken(doc, XmlDecl)
1372 #define SectionToken(doc) NewToken(doc, SectionTag)
1373 #define CDATAToken(doc)   NewToken(doc, CDATATag)
1374
1375 void TY_(AddStringLiteral)( Lexer* lexer, ctmbstr str )
1376 {
1377     byte c;
1378     while(0 != (c = *str++) )
1379         TY_(AddCharToLexer)( lexer, c );
1380 }
1381
1382 /*
1383 void AddStringLiteralLen( Lexer* lexer, ctmbstr str, int len )
1384 {
1385     byte c;
1386     int ix;
1387
1388     for ( ix=0; ix < len && (c = *str++); ++ix )
1389         TY_(AddCharToLexer)(lexer, c);
1390 }
1391 */
1392
1393 /* find doctype element */
1394 Node *TY_(FindDocType)( TidyDocImpl* doc )
1395 {
1396     Node* node;
1397     for ( node = (doc ? doc->root.content : NULL);
1398           node && node->type != DocTypeTag; 
1399           node = node->next )
1400         /**/;
1401     return node;
1402 }
1403
1404 /* find parent container element */
1405 Node* TY_(FindContainer)( Node* node )
1406 {
1407     for ( node = (node ? node->parent : NULL);
1408           node && TY_(nodeHasCM)(node, CM_INLINE);
1409           node = node->parent )
1410         /**/;
1411
1412     return node;
1413 }
1414
1415
1416 /* find html element */
1417 Node *TY_(FindHTML)( TidyDocImpl* doc )
1418 {
1419     Node *node;
1420     for ( node = (doc ? doc->root.content : NULL);
1421           node && !nodeIsHTML(node); 
1422           node = node->next )
1423         /**/;
1424
1425     return node;
1426 }
1427
1428 /* find XML Declaration */
1429 Node *TY_(FindXmlDecl)(TidyDocImpl* doc)
1430 {
1431     Node *node;
1432     for ( node = (doc ? doc->root.content : NULL);
1433           node && !(node->type == XmlDecl);
1434           node = node->next )
1435         /**/;
1436
1437     return node;
1438 }
1439
1440
1441 Node *TY_(FindHEAD)( TidyDocImpl* doc )
1442 {
1443     Node *node = TY_(FindHTML)( doc );
1444
1445     if ( node )
1446     {
1447         for ( node = node->content;
1448               node && !nodeIsHEAD(node); 
1449               node = node->next )
1450             /**/;
1451     }
1452
1453     return node;
1454 }
1455
1456 Node *TY_(FindTITLE)(TidyDocImpl* doc)
1457 {
1458     Node *node = TY_(FindHEAD)(doc);
1459
1460     if (node)
1461         for (node = node->content;
1462              node && !nodeIsTITLE(node);
1463              node = node->next) {}
1464
1465     return node;
1466 }
1467
1468 Node *TY_(FindBody)( TidyDocImpl* doc )
1469 {
1470     Node *node = ( doc ? doc->root.content : NULL );
1471
1472     while ( node && !nodeIsHTML(node) )
1473         node = node->next;
1474
1475     if (node == NULL)
1476         return NULL;
1477
1478     node = node->content;
1479     while ( node && !nodeIsBODY(node) && !nodeIsFRAMESET(node) )
1480         node = node->next;
1481
1482     if ( node && nodeIsFRAMESET(node) )
1483     {
1484         node = node->content;
1485         while ( node && !nodeIsNOFRAMES(node) )
1486             node = node->next;
1487
1488         if ( node )
1489         {
1490             node = node->content;
1491             while ( node && !nodeIsBODY(node) )
1492                 node = node->next;
1493         }
1494     }
1495
1496     return node;
1497 }
1498
1499 /* add meta element for Tidy */
1500 Bool TY_(AddGenerator)( TidyDocImpl* doc )
1501 {
1502     AttVal *attval;
1503     Node *node;
1504     Node *head = TY_(FindHEAD)( doc );
1505     tmbchar buf[256];
1506     
1507     if (head)
1508     {
1509 #ifdef PLATFORM_NAME
1510         TY_(tmbsnprintf)(buf, sizeof(buf), "HTML Tidy for HTML5 for "PLATFORM_NAME" version %s",
1511                          tidyLibraryVersion());
1512 #else
1513         TY_(tmbsnprintf)(buf, sizeof(buf), "HTML Tidy for HTML5 version %s", tidyLibraryVersion());
1514 #endif
1515
1516         for ( node = head->content; node; node = node->next )
1517         {
1518             if ( nodeIsMETA(node) )
1519             {
1520                 attval = TY_(AttrGetById)(node, TidyAttr_NAME);
1521
1522                 if (AttrValueIs(attval, "generator"))
1523                 {
1524                     attval = TY_(AttrGetById)(node, TidyAttr_CONTENT);
1525
1526                     if (AttrHasValue(attval) &&
1527                         TY_(tmbstrncasecmp)(attval->value, "HTML Tidy", 9) == 0)
1528                     {
1529                         /* update the existing content to reflect the */
1530                         /* actual version of Tidy currently being used */
1531                         
1532                         TidyDocFree(doc, attval->value);
1533                         attval->value = TY_(tmbstrdup)(doc->allocator, buf);
1534                         return no;
1535                     }
1536                 }
1537             }
1538         }
1539
1540         if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
1541         {
1542             node = TY_(InferredTag)(doc, TidyTag_META);
1543             TY_(AddAttribute)( doc, node, "name", "generator" );
1544             TY_(AddAttribute)( doc, node, "content", buf );
1545             TY_(InsertNodeAtStart)( head, node );
1546             return yes;
1547         }
1548     }
1549
1550     return no;
1551 }
1552
1553 /*\ examine <!DOCTYPE ...> to identify version 
1554  *  Issue #167 and #169
1555  *   If HTML5
1556  *        <!DOCTYPE html>
1557  *       <!DOCTYPE html SYSTEM "about:legacy-compat">
1558  *   else others
1559 \*/
1560 static uint FindGivenVersion( TidyDocImpl* doc, Node* doctype )
1561 {
1562     AttVal * fpi = TY_(GetAttrByName)(doctype, "PUBLIC");
1563     uint vers;
1564
1565     if (!fpi || !fpi->value) 
1566     {
1567         if (doctype->element && (TY_(tmbstrcmp)(doctype->element,"html") == 0))
1568         {
1569             return VERS_HTML5;  /* TODO: do we need to check MORE? */
1570         }
1571         /* TODO: Consider warning, error message */
1572         return VERS_UNKNOWN;
1573     }
1574     vers = GetVersFromFPI(fpi->value);
1575
1576     if (VERS_XHTML & vers)
1577     {
1578         TY_(SetOptionBool)(doc, TidyXmlOut, yes);
1579         TY_(SetOptionBool)(doc, TidyXhtmlOut, yes);
1580         doc->lexer->isvoyager = yes;
1581     }
1582
1583     /* todo: add a warning if case does not match? */
1584     TidyDocFree(doc, fpi->value);
1585     fpi->value = TY_(tmbstrdup)(doc->allocator, GetFPIFromVers(vers));
1586
1587     return vers;
1588 }
1589
1590 /* return guessed version */
1591 uint TY_(ApparentVersion)( TidyDocImpl* doc )
1592 {
1593     if ((doc->lexer->doctype == XH11 ||
1594          doc->lexer->doctype == XB10) &&
1595         (doc->lexer->versions & doc->lexer->doctype))
1596         return doc->lexer->doctype;
1597     else
1598         return TY_(HTMLVersion)(doc);
1599 }
1600
1601 ctmbstr TY_(HTMLVersionNameFromCode)( uint vers, Bool ARG_UNUSED(isXhtml) )
1602 {
1603     ctmbstr name = GetNameFromVers(vers);
1604
1605     /* this test has moved to ReportMarkupVersion() in localize.c, for localization reasons */
1606     /*
1607     if (!name)
1608         name = "HTML Proprietary";
1609      */
1610
1611     return name;
1612 }
1613
1614 Bool TY_(WarnMissingSIInEmittedDocType)( TidyDocImpl* doc )
1615 {
1616     Bool isXhtml = doc->lexer->isvoyager;
1617     Node* doctype;
1618     
1619     /* Do not warn in XHTML mode */
1620     if ( isXhtml )
1621         return no;
1622
1623     /* Do not warn if emitted doctype is proprietary */
1624     if ( TY_(HTMLVersionNameFromCode)(doc->lexer->versionEmitted, isXhtml ) == NULL )
1625         return no;
1626
1627     /* Do not warn if no SI is possible */
1628     if ( GetSIFromVers(doc->lexer->versionEmitted) == NULL )
1629         return no;
1630
1631     if ( (doctype = TY_(FindDocType)( doc )) != NULL
1632          && TY_(GetAttrByName)(doctype, "SYSTEM") == NULL )
1633         return yes;
1634
1635     return no;
1636 }
1637
1638
1639 /* Put DOCTYPE declaration between the
1640 ** <?xml version "1.0" ... ?> declaration, if any,
1641 ** and the <html> tag.  Should also work for any comments, 
1642 ** etc. that may precede the <html> tag.
1643 */
1644
1645 static Node* NewDocTypeNode( TidyDocImpl* doc )
1646 {
1647     Node* doctype = NULL;
1648     Node* html = TY_(FindHTML)( doc );
1649
1650     if ( !html )
1651         return NULL;
1652
1653     doctype = TY_(NewNode)( doc->allocator, NULL );
1654     doctype->type = DocTypeTag;
1655     TY_(InsertNodeBeforeElement)(html, doctype);
1656     return doctype;
1657 }
1658
1659 Bool TY_(SetXHTMLDocType)( TidyDocImpl* doc )
1660 {
1661     Lexer *lexer = doc->lexer;
1662     Node *doctype = TY_(FindDocType)( doc );
1663     TidyDoctypeModes dtmode = (TidyDoctypeModes)cfg(doc, TidyDoctypeMode);
1664     ctmbstr pub = "PUBLIC";
1665     ctmbstr sys = "SYSTEM";
1666
1667     lexer->versionEmitted = TY_(ApparentVersion)( doc );
1668
1669     if (dtmode == TidyDoctypeOmit)
1670     {
1671         if (doctype)
1672             TY_(DiscardElement)(doc, doctype);
1673         return yes;
1674     }
1675
1676     if (dtmode == TidyDoctypeUser && !cfgStr(doc, TidyDoctype))
1677         return no;
1678
1679     if (!doctype)
1680     {
1681         doctype = NewDocTypeNode(doc);
1682         doctype->element = TY_(tmbstrdup)(doc->allocator, "html");
1683     }
1684     else
1685     {
1686         doctype->element = TY_(tmbstrtolower)(doctype->element);
1687     }
1688
1689     switch(dtmode)
1690     {
1691     case TidyDoctypeHtml5:
1692         /* HTML5 */
1693         TY_(RepairAttrValue)(doc, doctype, pub, NULL);
1694         TY_(RepairAttrValue)(doc, doctype, sys, NULL);
1695         lexer->versionEmitted = XH50;
1696         break;
1697     case TidyDoctypeStrict:
1698         /* XHTML 1.0 Strict */
1699         TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10S));
1700         TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10S));
1701         lexer->versionEmitted = X10S;
1702         break;
1703     case TidyDoctypeLoose:
1704         /* XHTML 1.0 Transitional */
1705         TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10T));
1706         TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10T));
1707         lexer->versionEmitted = X10T;
1708         break;
1709     case TidyDoctypeUser:
1710         /* user defined document type declaration */
1711         TY_(RepairAttrValue)(doc, doctype, pub, cfgStr(doc, TidyDoctype));
1712         TY_(RepairAttrValue)(doc, doctype, sys, "");
1713         break;
1714     case TidyDoctypeAuto:
1715         if (lexer->doctype == VERS_UNKNOWN) {
1716           lexer->versionEmitted = XH50;
1717           return yes;
1718         }
1719         else if (lexer->versions & XH11 && lexer->doctype == XH11)
1720         {
1721             if (!TY_(GetAttrByName)(doctype, sys))
1722                 TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(XH11));
1723             lexer->versionEmitted = XH11;
1724             return yes;
1725         }
1726         else if (lexer->versions & XH11 && !(lexer->versions & VERS_HTML40))
1727         {
1728             TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(XH11));
1729             TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(XH11));
1730             lexer->versionEmitted = XH11;
1731         }
1732         else if (lexer->versions & XB10 && lexer->doctype == XB10)
1733         {
1734             if (!TY_(GetAttrByName)(doctype, sys))
1735                 TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(XB10));
1736             lexer->versionEmitted = XB10;
1737             return yes;
1738         }
1739         else if (lexer->versions & VERS_HTML40_STRICT)
1740         {
1741             TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10S));
1742             TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10S));
1743             lexer->versionEmitted = X10S;
1744         }
1745         else if (lexer->versions & VERS_FRAMESET)
1746         {
1747             TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10F));
1748             TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10F));
1749             lexer->versionEmitted = X10F;
1750         }
1751         else if (lexer->versions & VERS_LOOSE)
1752         {
1753             TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10T));
1754             TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10T));
1755             lexer->versionEmitted = X10T;
1756         }
1757         else
1758         {
1759             if (doctype)
1760                 TY_(DiscardElement)(doc, doctype);
1761             return no;
1762         }
1763         break;
1764     case TidyDoctypeOmit:
1765         assert(0);
1766         break;
1767     }
1768
1769     return no;
1770 }
1771
1772 /* fixup doctype if missing */
1773 Bool TY_(FixDocType)( TidyDocImpl* doc )
1774 {
1775     Lexer* lexer = doc->lexer;
1776     Node* doctype = TY_(FindDocType)( doc );
1777     uint dtmode = cfg( doc, TidyDoctypeMode );
1778     uint guessed = VERS_UNKNOWN;
1779     Bool hadSI = no;
1780
1781     /* Issue #167 - found doctype, and doctype is default VERS_HTML5, set VERS_HTML5 and return yes */
1782     if (doctype && (dtmode == TidyDoctypeAuto) &&
1783         (lexer->doctype == VERS_HTML5) )
1784     {
1785         lexer->versionEmitted = lexer->doctype;
1786         return yes;
1787     }
1788     if (dtmode == TidyDoctypeAuto &&
1789         lexer->versions & lexer->doctype &&
1790         !(VERS_XHTML & lexer->doctype && !lexer->isvoyager)
1791         && TY_(FindDocType)(doc))
1792     {
1793         lexer->versionEmitted = lexer->doctype;
1794         return yes;
1795     }
1796
1797     if (dtmode == TidyDoctypeOmit)
1798     {
1799         if (doctype)
1800             TY_(DiscardElement)( doc, doctype );
1801         lexer->versionEmitted = TY_(ApparentVersion)( doc );
1802         return yes;
1803     }
1804
1805     if (cfgBool(doc, TidyXmlOut))
1806         return yes;
1807
1808     if (doctype)
1809         hadSI = TY_(GetAttrByName)(doctype, "SYSTEM") != NULL;
1810
1811     if ((dtmode == TidyDoctypeStrict ||
1812          dtmode == TidyDoctypeLoose) && doctype)
1813     {
1814         TY_(DiscardElement)(doc, doctype);
1815         doctype = NULL;
1816     }
1817
1818     switch (dtmode)
1819     {
1820     case TidyDoctypeHtml5:
1821         guessed = HT50;
1822         break;
1823     case TidyDoctypeStrict:
1824         guessed = H41S;
1825         break;
1826     case TidyDoctypeLoose:
1827         guessed = H41T;
1828         break;
1829     case TidyDoctypeAuto:
1830         guessed = TY_(HTMLVersion)(doc);
1831         break;
1832     }
1833
1834     lexer->versionEmitted = guessed;
1835     if (guessed == VERS_UNKNOWN)
1836         return no;
1837
1838     if (doctype)
1839     {
1840         doctype->element = TY_(tmbstrtolower)(doctype->element);
1841     }
1842     else
1843     {
1844         doctype = NewDocTypeNode(doc);
1845         doctype->element = TY_(tmbstrdup)(doc->allocator, "html");
1846     }
1847
1848     TY_(RepairAttrValue)(doc, doctype, "PUBLIC", GetFPIFromVers(guessed));
1849
1850     if (hadSI)
1851         TY_(RepairAttrValue)(doc, doctype, "SYSTEM", GetSIFromVers(guessed));
1852
1853     return yes;
1854 }
1855
1856 /* ensure XML document starts with <?xml version="1.0"?> */
1857 /* add encoding attribute if not using ASCII or UTF-8 output */
1858 Bool TY_(FixXmlDecl)( TidyDocImpl* doc )
1859 {
1860     Node* xml;
1861     AttVal *version, *encoding;
1862     Lexer*lexer = doc->lexer;
1863     Node* root = &doc->root;
1864
1865     if ( root->content && root->content->type == XmlDecl )
1866     {
1867         xml = root->content;
1868     }
1869     else
1870     {
1871         xml = TY_(NewNode)(lexer->allocator, lexer);
1872         xml->type = XmlDecl;
1873         if ( root->content )
1874             TY_(InsertNodeBeforeElement)(root->content, xml);
1875         else
1876             root->content = xml;
1877     }
1878
1879     version = TY_(GetAttrByName)(xml, "version");
1880     encoding = TY_(GetAttrByName)(xml, "encoding");
1881
1882     /*
1883       We need to insert a check if declared encoding 
1884       and output encoding mismatch and fix the XML
1885       declaration accordingly!!!
1886     */
1887
1888     if ( encoding == NULL && cfg(doc, TidyOutCharEncoding) != UTF8 )
1889     {
1890         ctmbstr enc = TY_(GetEncodingNameFromTidyId)(cfg(doc, TidyOutCharEncoding));
1891         if ( enc )
1892             TY_(AddAttribute)( doc, xml, "encoding", enc );
1893     }
1894
1895     if ( version == NULL )
1896         TY_(AddAttribute)( doc, xml, "version", "1.0" );
1897     return yes;
1898 }
1899
1900 Node* TY_(InferredTag)(TidyDocImpl* doc, TidyTagId id)
1901 {
1902     Lexer *lexer = doc->lexer;
1903     Node *node = TY_(NewNode)( lexer->allocator, lexer );
1904     const Dict* dict = TY_(LookupTagDef)(id);
1905
1906     assert( dict != NULL );
1907
1908     node->type = StartTag;
1909     node->implicit = yes;
1910     node->element = TY_(tmbstrdup)(doc->allocator, dict->name);
1911     node->tag = dict;
1912     node->start = lexer->txtstart;
1913     node->end = lexer->txtend;
1914
1915     return node;
1916 }
1917
1918 static Bool ExpectsContent(Node *node)
1919 {
1920     if (node->type != StartTag)
1921         return no;
1922
1923     /* unknown element? */
1924     if (node->tag == NULL)
1925         return yes;
1926
1927     if (node->tag->model & CM_EMPTY)
1928         return no;
1929
1930     return yes;
1931 }
1932
1933 /*
1934   create a text node for the contents of
1935   a CDATA element like style or script
1936   which ends with </foo> for some foo.
1937 */
1938
1939 typedef enum
1940 {
1941     CDATA_INTERMEDIATE,
1942     CDATA_STARTTAG,
1943     CDATA_ENDTAG
1944 } CDATAState;
1945
1946 static Node *GetCDATA( TidyDocImpl* doc, Node *container )
1947 {
1948     Lexer* lexer = doc->lexer;
1949     uint start = 0;
1950     int nested = 0;
1951     CDATAState state = CDATA_INTERMEDIATE;
1952     uint i;
1953     Bool isEmpty = yes;
1954     Bool matches = no;
1955     uint c;
1956     Bool hasSrc = TY_(AttrGetById)(container, TidyAttr_SRC) != NULL;
1957
1958     SetLexerLocus( doc, lexer );
1959     lexer->waswhite = no;
1960     lexer->txtstart = lexer->txtend = lexer->lexsize;
1961
1962     /* seen start tag, look for matching end tag */
1963     while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
1964     {
1965         TY_(AddCharToLexer)(lexer, c);
1966         lexer->txtend = lexer->lexsize;
1967
1968         if (state == CDATA_INTERMEDIATE)
1969         {
1970             if (c != '<')
1971             {
1972                 if (isEmpty && !TY_(IsWhite)(c))
1973                     isEmpty = no;
1974                 continue;
1975             }
1976
1977             c = TY_(ReadChar)(doc->docIn);
1978
1979             if (TY_(IsLetter)(c))
1980             {
1981                 /* <head><script src=foo><meta name=foo content=bar>*/
1982                 if (hasSrc && isEmpty && nodeIsSCRIPT(container))
1983                 {
1984                     /* ReportError(doc, container, NULL, MISSING_ENDTAG_FOR); */
1985                     lexer->lexsize = lexer->txtstart;
1986                     TY_(UngetChar)(c, doc->docIn);
1987                     TY_(UngetChar)('<', doc->docIn);
1988                     return NULL;
1989                 }
1990                 TY_(AddCharToLexer)(lexer, c);
1991                 start = lexer->lexsize - 1;
1992                 state = CDATA_STARTTAG;
1993             }
1994             else if (c == '/')
1995             {
1996                 TY_(AddCharToLexer)(lexer, c);
1997
1998                 c = TY_(ReadChar)(doc->docIn);
1999                 
2000                 if (!TY_(IsLetter)(c))
2001                 {
2002                     TY_(UngetChar)(c, doc->docIn);
2003                     continue;
2004                 }
2005                 TY_(UngetChar)(c, doc->docIn);
2006
2007                 start = lexer->lexsize;
2008                 state = CDATA_ENDTAG;
2009             }
2010             else if (c == '\\')
2011             {
2012                 /* recognize document.write("<script><\/script>") */
2013                 TY_(AddCharToLexer)(lexer, c);
2014
2015                 c = TY_(ReadChar)(doc->docIn);
2016
2017                 if (c != '/')
2018                 {
2019                     TY_(UngetChar)(c, doc->docIn);
2020                     continue;
2021                 }
2022
2023                 TY_(AddCharToLexer)(lexer, c);
2024                 c = TY_(ReadChar)(doc->docIn);
2025                 
2026                 if (!TY_(IsLetter)(c))
2027                 {
2028                     TY_(UngetChar)(c, doc->docIn);
2029                     continue;
2030                 }
2031                 TY_(UngetChar)(c, doc->docIn);
2032
2033                 start = lexer->lexsize;
2034                 state = CDATA_ENDTAG;
2035             }
2036             else
2037             {
2038                 TY_(UngetChar)(c, doc->docIn);
2039             }
2040         }
2041         /* '<' + Letter found */
2042         else if (state == CDATA_STARTTAG)
2043         {
2044             if (TY_(IsLetter)(c))
2045                 continue;
2046
2047             matches = TY_(tmbstrncasecmp)(container->element, lexer->lexbuf + start,
2048                                           TY_(tmbstrlen)(container->element)) == 0;
2049             if (matches)
2050                 nested++;
2051
2052             state = CDATA_INTERMEDIATE;
2053         }
2054         /* '<' + '/' + Letter found */
2055         else if (state == CDATA_ENDTAG)
2056         {
2057             if (TY_(IsLetter)(c))
2058                 continue;
2059
2060             matches = TY_(tmbstrncasecmp)(container->element, lexer->lexbuf + start,
2061                                           TY_(tmbstrlen)(container->element)) == 0;
2062
2063             if (isEmpty && !matches)
2064             {
2065                 /* ReportError(doc, container, NULL, MISSING_ENDTAG_FOR); */
2066
2067                 for (i = lexer->lexsize - 1; i >= start; --i)
2068                     TY_(UngetChar)((uint)lexer->lexbuf[i], doc->docIn);
2069                 TY_(UngetChar)('/', doc->docIn);
2070                 TY_(UngetChar)('<', doc->docIn);
2071                 break;
2072             }
2073
2074             if (matches && nested-- <= 0)
2075             {
2076                 for (i = lexer->lexsize - 1; i >= start; --i)
2077                     TY_(UngetChar)((uint)lexer->lexbuf[i], doc->docIn);
2078                 TY_(UngetChar)('/', doc->docIn);
2079                 TY_(UngetChar)('<', doc->docIn);
2080                 lexer->lexsize -= (lexer->lexsize - start) + 2;
2081                 break;
2082             }
2083             else if (lexer->lexbuf[start - 2] != '\\')
2084             {
2085                 /* if the end tag is not already escaped using backslash */
2086                 SetLexerLocus( doc, lexer );
2087                 lexer->columns -= 3;
2088                 TY_(ReportError)(doc, NULL, NULL, BAD_CDATA_CONTENT);
2089
2090                 /* if javascript insert backslash before / */
2091                 if (TY_(IsJavaScript)(container))
2092                 {
2093                     for (i = lexer->lexsize; i > start-1; --i)
2094                         lexer->lexbuf[i] = lexer->lexbuf[i-1];
2095
2096                     lexer->lexbuf[start-1] = '\\';
2097                     lexer->lexsize++;
2098                 }
2099             }
2100             state = CDATA_INTERMEDIATE;
2101         }
2102     }
2103     if (isEmpty)
2104         lexer->lexsize = lexer->txtstart = lexer->txtend;
2105     else
2106         lexer->txtend = lexer->lexsize;
2107
2108     if (c == EndOfStream)
2109         TY_(ReportError)(doc, container, NULL, MISSING_ENDTAG_FOR );
2110
2111 /* this was disabled for some reason... */
2112 #if 0
2113     if (lexer->txtend > lexer->txtstart)
2114         return TextToken(lexer);
2115     else
2116         return NULL;
2117 #else
2118     return TY_(TextToken)(lexer);
2119 #endif
2120 }
2121
2122 void TY_(UngetToken)( TidyDocImpl* doc )
2123 {
2124     doc->lexer->pushed = yes;
2125 }
2126
2127 #ifdef TIDY_STORE_ORIGINAL_TEXT
2128 #define CondReturnTextNode(doc, skip) \
2129             if (lexer->txtend > lexer->txtstart) \
2130             { \
2131                 lexer->token = TY_(TextToken)(lexer); \
2132                 StoreOriginalTextInToken(doc, lexer->token, skip); \
2133                 return lexer->token; \
2134             }
2135 #else
2136 #if !defined(NDEBUG) && defined(_MSC_VER)
2137 #define CondReturnTextNode(doc, skip) \
2138             if (lexer->txtend > lexer->txtstart) { \
2139                 Node *_node = TY_(TextToken)(lexer); \
2140                 lexer->token = _node; \
2141                 GTDBG(doc,"text_node",_node); \
2142                 return _node; \
2143             }
2144
2145 #else
2146 #define CondReturnTextNode(doc, skip) \
2147             if (lexer->txtend > lexer->txtstart) \
2148             { \
2149                 lexer->token = TY_(TextToken)(lexer); \
2150                 return lexer->token; \
2151             }
2152 #endif
2153 #endif
2154
2155 /*
2156   modes for GetToken()
2157
2158   MixedContent   -- for elements which don't accept PCDATA
2159   Preformatted   -- white space preserved as is
2160   IgnoreMarkup   -- for CDATA elements such as script, style
2161 */
2162 static Node* GetTokenFromStream( TidyDocImpl* doc, GetTokenMode mode );
2163
2164 Node* TY_(GetToken)( TidyDocImpl* doc, GetTokenMode mode )
2165 {
2166     Node *node;
2167     Lexer* lexer = doc->lexer;
2168
2169     if (lexer->pushed || lexer->itoken)
2170     {
2171         /* Deal with previously returned duplicate inline token */
2172         if (lexer->itoken)
2173         {
2174             /* itoken rejected */
2175             if (lexer->pushed)
2176             {
2177                 lexer->pushed = no;
2178                 node = lexer->itoken;
2179                 GTDBG(doc,"lex-itoken", node);
2180                 return node;
2181             }
2182             /* itoken has been accepted */
2183             lexer->itoken = NULL;
2184         }
2185             
2186         /* duplicate inlines in preference to pushed text nodes when appropriate */
2187         lexer->pushed = no;
2188         if (lexer->token->type != TextNode
2189             || !(lexer->insert || lexer->inode)) {
2190             node = lexer->token;
2191             GTDBG(doc,"lex-token", node);
2192             return node;
2193         }
2194         lexer->itoken = TY_(InsertedToken)( doc );
2195         node = lexer->itoken;
2196         GTDBG(doc,"lex-inserted", node);
2197         return node;
2198     }
2199
2200     assert( !(lexer->pushed || lexer->itoken) );
2201
2202     /* at start of block elements, unclosed inline
2203        elements are inserted into the token stream */
2204     if (lexer->insert || lexer->inode) {
2205         /*\ Issue #92: could fix by the following, but instead chose not to stack these 2
2206          *  if ( !(lexer->insert && (nodeIsINS(lexer->insert) || nodeIsDEL(lexer->insert))) ) {
2207         \*/
2208         lexer->token = TY_(InsertedToken)( doc );
2209         node = lexer->token;
2210         GTDBG(doc,"lex-inserted2", node);
2211         return node;
2212     }
2213
2214     if (mode == CdataContent)
2215     {
2216         assert( lexer->parent != NULL );
2217         node = GetCDATA(doc, lexer->parent);
2218         GTDBG(doc,"lex-cdata", node);
2219         return node;
2220     }
2221
2222     return GetTokenFromStream( doc, mode );
2223 }
2224
2225 #if !defined(NDEBUG) && defined(_MSC_VER)
2226 static void check_me(char *name)
2227 {
2228     SPRTF("Have node %s\n", name);
2229 }
2230 #endif
2231
2232 static Node* GetTokenFromStream( TidyDocImpl* doc, GetTokenMode mode )
2233 {
2234     Lexer* lexer = doc->lexer;
2235     uint c, lexdump, badcomment = 0;
2236     Bool isempty = no;
2237     AttVal *attributes = NULL;
2238     Node *node;
2239
2240     /* Lexer->token must be set on return. Nullify it for safety. */
2241     lexer->token = NULL;
2242
2243     SetLexerLocus( doc, lexer );
2244     lexer->waswhite = no;
2245
2246     lexer->txtstart = lexer->txtend = lexer->lexsize;
2247
2248     while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
2249     {
2250         if (lexer->insertspace)
2251         {
2252             TY_(AddCharToLexer)(lexer, ' ');
2253             lexer->waswhite = yes;
2254             lexer->insertspace = no;
2255         }
2256
2257         if (c == 160 && (mode == Preformatted))
2258             c = ' ';
2259
2260         TY_(AddCharToLexer)(lexer, c);
2261
2262         switch (lexer->state)
2263         {
2264             case LEX_CONTENT:  /* element content */
2265
2266                 /*
2267                  Discard white space if appropriate. Its cheaper
2268                  to do this here rather than in parser methods
2269                  for elements that don't have mixed content.
2270                 */
2271                 if (TY_(IsWhite)(c) && (mode == IgnoreWhitespace) 
2272                       && lexer->lexsize == lexer->txtstart + 1)
2273                 {
2274                     --(lexer->lexsize);
2275                     lexer->waswhite = no;
2276                     SetLexerLocus( doc, lexer );
2277                     continue;
2278                 }
2279
2280                 if (c == '<')
2281                 {
2282                     lexer->state = LEX_GT;
2283                     continue;
2284                 }
2285
2286                 if (TY_(IsWhite)(c))
2287                 {
2288                     /* was previous character white? */
2289                     if (lexer->waswhite)
2290                     {
2291                         if (mode != Preformatted && mode != IgnoreMarkup)
2292                         {
2293                             --(lexer->lexsize);
2294                             SetLexerLocus( doc, lexer );
2295                         }
2296                     }
2297                     else /* prev character wasn't white */
2298                     {
2299                         lexer->waswhite = yes;
2300
2301                         if (mode != Preformatted && mode != IgnoreMarkup && c != ' ')
2302                             ChangeChar(lexer, ' ');
2303                     }
2304
2305                     continue;
2306                 }
2307                 else if (c == '&' && mode != IgnoreMarkup)
2308                     ParseEntity( doc, mode );
2309
2310                 /* this is needed to avoid trimming trailing whitespace */
2311                 if (mode == IgnoreWhitespace)
2312                     mode = MixedContent;
2313
2314                 lexer->waswhite = no;
2315                 continue;
2316
2317             case LEX_GT:  /* < */
2318
2319                 /* check for endtag */
2320                 if (c == '/')
2321                 {
2322                     if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
2323                     {
2324                         TY_(UngetChar)(c, doc->docIn);
2325                         continue;
2326                     }
2327
2328                     TY_(AddCharToLexer)(lexer, c);
2329
2330                     if (TY_(IsLetter)(c))
2331                     {
2332                         lexer->lexsize -= 3;
2333                         lexer->txtend = lexer->lexsize;
2334                         TY_(UngetChar)(c, doc->docIn);
2335                         lexer->state = LEX_ENDTAG;
2336                         lexer->lexbuf[lexer->lexsize] = '\0';  /* debug */
2337                         doc->docIn->curcol -= 2;
2338
2339                         /* if some text before the </ return it now */
2340                         if (lexer->txtend > lexer->txtstart)
2341                         {
2342                             /* trim space character before end tag */
2343                             if (mode == IgnoreWhitespace && lexer->lexbuf[lexer->lexsize - 1] == ' ')
2344                             {
2345                                 lexer->lexsize -= 1;
2346                                 lexer->txtend = lexer->lexsize;
2347                             }
2348                             lexer->token = TY_(TextToken)(lexer);
2349 #ifdef TIDY_STORE_ORIGINAL_TEXT
2350                             StoreOriginalTextInToken(doc, lexer->token, 3);
2351 #endif
2352                             node = lexer->token;
2353                             GTDBG(doc,"text", node);
2354                             return node;
2355                         }
2356
2357                         continue;       /* no text so keep going */
2358                     }
2359
2360                     /* otherwise treat as CDATA */
2361                     lexer->waswhite = no;
2362                     lexer->state = LEX_CONTENT;
2363                     continue;
2364                 }
2365
2366                 if (mode == IgnoreMarkup)
2367                 {
2368                     /* otherwise treat as CDATA */
2369                     lexer->waswhite = no;
2370                     lexer->state = LEX_CONTENT;
2371                     continue;
2372                 }
2373
2374                 /*
2375                    look out for comments, doctype or marked sections
2376                    this isn't quite right, but its getting there ...
2377                 */
2378                 if (c == '!')
2379                 {
2380                     c = TY_(ReadChar)(doc->docIn);
2381
2382                     if (c == '-')
2383                     {
2384                         c = TY_(ReadChar)(doc->docIn);
2385
2386                         if (c == '-')
2387                         {
2388                             lexer->state = LEX_COMMENT;  /* comment */
2389                             lexer->lexsize -= 2;
2390                             lexer->txtend = lexer->lexsize;
2391
2392                             CondReturnTextNode(doc, 4)
2393
2394                             lexer->txtstart = lexer->lexsize;
2395                             continue;
2396                         }
2397
2398                         TY_(ReportError)(doc, NULL, NULL, MALFORMED_COMMENT );
2399                     }
2400                     else if (c == 'd' || c == 'D')
2401                     {
2402                         /* todo: check for complete "<!DOCTYPE" not just <!D */
2403
2404                         uint skip = 0;
2405
2406                         lexer->state = LEX_DOCTYPE; /* doctype */
2407                         lexer->lexsize -= 2;
2408                         lexer->txtend = lexer->lexsize;
2409                         mode = IgnoreWhitespace;
2410
2411                         /* skip until white space or '>' */
2412
2413                         for (;;)
2414                         {
2415                             c = TY_(ReadChar)(doc->docIn);
2416                             ++skip;
2417
2418                             if (c == EndOfStream || c == '>')
2419                             {
2420                                 TY_(UngetChar)(c, doc->docIn);
2421                                 break;
2422                             }
2423
2424
2425                             if (!TY_(IsWhite)(c))
2426                                 continue;
2427
2428                             /* and skip to end of whitespace */
2429
2430                             for (;;)
2431                             {
2432                                 c = TY_(ReadChar)(doc->docIn);
2433                                 ++skip;
2434
2435                                 if (c == EndOfStream || c == '>')
2436                                 {
2437                                     TY_(UngetChar)(c, doc->docIn);
2438                                     break;
2439                                 }
2440
2441
2442                                 if (TY_(IsWhite)(c))
2443                                     continue;
2444
2445                                 TY_(UngetChar)(c, doc->docIn);
2446                                 break;
2447                             }
2448
2449                             break;
2450                         }
2451
2452                         CondReturnTextNode(doc, (skip + 3))
2453
2454                         lexer->txtstart = lexer->lexsize;
2455                         continue;
2456                     }
2457                     else if (c == '[')
2458                     {
2459                         /* Word 2000 embeds <![if ...]> ... <![endif]> sequences */
2460                         lexer->lexsize -= 2;
2461                         lexer->state = LEX_SECTION;
2462                         lexer->txtend = lexer->lexsize;
2463
2464                         CondReturnTextNode(doc, 2)
2465
2466                         lexer->txtstart = lexer->lexsize;
2467                         continue;
2468                     }
2469
2470
2471
2472                     /* else swallow characters up to and including next '>' */
2473                     while ((c = TY_(ReadChar)(doc->docIn)) != '>')
2474                     {
2475                         if (c == EndOfStream)
2476                         {
2477                             TY_(UngetChar)(c, doc->docIn);
2478                             break;
2479                         }
2480                     }
2481
2482                     lexer->lexsize -= 2;
2483                     lexer->lexbuf[lexer->lexsize] = '\0';
2484                     lexer->state = LEX_CONTENT;
2485                     continue;
2486                 }
2487
2488                 /*
2489                    processing instructions
2490                 */
2491
2492                 if (c == '?')
2493                 {
2494                     lexer->lexsize -= 2;
2495                     lexer->state = LEX_PROCINSTR;
2496                     lexer->txtend = lexer->lexsize;
2497
2498                     CondReturnTextNode(doc, 2)
2499
2500                     lexer->txtstart = lexer->lexsize;
2501                     continue;
2502                 }
2503
2504                 /* Microsoft ASP's e.g. <% ... server-code ... %> */
2505                 if (c == '%')
2506                 {
2507                     lexer->lexsize -= 2;
2508                     lexer->state = LEX_ASP;
2509                     lexer->txtend = lexer->lexsize;
2510
2511                     CondReturnTextNode(doc, 2)
2512
2513                     lexer->txtstart = lexer->lexsize;
2514                     continue;
2515                 }
2516
2517                 /* Netscapes JSTE e.g. <# ... server-code ... #> */
2518                 if (c == '#')
2519                 {
2520                     lexer->lexsize -= 2;
2521                     lexer->state = LEX_JSTE;
2522                     lexer->txtend = lexer->lexsize;
2523
2524                     CondReturnTextNode(doc, 2)
2525
2526                     lexer->txtstart = lexer->lexsize;
2527                     continue;
2528                 }
2529
2530                 /* check for start tag */
2531                 if (TY_(IsLetter)(c))
2532                 {
2533                     TY_(UngetChar)(c, doc->docIn);     /* push back letter */
2534                     TY_(UngetChar)('<', doc->docIn);
2535                     lexer->lexsize -= 2;      /* discard "<" + letter */
2536                     lexer->txtend = lexer->lexsize;
2537                     lexer->state = LEX_STARTTAG;         /* ready to read tag name */
2538
2539                     CondReturnTextNode(doc, 2)
2540
2541                     /* lexer->txtstart = lexer->lexsize; missing here? */
2542                     continue;       /* no text so keep going */
2543                 }
2544
2545                 /* fix for bug 762102 */
2546                 if (c == '&')
2547                 {
2548                     TY_(UngetChar)(c, doc->docIn);
2549                     --(lexer->lexsize);
2550                 }
2551
2552                 /* otherwise treat as CDATA */
2553                 lexer->state = LEX_CONTENT;
2554                 lexer->waswhite = no;
2555                 continue;
2556
2557             case LEX_ENDTAG:  /* </letter */
2558                 lexer->txtstart = lexer->lexsize - 1;
2559                 doc->docIn->curcol += 2;
2560                 c = ParseTagName( doc );
2561                 lexer->token = TagToken( doc, EndTag );  /* create endtag token */
2562                 lexer->lexsize = lexer->txtend = lexer->txtstart;
2563
2564                 /* skip to '>' */
2565                 while ( c != '>' && c != EndOfStream )
2566                 {
2567                     c = TY_(ReadChar)(doc->docIn);
2568                 }
2569
2570                 if (c == EndOfStream)
2571                 {
2572                     TY_(FreeNode)( doc, lexer->token );
2573                     continue;
2574                 }
2575
2576                 lexer->state = LEX_CONTENT;
2577                 lexer->waswhite = no;
2578 #ifdef TIDY_STORE_ORIGINAL_TEXT
2579                 StoreOriginalTextInToken(doc, lexer->token, 0); /* hmm... */
2580 #endif
2581                 node = lexer->token;
2582                 GTDBG(doc,"endtag", node);
2583                 return node;  /* the endtag token */
2584
2585             case LEX_STARTTAG: /* first letter of tagname */
2586                 c = TY_(ReadChar)(doc->docIn);
2587                 ChangeChar(lexer, (tmbchar)c);
2588                 lexer->txtstart = lexer->lexsize - 1; /* set txtstart to first letter */
2589                 c = ParseTagName( doc );
2590                 isempty = no;
2591                 attributes = NULL;
2592                 lexer->token = TagToken( doc, StartTag ); /* [i_a]2 'isempty' is always false, thanks to code 2 lines above */
2593
2594                 /* parse attributes, consuming closing ">" */
2595                 if (c != '>')
2596                 {
2597                     if (c == '/')
2598                         TY_(UngetChar)(c, doc->docIn);
2599
2600                     attributes = ParseAttrs( doc, &isempty );
2601                 }
2602
2603                 if (isempty)
2604                     lexer->token->type = StartEndTag;
2605
2606                 lexer->token->attributes = attributes;
2607                 lexer->lexsize = lexer->txtend = lexer->txtstart;
2608
2609                 /* swallow newline following start tag */
2610                 /* special check needed for CRLF sequence */
2611                 /* this doesn't apply to empty elements */
2612                 /* nor to preformatted content that needs escaping */
2613
2614                 if ((mode != Preformatted && ExpectsContent(lexer->token))
2615                     || nodeIsBR(lexer->token) || nodeIsHR(lexer->token))
2616                 {
2617                     c = TY_(ReadChar)(doc->docIn);
2618
2619                     if (c != '\n' && c != '\f')
2620                         TY_(UngetChar)(c, doc->docIn);
2621
2622                     lexer->waswhite = yes;  /* to swallow leading whitespace */
2623                 }
2624                 else
2625                     lexer->waswhite = no;
2626
2627                 lexer->state = LEX_CONTENT;
2628                 if (lexer->token->tag == NULL) 
2629                 {
2630                     if (mode != OtherNamespace) /* [i_a]2 only issue warning if NOT 'OtherNamespace', and tag null */
2631                         TY_(ReportFatal)( doc, NULL, lexer->token, UNKNOWN_ELEMENT );
2632                 }
2633                 else if ( !cfgBool(doc, TidyXmlTags) )
2634                 {
2635                     Node* curr = lexer->token;
2636                     TY_(ConstrainVersion)( doc, curr->tag->versions );
2637                     
2638                     if ( curr->tag->versions & VERS_PROPRIETARY )
2639                     {
2640                         if ( !cfgBool(doc, TidyMakeClean) ||
2641                              ( !nodeIsNOBR(curr) && !nodeIsWBR(curr) ) )
2642                         {
2643                             TY_(ReportError)(doc, NULL, curr, PROPRIETARY_ELEMENT );
2644
2645                             if ( nodeIsLAYER(curr) )
2646                                 doc->badLayout |= USING_LAYER;
2647                             else if ( nodeIsSPACER(curr) )
2648                                 doc->badLayout |= USING_SPACER;
2649                             else if ( nodeIsNOBR(curr) )
2650                                 doc->badLayout |= USING_NOBR;
2651                         }
2652                     }
2653
2654                     TY_(RepairDuplicateAttributes)( doc, curr, no );
2655                 } else 
2656                     TY_(RepairDuplicateAttributes)( doc, lexer->token, yes );
2657 #ifdef TIDY_STORE_ORIGINAL_TEXT
2658                 StoreOriginalTextInToken(doc, lexer->token, 0);
2659 #endif
2660                 node = lexer->token;
2661                 GTDBG(doc,"starttag", node);
2662                 return node;  /* return start tag */
2663
2664             case LEX_COMMENT:  /* seen <!-- so look for --> */
2665
2666                 if (c != '-')
2667                     continue;
2668
2669                 c = TY_(ReadChar)(doc->docIn);
2670                 TY_(AddCharToLexer)(lexer, c);
2671
2672                 if (c != '-')
2673                     continue;
2674
2675             end_comment:
2676                 c = TY_(ReadChar)(doc->docIn);
2677
2678                 if (c == '>')
2679                 {
2680                     if (badcomment)
2681                         TY_(ReportError)(doc, NULL, NULL, MALFORMED_COMMENT );
2682
2683                     /* do not store closing -- in lexbuf */
2684                     lexer->lexsize -= 2;
2685                     lexer->txtend = lexer->lexsize;
2686                     lexer->lexbuf[lexer->lexsize] = '\0';
2687                     lexer->state = LEX_CONTENT;
2688                     lexer->waswhite = no;
2689                     lexer->token = CommentToken(doc);
2690
2691                     /* now look for a line break */
2692
2693                     c = TY_(ReadChar)(doc->docIn);
2694
2695                     if (c == '\n')
2696                         lexer->token->linebreak = yes;
2697                     else
2698                         TY_(UngetChar)(c, doc->docIn);
2699
2700                     node = lexer->token;
2701                     GTDBG(doc,"comment", node);
2702                     return node;
2703                 }
2704
2705                 /* note position of first such error in the comment */
2706                 if (!badcomment)
2707                 {
2708                     SetLexerLocus( doc, lexer );
2709                     lexer->columns -= 3;
2710                 }
2711
2712                 badcomment++;
2713
2714                 if ( cfgBool(doc, TidyFixComments) )
2715                     lexer->lexbuf[lexer->lexsize - 2] = '=';
2716
2717                 /* if '-' then look for '>' to end the comment */
2718                 if (c == '-')
2719                 {
2720                     TY_(AddCharToLexer)(lexer, c);
2721                     goto end_comment;
2722                 }
2723
2724                 /* otherwise continue to look for --> */
2725                 lexer->lexbuf[lexer->lexsize - 1] = '=';
2726
2727                 /* http://tidy.sf.net/bug/1266647 */
2728                 TY_(AddCharToLexer)(lexer, c);
2729
2730                 continue; 
2731
2732             case LEX_DOCTYPE:  /* seen <!d so look for '>' munging whitespace */
2733
2734                 /* use ParseDocTypeDecl() to tokenize doctype declaration */
2735                 TY_(UngetChar)(c, doc->docIn);
2736                 lexer->lexsize -= 1;
2737                 lexer->token = ParseDocTypeDecl(doc);
2738
2739                 lexer->txtend = lexer->lexsize;
2740                 lexer->lexbuf[lexer->lexsize] = '\0';
2741                 lexer->state = LEX_CONTENT;
2742                 lexer->waswhite = no;
2743
2744                 /* make a note of the version named by the 1st doctype */
2745                 if (lexer->doctype == VERS_UNKNOWN && lexer->token && !cfgBool(doc, TidyXmlTags))
2746                 {
2747                     lexer->doctype = FindGivenVersion(doc, lexer->token);
2748                     if (lexer->doctype != VERS_HTML5)
2749                     {
2750                         /*\
2751                          *  Back to legacy HTML4 mode for -
2752                          *  Issue #167 & #169 - TidyTag_A
2753                          *  Issue #196        - TidyTag_CAPTION
2754                          *  others?
2755                         \*/ 
2756                         TY_(AdjustTags)(doc); /* Dynamically modify the tags table  */
2757                     }
2758                 }
2759                 node = lexer->token;
2760                 GTDBG(doc,"doctype", node);
2761                 return node;
2762
2763             case LEX_PROCINSTR:  /* seen <? so look for '>' */
2764                 /* check for PHP preprocessor instructions <?php ... ?> */
2765
2766                 if  (lexer->lexsize - lexer->txtstart == 3)
2767                 {
2768                     if (TY_(tmbstrncmp)(lexer->lexbuf + lexer->txtstart, "php", 3) == 0)
2769                     {
2770                         lexer->state = LEX_PHP;
2771                         continue;
2772                     }
2773                 }
2774
2775                 if  (lexer->lexsize - lexer->txtstart == 4)
2776                 {
2777                     if (TY_(tmbstrncmp)(lexer->lexbuf + lexer->txtstart, "xml", 3) == 0 &&
2778                         TY_(IsWhite)(lexer->lexbuf[lexer->txtstart + 3]))
2779                     {
2780                         lexer->state = LEX_XMLDECL;
2781                         attributes = NULL;
2782                         continue;
2783                     }
2784                 }
2785
2786                 if (cfgBool(doc, TidyXmlPIs) || lexer->isvoyager) /* insist on ?> as terminator */
2787                 {
2788                     if (c != '?')
2789                         continue;
2790
2791                     /* now look for '>' */
2792                     c = TY_(ReadChar)(doc->docIn);
2793
2794                     if (c == EndOfStream)
2795                     {
2796                         TY_(ReportError)(doc, NULL, NULL, UNEXPECTED_END_OF_FILE );
2797                         TY_(UngetChar)(c, doc->docIn);
2798                         continue;
2799                     }
2800
2801                     TY_(AddCharToLexer)(lexer, c);
2802                 }
2803
2804
2805                 if (c != '>')
2806                     continue;
2807
2808                 lexer->lexsize -= 1;
2809
2810                 if (lexer->lexsize)
2811                 {
2812                     uint i;
2813                     Bool closed;
2814
2815                     for (i = 0; i < lexer->lexsize - lexer->txtstart &&
2816                         !TY_(IsWhite)(lexer->lexbuf[i + lexer->txtstart]); ++i)
2817                         /**/;
2818
2819                     closed = lexer->lexbuf[lexer->lexsize - 1] == '?';
2820
2821                     if (closed)
2822                         lexer->lexsize -= 1;
2823
2824                     lexer->txtstart += i;
2825                     lexer->txtend = lexer->lexsize;
2826                     lexer->lexbuf[lexer->lexsize] = '\0';
2827
2828                     lexer->token = PIToken(doc);
2829                     lexer->token->closed = closed;
2830                     lexer->token->element = TY_(tmbstrndup)(doc->allocator,
2831                                                             lexer->lexbuf +
2832                                                             lexer->txtstart - i, i);
2833                 }
2834                 else
2835                 {
2836                     lexer->txtend = lexer->lexsize;
2837                     lexer->lexbuf[lexer->lexsize] = '\0';
2838                     lexer->token = PIToken(doc);
2839                 }
2840
2841                 lexer->state = LEX_CONTENT;
2842                 lexer->waswhite = no;
2843                 node = lexer->token;
2844                 GTDBG(doc,"procinstr", node);
2845                 return node;
2846
2847             case LEX_ASP:  /* seen <% so look for "%>" */
2848                 if (c != '%')
2849                     continue;
2850
2851                 /* now look for '>' */
2852                 c = TY_(ReadChar)(doc->docIn);
2853
2854
2855                 if (c != '>')
2856                 {
2857                     TY_(UngetChar)(c, doc->docIn);
2858                     continue;
2859                 }
2860
2861                 lexer->lexsize -= 1;
2862                 lexer->txtend = lexer->lexsize;
2863                 lexer->lexbuf[lexer->lexsize] = '\0';
2864                 lexer->state = LEX_CONTENT;
2865                 lexer->waswhite = no;
2866                 lexer->token = AspToken(doc);
2867                 node = lexer->token;
2868                 GTDBG(doc,"ASP", node);
2869                 return node;  /* the endtag token */
2870
2871
2872
2873             case LEX_JSTE:  /* seen <# so look for "#>" */
2874                 if (c != '#')
2875                     continue;
2876
2877                 /* now look for '>' */
2878                 c = TY_(ReadChar)(doc->docIn);
2879
2880
2881                 if (c != '>')
2882                 {
2883                     TY_(UngetChar)(c, doc->docIn);
2884                     continue;
2885                 }
2886
2887                 lexer->lexsize -= 1;
2888                 lexer->txtend = lexer->lexsize;
2889                 lexer->lexbuf[lexer->lexsize] = '\0';
2890                 lexer->state = LEX_CONTENT;
2891                 lexer->waswhite = no;
2892                 lexer->token = JsteToken(doc);
2893                 node = lexer->token;
2894                 GTDBG(doc,"JSTE", node);
2895                 return node;  /* the JSTE token */
2896
2897
2898             case LEX_PHP: /* seen "<?php" so look for "?>" */
2899                 if (c != '?')
2900                     continue;
2901
2902                 /* now look for '>' */
2903                 c = TY_(ReadChar)(doc->docIn);
2904
2905                 if (c != '>')
2906                 {
2907                     TY_(UngetChar)(c, doc->docIn);
2908                     continue;
2909                 }
2910
2911                 lexer->lexsize -= 1;
2912                 lexer->txtend = lexer->lexsize;
2913                 lexer->lexbuf[lexer->lexsize] = '\0';
2914                 lexer->state = LEX_CONTENT;
2915                 lexer->waswhite = no;
2916                 lexer->token = PhpToken(doc);
2917                 node = lexer->token;
2918                 GTDBG(doc,"PHP", node);
2919                 return node;  /* the PHP token */
2920
2921             case LEX_XMLDECL: /* seen "<?xml" so look for "?>" */
2922
2923                 if (TY_(IsWhite)(c) && c != '?')
2924                     continue;
2925
2926                 /* get pseudo-attribute */
2927                 if (c != '?')
2928                 {
2929                     tmbstr name;
2930                     Node *asp, *php;
2931                     AttVal *av = NULL;
2932                     int pdelim = 0;
2933                     isempty = no;
2934
2935                     TY_(UngetChar)(c, doc->docIn);
2936
2937                     name = ParseAttribute( doc, &isempty, &asp, &php );
2938
2939                     if (!name)
2940                     {
2941                         /* fix for http://tidy.sf.net/bug/788031 */
2942                         lexer->lexsize -= 1;
2943                         lexer->txtend = lexer->txtstart;
2944                         lexer->lexbuf[lexer->txtend] = '\0';
2945                         lexer->state = LEX_CONTENT;
2946                         lexer->waswhite = no;
2947                         lexer->token = XmlDeclToken(doc);
2948                         lexer->token->attributes = attributes;
2949                         node = lexer->token;
2950                         GTDBG(doc,"xml", node);
2951                         return node;  /* the xml token */
2952                     }
2953
2954                     av = TY_(NewAttribute)(doc);
2955                     av->attribute = name;
2956                     av->value = ParseValue( doc, name, yes, &isempty, &pdelim );
2957                     av->delim = pdelim;
2958                     av->dict = TY_(FindAttribute)( doc, av );
2959
2960                     AddAttrToList( &attributes, av );
2961                     /* continue; */
2962                 }
2963
2964                 /* now look for '>' */
2965                 c = TY_(ReadChar)(doc->docIn);
2966
2967                 if (c != '>')
2968                 {
2969                     TY_(UngetChar)(c, doc->docIn);
2970                     continue;
2971                 }
2972                 lexer->lexsize -= 1;
2973                 lexer->txtend = lexer->txtstart;
2974                 lexer->lexbuf[lexer->txtend] = '\0';
2975                 lexer->state = LEX_CONTENT;
2976                 lexer->waswhite = no;
2977                 lexer->token = XmlDeclToken(doc);
2978                 lexer->token->attributes = attributes;
2979                 node = lexer->token;
2980                 GTDBG(doc,"XML", node);
2981                 return node;  /* the XML token */
2982
2983             case LEX_SECTION: /* seen "<![" so look for "]>" */
2984                 if (c == '[')
2985                 {
2986                     if (lexer->lexsize == (lexer->txtstart + 6) &&
2987                         TY_(tmbstrncmp)(lexer->lexbuf+lexer->txtstart, "CDATA[", 6) == 0)
2988                     {
2989                         lexer->state = LEX_CDATA;
2990                         lexer->lexsize -= 6;
2991                         continue;
2992                     }
2993                 }
2994
2995                 if (c != ']')
2996                     continue;
2997
2998                 /* now look for '>' */
2999                 c = TY_(ReadChar)(doc->docIn);
3000
3001                 lexdump = 1;
3002                 if (c != '>')
3003                 {
3004                     /* Issue #153 - can also be ]'-->' */
3005                     if (c == '-') 
3006                     {
3007                         c = TY_(ReadChar)(doc->docIn);
3008                         if (c == '-')
3009                         {
3010                             c = TY_(ReadChar)(doc->docIn);
3011                             if (c != '>')
3012                             {
3013                                 TY_(UngetChar)(c, doc->docIn);
3014                                 TY_(UngetChar)('-', doc->docIn);
3015                                 TY_(UngetChar)('-', doc->docIn);
3016                                 continue;
3017                             }
3018                             /* this failed!
3019                                TY_(AddCharToLexer)(lexer, '-'); TY_(AddCharToLexer)(lexer, '-'); lexdump = 0; 
3020                                got output <![endif]--]> - needs furhter fix in pprint section output 
3021                              */
3022                         }
3023                         else
3024                         {
3025                             TY_(UngetChar)(c, doc->docIn);
3026                             TY_(UngetChar)('-', doc->docIn);
3027                             continue;
3028                         }
3029                     } 
3030                     else 
3031                     {
3032                         TY_(UngetChar)(c, doc->docIn);
3033                         continue;
3034                     }
3035                 }
3036  
3037                 lexer->lexsize -= lexdump;
3038                 lexer->txtend = lexer->lexsize;
3039                 lexer->lexbuf[lexer->lexsize] = '\0';
3040                 lexer->state = LEX_CONTENT;
3041                 lexer->waswhite = no;
3042                 lexer->token = SectionToken(doc);
3043                 node = lexer->token;
3044                 GTDBG(doc,"SECTION", node);
3045                 return node;  /* the SECTION token */
3046
3047             case LEX_CDATA: /* seen "<![CDATA[" so look for "]]>" */
3048                 if (c != ']')
3049                     continue;
3050
3051                 /* now look for ']' */
3052                 c = TY_(ReadChar)(doc->docIn);
3053
3054                 if (c != ']')
3055                 {
3056                     TY_(UngetChar)(c, doc->docIn);
3057                     continue;
3058                 }
3059
3060                 /* now look for '>' */
3061                 c = TY_(ReadChar)(doc->docIn);
3062
3063                 if (c != '>')
3064                 {
3065                     TY_(UngetChar)(c, doc->docIn);
3066                     TY_(UngetChar)(']', doc->docIn);
3067                     continue;
3068                 }
3069
3070                 lexer->lexsize -= 1;
3071                 lexer->txtend = lexer->lexsize;
3072                 lexer->lexbuf[lexer->lexsize] = '\0';
3073                 lexer->state = LEX_CONTENT;
3074                 lexer->waswhite = no;
3075                 lexer->token = CDATAToken(doc);
3076                 node = lexer->token;
3077                 GTDBG(doc,"CDATA", node);
3078                 return node;  /* the CDATA token */
3079         }
3080     }
3081
3082     if (lexer->state == LEX_CONTENT)  /* text string */
3083     {
3084         lexer->txtend = lexer->lexsize;
3085
3086         if (lexer->txtend > lexer->txtstart)
3087         {
3088             TY_(UngetChar)(c, doc->docIn);
3089
3090             if (lexer->lexbuf[lexer->lexsize - 1] == ' ')
3091             {
3092                 lexer->lexsize -= 1;
3093                 lexer->txtend = lexer->lexsize;
3094             }
3095             lexer->token = TY_(TextToken)(lexer);
3096 #ifdef TIDY_STORE_ORIGINAL_TEXT
3097             StoreOriginalTextInToken(doc, lexer->token, 0); /* ? */
3098 #endif
3099             node = lexer->token;
3100             GTDBG(doc,"textstring", node);
3101             return node;  /* the textstring token */
3102         }
3103     }
3104     else if (lexer->state == LEX_COMMENT) /* comment */
3105     {
3106         if (c == EndOfStream)
3107             TY_(ReportError)(doc, NULL, NULL, MALFORMED_COMMENT );
3108
3109         lexer->txtend = lexer->lexsize;
3110         lexer->lexbuf[lexer->lexsize] = '\0';
3111         lexer->state = LEX_CONTENT;
3112         lexer->waswhite = no;
3113         lexer->token = CommentToken(doc);
3114         node = lexer->token;
3115         GTDBG(doc,"COMMENT", node);
3116         return node;  /* the COMMENT token */
3117     }
3118
3119 #if !defined(NDEBUG) && defined(_MSC_VER)
3120     SPRTF("Returning NULL...\n");
3121 #endif
3122     return NULL;
3123 }
3124
3125 static void MapStr( ctmbstr str, uint code )
3126 {
3127     while ( *str )
3128     {
3129         uint i = (byte) *str++;
3130         lexmap[i] |= code;
3131     }
3132 }
3133
3134 void TY_(InitMap)(void)
3135 {
3136     MapStr("\r\n\f", newline|white);
3137     MapStr(" \t", white);
3138     MapStr("-.:_", namechar);
3139     MapStr("0123456789", digit|digithex|namechar);
3140     MapStr("abcdefghijklmnopqrstuvwxyz", lowercase|letter|namechar);
3141     MapStr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", uppercase|letter|namechar);
3142     MapStr("abcdefABCDEF", digithex);
3143 }
3144
3145 /*
3146  parser for ASP within start tags
3147
3148  Some people use ASP for to customize attributes
3149  Tidy isn't really well suited to dealing with ASP
3150  This is a workaround for attributes, but won't
3151  deal with the case where the ASP is used to tailor
3152  the attribute value. Here is an example of a work
3153  around for using ASP in attribute values:
3154
3155   href='<%=rsSchool.Fields("ID").Value%>'
3156
3157  where the ASP that generates the attribute value
3158  is masked from Tidy by the quotemarks.
3159
3160 */
3161
3162 static Node *ParseAsp( TidyDocImpl* doc )
3163 {
3164     Lexer* lexer = doc->lexer;
3165     uint c;
3166     Node *asp = NULL;
3167
3168     lexer->txtstart = lexer->lexsize;
3169
3170     for (;;)
3171     {
3172         if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3173             break;
3174
3175         TY_(AddCharToLexer)(lexer, c);
3176
3177
3178         if (c != '%')
3179             continue;
3180
3181         if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3182             break;
3183
3184         TY_(AddCharToLexer)(lexer, c);
3185
3186         if (c == '>')
3187         {
3188             lexer->lexsize -= 2;
3189             break;
3190         }
3191     }
3192
3193     lexer->txtend = lexer->lexsize;
3194     if (lexer->txtend > lexer->txtstart)
3195         asp = AspToken(doc);
3196
3197     lexer->txtstart = lexer->txtend;
3198     return asp;
3199 }   
3200  
3201
3202 /*
3203  PHP is like ASP but is based upon XML
3204  processing instructions, e.g. <?php ... ?>
3205 */
3206 static Node *ParsePhp( TidyDocImpl* doc )
3207 {
3208     Lexer* lexer = doc->lexer;
3209     uint c;
3210     Node *php = NULL;
3211
3212     lexer->txtstart = lexer->lexsize;
3213
3214     for (;;)
3215     {
3216         if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3217             break;
3218
3219         TY_(AddCharToLexer)(lexer, c);
3220
3221
3222         if (c != '?')
3223             continue;
3224
3225         if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3226             break;
3227
3228         TY_(AddCharToLexer)(lexer, c);
3229
3230         if (c == '>')
3231         {
3232             lexer->lexsize -= 2;
3233             break;
3234         }
3235     }
3236
3237     lexer->txtend = lexer->lexsize;
3238     if (lexer->txtend > lexer->txtstart)
3239         php = PhpToken(doc);
3240
3241     lexer->txtstart = lexer->txtend;
3242     return php;
3243 }   
3244
3245 /* consumes the '>' terminating start tags */
3246 static tmbstr  ParseAttribute( TidyDocImpl* doc, Bool *isempty,
3247                               Node **asp, Node **php)
3248 {
3249     Lexer* lexer = doc->lexer;
3250     int start, len = 0;
3251     tmbstr attr = NULL;
3252     uint c, lastc;
3253
3254     *asp = NULL;  /* clear asp pointer */
3255     *php = NULL;  /* clear php pointer */
3256
3257  /* skip white space before the attribute */
3258
3259     for (;;)
3260     {
3261         c = TY_(ReadChar)( doc->docIn );
3262
3263
3264         if (c == '/')
3265         {
3266             c = TY_(ReadChar)( doc->docIn );
3267
3268             if (c == '>')
3269             {
3270                 *isempty = yes;
3271                 return NULL;
3272             }
3273
3274             TY_(UngetChar)(c, doc->docIn);
3275             c = '/';
3276             break;
3277         }
3278
3279         if (c == '>')
3280             return NULL;
3281
3282         if (c =='<')
3283         {
3284             c = TY_(ReadChar)(doc->docIn);
3285
3286             if (c == '%')
3287             {
3288                 *asp = ParseAsp( doc );
3289                 return NULL;
3290             }
3291             else if (c == '?')
3292             {
3293                 *php = ParsePhp( doc );
3294                 return NULL;
3295             }
3296
3297             TY_(UngetChar)(c, doc->docIn);
3298             TY_(UngetChar)('<', doc->docIn);
3299             TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3300             return NULL;
3301         }
3302
3303         if (c == '=')
3304         {
3305             TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_EQUALSIGN );
3306             continue;
3307         }
3308
3309         if (c == '"' || c == '\'')
3310         {
3311             TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_QUOTEMARK );
3312             continue;
3313         }
3314
3315         if (c == EndOfStream)
3316         {
3317             TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3318             TY_(UngetChar)(c, doc->docIn);
3319             return NULL;
3320         }
3321
3322
3323         if (!TY_(IsWhite)(c))
3324            break;
3325     }
3326
3327     start = lexer->lexsize;
3328     lastc = c;
3329
3330     for (;;)
3331     {
3332      /* but push back '=' for parseValue() */
3333         if (c == '=' || c == '>')
3334         {
3335             TY_(UngetChar)(c, doc->docIn);
3336             break;
3337         }
3338
3339         if (c == '<' || c == EndOfStream)
3340         {
3341             TY_(UngetChar)(c, doc->docIn);
3342             break;
3343         }
3344
3345         if (lastc == '-' && (c == '"' || c == '\''))
3346         {
3347             lexer->lexsize--;
3348             --len;
3349             TY_(UngetChar)(c, doc->docIn);
3350             break;
3351         }
3352
3353         if (TY_(IsWhite)(c))
3354             break;
3355
3356         /* what should be done about non-namechar characters? */
3357         /* currently these are incorporated into the attr name */
3358
3359         if ( !cfgBool(doc, TidyXmlTags) && TY_(IsUpper)(c) )
3360             c = TY_(ToLower)(c);
3361
3362         TY_(AddCharToLexer)( lexer, c );
3363         lastc = c;
3364         c = TY_(ReadChar)(doc->docIn);
3365     }
3366
3367     /* handle attribute names with multibyte chars */
3368     len = lexer->lexsize - start;
3369     attr = (len > 0 ? TY_(tmbstrndup)(doc->allocator,
3370                                       lexer->lexbuf+start, len) : NULL);
3371     lexer->lexsize = start;
3372     return attr;
3373 }
3374
3375 /*
3376  invoked when < is seen in place of attribute value
3377  but terminates on whitespace if not ASP, PHP or Tango
3378  this routine recognizes ' and " quoted strings
3379 */
3380 static int ParseServerInstruction( TidyDocImpl* doc )
3381 {
3382     Lexer* lexer = doc->lexer;
3383     uint c;
3384     int delim = '"';
3385     Bool isrule = no;
3386
3387     c = TY_(ReadChar)(doc->docIn);
3388     TY_(AddCharToLexer)(lexer, c);
3389
3390     /* check for ASP, PHP or Tango */
3391     if (c == '%' || c == '?' || c == '@')
3392         isrule = yes;
3393
3394     for (;;)
3395     {
3396         c = TY_(ReadChar)(doc->docIn);
3397
3398         if (c == EndOfStream)
3399             break;
3400
3401         if (c == '>')
3402         {
3403             if (isrule)
3404                 TY_(AddCharToLexer)(lexer, c);
3405             else
3406                 TY_(UngetChar)(c, doc->docIn);
3407
3408             break;
3409         }
3410
3411         /* if not recognized as ASP, PHP or Tango */
3412         /* then also finish value on whitespace */
3413         if (!isrule)
3414         {
3415             if (TY_(IsWhite)(c))
3416                 break;
3417         }
3418
3419         TY_(AddCharToLexer)(lexer, c);
3420
3421         if (c == '"')
3422         {
3423             do
3424             {
3425                 c = TY_(ReadChar)(doc->docIn);
3426                 if (c == EndOfStream) /* #427840 - fix by Terry Teague 30 Jun 01 */
3427                 {
3428                     TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3429                     TY_(UngetChar)(c, doc->docIn);
3430                     return 0;
3431                 }
3432                 if (c == '>') /* #427840 - fix by Terry Teague 30 Jun 01 */
3433                 {
3434                     TY_(UngetChar)(c, doc->docIn);
3435                     TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3436                     return 0;
3437                 }
3438                 TY_(AddCharToLexer)(lexer, c);
3439             }
3440             while (c != '"');
3441             delim = '\'';
3442             continue;
3443         }
3444
3445         if (c == '\'')
3446         {
3447             do
3448             {
3449                 c = TY_(ReadChar)(doc->docIn);
3450                 if (c == EndOfStream) /* #427840 - fix by Terry Teague 30 Jun 01 */
3451                 {
3452                     TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3453                     TY_(UngetChar)(c, doc->docIn);
3454                     return 0;
3455                 }
3456                 if (c == '>') /* #427840 - fix by Terry Teague 30 Jun 01 */
3457                 {
3458                     TY_(UngetChar)(c, doc->docIn);
3459                     TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3460                     return 0;
3461                 }
3462                 TY_(AddCharToLexer)(lexer, c);
3463             }
3464             while (c != '\'');
3465         }
3466     }
3467
3468     return delim;
3469 }
3470
3471 /* values start with "=" or " = " etc. */
3472 /* doesn't consume the ">" at end of start tag */
3473
3474 static tmbstr ParseValue( TidyDocImpl* doc, ctmbstr name,
3475                           Bool foldCase, Bool *isempty, int *pdelim)
3476 {
3477     Lexer* lexer = doc->lexer;
3478     int len = 0, start;
3479     Bool seen_gt = no;
3480     Bool munge = yes;
3481     uint c, lastc, delim, quotewarning;
3482     tmbstr value;
3483
3484     delim = (tmbchar) 0;
3485     *pdelim = '"';
3486
3487     /*
3488      Henry Zrepa reports that some folk are using the
3489      embed element with script attributes where newlines
3490      are significant and must be preserved
3491     */
3492     if ( cfgBool(doc, TidyLiteralAttribs) )
3493         munge = no;
3494
3495  /* skip white space before the '=' */
3496
3497     for (;;)
3498     {
3499         c = TY_(ReadChar)(doc->docIn);
3500
3501         if (c == EndOfStream)
3502         {
3503             TY_(UngetChar)(c, doc->docIn);
3504             break;
3505         }
3506
3507         if (!TY_(IsWhite)(c))
3508            break;
3509     }
3510
3511 /*
3512   c should be '=' if there is a value
3513   other legal possibilities are white
3514   space, '/' and '>'
3515 */
3516
3517     if (c != '=' && c != '"' && c != '\'')
3518     {
3519         TY_(UngetChar)(c, doc->docIn);
3520         return NULL;
3521     }
3522
3523  /* skip white space after '=' */
3524
3525     for (;;)
3526     {
3527         c = TY_(ReadChar)(doc->docIn);
3528
3529         if (c == EndOfStream)
3530         {
3531             TY_(UngetChar)(c, doc->docIn);
3532             break;
3533         }
3534
3535         if (!TY_(IsWhite)(c))
3536            break;
3537     }
3538
3539  /* check for quote marks */
3540
3541     if (c == '"' || c == '\'')
3542         delim = c;
3543     else if (c == '<')
3544     {
3545         start = lexer->lexsize;
3546         TY_(AddCharToLexer)(lexer, c);
3547         *pdelim = ParseServerInstruction( doc );
3548         len = lexer->lexsize - start;
3549         lexer->lexsize = start;
3550         return (len > 0 ? TY_(tmbstrndup)(doc->allocator,
3551                                           lexer->lexbuf+start, len) : NULL);
3552     }
3553     else
3554         TY_(UngetChar)(c, doc->docIn);
3555
3556  /*
3557    and read the value string
3558    check for quote mark if needed
3559  */
3560
3561     quotewarning = 0;
3562     start = lexer->lexsize;
3563     c = '\0';
3564
3565     for (;;)
3566     {
3567         lastc = c;  /* track last character */
3568         c = TY_(ReadChar)(doc->docIn);
3569
3570         if (c == EndOfStream)
3571         {
3572             TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3573             TY_(UngetChar)(c, doc->docIn);
3574             break;
3575         }
3576
3577         if (delim == (tmbchar)0)
3578         {
3579             if (c == '>')
3580             {
3581                 TY_(UngetChar)(c, doc->docIn);
3582                 break;
3583             }
3584
3585             if (c == '"' || c == '\'')
3586             {
3587                 uint q = c;
3588
3589                 TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_QUOTEMARK );
3590
3591                 /* handle <input onclick=s("btn1")> and <a title=foo""">...</a> */
3592                 /* this doesn't handle <a title=foo"/> which browsers treat as  */
3593                 /* 'foo"/' nor  <a title=foo" /> which browser treat as 'foo"'  */
3594                 
3595                 c = TY_(ReadChar)(doc->docIn);
3596                 if (c == '>')
3597                 {
3598                     TY_(AddCharToLexer)(lexer, q);
3599                     TY_(UngetChar)(c, doc->docIn);
3600                     break;
3601                 }
3602                 else
3603                 {
3604                     TY_(UngetChar)(c, doc->docIn);
3605                     c = q;
3606                 }
3607             }
3608
3609             if (c == '<')
3610             {
3611                 TY_(UngetChar)(c, doc->docIn);
3612                 c = '>';
3613                 TY_(UngetChar)(c, doc->docIn);
3614                 TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3615                 break;
3616             }
3617
3618             /*
3619              For cases like <br clear=all/> need to avoid treating /> as
3620              part of the attribute value, however care is needed to avoid
3621              so treating <a href=http://www.acme.com/> in this way, which
3622              would map the <a> tag to <a href="http://www.acme.com"/>
3623             */
3624             if (c == '/')
3625             {
3626                 /* peek ahead in case of /> */
3627                 c = TY_(ReadChar)(doc->docIn);
3628
3629                 if ( c == '>' && !TY_(IsUrl)(doc, name) )
3630                 {
3631                     *isempty = yes;
3632                     TY_(UngetChar)(c, doc->docIn);
3633                     break;
3634                 }
3635
3636                 /* unget peeked character */
3637                 TY_(UngetChar)(c, doc->docIn);
3638                 c = '/';
3639             }
3640         }
3641         else  /* delim is '\'' or '"' */
3642         {
3643             if (c == delim)
3644                 break;
3645
3646             if (c == '\n' || c == '<' || c == '>')
3647                 ++quotewarning;
3648
3649             if (c == '>')
3650                 seen_gt = yes;
3651         }
3652
3653         if (c == '&')
3654         {
3655             TY_(AddCharToLexer)(lexer, c);
3656             ParseEntity( doc, IgnoreWhitespace );
3657             if (lexer->lexbuf[lexer->lexsize - 1] == '\n' && munge)
3658                 ChangeChar(lexer, ' ');
3659             continue;
3660         }
3661
3662         /*
3663          kludge for JavaScript attribute values
3664          with line continuations in string literals
3665         */
3666         if (c == '\\')
3667         {
3668             c = TY_(ReadChar)(doc->docIn);
3669
3670             if (c != '\n')
3671             {
3672                 TY_(UngetChar)(c, doc->docIn);
3673                 c = '\\';
3674             }
3675         }
3676
3677         if (TY_(IsWhite)(c))
3678         {
3679             if ( delim == 0 )
3680                 break;
3681
3682             if (munge)
3683             {
3684                 /* discard line breaks in quoted URLs */ 
3685                 /* #438650 - fix by Randy Waki */
3686                 if ( c == '\n' && TY_(IsUrl)(doc, name) )
3687                 {
3688                     /* warn that we discard this newline */
3689                     TY_(ReportAttrError)( doc, lexer->token, NULL, NEWLINE_IN_URI);
3690                     continue;
3691                 }
3692                 
3693                 c = ' ';
3694
3695                 if (lastc == ' ')
3696                 {
3697                     if (TY_(IsUrl)(doc, name) )
3698                         TY_(ReportAttrError)( doc, lexer->token, NULL, WHITE_IN_URI);
3699                     continue;
3700                 }
3701             }
3702         }
3703         else if (foldCase && TY_(IsUpper)(c))
3704             c = TY_(ToLower)(c);
3705
3706         TY_(AddCharToLexer)(lexer, c);
3707     }
3708
3709     if (quotewarning > 10 && seen_gt && munge)
3710     {
3711         /*
3712            there is almost certainly a missing trailing quote mark
3713            as we have see too many newlines, < or > characters.
3714
3715            an exception is made for Javascript attributes and the
3716            javascript URL scheme which may legitimately include < and >,
3717            and for attributes starting with "<xml " as generated by
3718            Microsoft Office.
3719         */
3720         if ( !TY_(IsScript)(doc, name) &&
3721              !(TY_(IsUrl)(doc, name) && TY_(tmbstrncmp)(lexer->lexbuf+start, "javascript:", 11) == 0) &&
3722              !(TY_(tmbstrncmp)(lexer->lexbuf+start, "<xml ", 5) == 0)
3723            )
3724             TY_(ReportFatal)( doc, NULL, NULL, SUSPECTED_MISSING_QUOTE ); 
3725     }
3726
3727     len = lexer->lexsize - start;
3728     lexer->lexsize = start;
3729
3730
3731     if (len > 0 || delim)
3732     {
3733         /* ignore leading and trailing white space for all but title, alt, value */
3734         /* and prompts attributes unless --literal-attributes is set to yes      */
3735         /* #994841 - Whitespace is removed from value attributes                 */
3736
3737         if (munge &&
3738             TY_(tmbstrcasecmp)(name, "alt") &&
3739             TY_(tmbstrcasecmp)(name, "title") &&
3740             TY_(tmbstrcasecmp)(name, "value") &&
3741             TY_(tmbstrcasecmp)(name, "prompt"))
3742         {
3743             while (TY_(IsWhite)(lexer->lexbuf[start+len-1]))
3744                 --len;
3745
3746             while (TY_(IsWhite)(lexer->lexbuf[start]) && start < len)
3747             {
3748                 ++start;
3749                 --len;
3750             }
3751         }
3752
3753         value = TY_(tmbstrndup)(doc->allocator, lexer->lexbuf + start, len);
3754     }
3755     else
3756         value = NULL;
3757
3758     /* note delimiter if given */
3759     *pdelim = (delim ? delim : '"');
3760
3761     return value;
3762 }
3763
3764 /* attr must be non-NULL */
3765 static Bool IsValidAttrName( ctmbstr attr )
3766 {
3767     uint i, c = attr[0];
3768
3769     /* first character should be a letter */
3770     if (!TY_(IsLetter)(c))
3771         return no;
3772
3773     /* remaining characters should be namechars */
3774     for( i = 1; i < TY_(tmbstrlen)(attr); i++)
3775     {
3776         c = attr[i];
3777
3778         if (TY_(IsNamechar)(c))
3779             continue;
3780
3781         return no;
3782     }
3783
3784     return yes;
3785 }
3786
3787 /* create a new attribute */
3788 AttVal *TY_(NewAttribute)( TidyDocImpl* doc )
3789 {
3790     AttVal *av = (AttVal*) TidyDocAlloc( doc, sizeof(AttVal) );
3791     TidyClearMemory( av, sizeof(AttVal) );
3792     return av;
3793 }
3794
3795 /* create a new attribute with given name and value */
3796 AttVal* TY_(NewAttributeEx)( TidyDocImpl* doc, ctmbstr name, ctmbstr value,
3797                              int delim )
3798 {
3799     AttVal *av = TY_(NewAttribute)(doc);
3800     av->attribute = TY_(tmbstrdup)(doc->allocator, name);
3801     av->value = TY_(tmbstrdup)(doc->allocator, value);
3802     av->delim = delim;
3803     av->dict = TY_(FindAttribute)( doc, av );
3804     return av;
3805 }
3806
3807 static void AddAttrToList( AttVal** list, AttVal* av )
3808 {
3809   if ( *list == NULL )
3810     *list = av;
3811   else
3812   {
3813     AttVal* here = *list;
3814     while ( here->next )
3815       here = here->next;
3816     here->next = av;
3817   }
3818 }
3819
3820 void TY_(InsertAttributeAtEnd)( Node *node, AttVal *av )
3821 {
3822     AddAttrToList(&node->attributes, av);
3823 }
3824
3825 void TY_(InsertAttributeAtStart)( Node *node, AttVal *av )
3826 {
3827     av->next = node->attributes;
3828     node->attributes = av;
3829 }
3830
3831 /* swallows closing '>' */
3832
3833 static AttVal* ParseAttrs( TidyDocImpl* doc, Bool *isempty )
3834 {
3835     Lexer* lexer = doc->lexer;
3836     AttVal *av, *list;
3837     tmbstr value;
3838     int delim;
3839     Node *asp, *php;
3840
3841     list = NULL;
3842
3843     while ( !EndOfInput(doc) )
3844     {
3845         tmbstr attribute = ParseAttribute( doc, isempty, &asp, &php );
3846
3847         if (attribute == NULL)
3848         {
3849             /* check if attributes are created by ASP markup */
3850             if (asp)
3851             {
3852                 av = TY_(NewAttribute)(doc);
3853                 av->asp = asp;
3854                 AddAttrToList( &list, av ); 
3855                 continue;
3856             }
3857
3858             /* check if attributes are created by PHP markup */
3859             if (php)
3860             {
3861                 av = TY_(NewAttribute)(doc);
3862                 av->php = php;
3863                 AddAttrToList( &list, av ); 
3864                 continue;
3865             }
3866
3867             break;
3868         }
3869
3870         value = ParseValue( doc, attribute, no, isempty, &delim );
3871
3872         if (attribute && (IsValidAttrName(attribute) ||
3873             (cfgBool(doc, TidyXmlTags) && IsValidXMLAttrName(attribute))))
3874         {
3875             av = TY_(NewAttribute)(doc);
3876             av->delim = delim;
3877             av->attribute = attribute;
3878             av->value = value;
3879             av->dict = TY_(FindAttribute)( doc, av );
3880             AddAttrToList( &list, av ); 
3881         }
3882         else
3883         {
3884             av = TY_(NewAttribute)(doc);
3885             av->attribute = attribute;
3886             av->value = value;
3887
3888             if (LastChar(attribute) == '"')
3889                 TY_(ReportAttrError)( doc, lexer->token, av, MISSING_QUOTEMARK);
3890             else if (value == NULL)
3891                 TY_(ReportAttrError)(doc, lexer->token, av, MISSING_ATTR_VALUE);
3892             else
3893                 TY_(ReportAttrError)(doc, lexer->token, av, INVALID_ATTRIBUTE);
3894
3895             TY_(FreeAttribute)( doc, av );
3896         }
3897     }
3898
3899     return list;
3900 }
3901
3902 /*
3903   Returns document type declarations like
3904
3905   <!DOCTYPE foo PUBLIC "fpi" "sysid">
3906   <!DOCTYPE bar SYSTEM "sysid">
3907   <!DOCTYPE baz [ <!ENTITY ouml "&#246"> ]>
3908
3909   as
3910
3911   <foo PUBLIC="fpi" SYSTEM="sysid" />
3912   <bar SYSTEM="sysid" />
3913   <baz> &lt;!ENTITY ouml &quot;&amp;#246&quot;&gt; </baz>
3914 */
3915 static Node *ParseDocTypeDecl(TidyDocImpl* doc)
3916 {
3917     Lexer *lexer = doc->lexer;
3918     int start = lexer->lexsize;
3919     ParseDocTypeDeclState state = DT_DOCTYPENAME;
3920     uint c;
3921     uint delim = 0;
3922     Bool hasfpi = yes;
3923
3924     Node* node = TY_(NewNode)(lexer->allocator, lexer);
3925     node->type = DocTypeTag;
3926     node->start = lexer->txtstart;
3927     node->end = lexer->txtend;
3928
3929     lexer->waswhite = no;
3930
3931     /* todo: reset lexer->lexsize when appropriate to avoid wasting memory */
3932
3933     while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
3934     {
3935         /* convert newlines to spaces */
3936         if (state != DT_INTSUBSET)
3937             c = c == '\n' ? ' ' : c;
3938
3939         /* convert white-space sequences to single space character */
3940         if (TY_(IsWhite)(c) && state != DT_INTSUBSET)
3941         {
3942             if (!lexer->waswhite)
3943             {
3944                 TY_(AddCharToLexer)(lexer, c);
3945                 lexer->waswhite = yes;
3946             }
3947             else
3948             {
3949                 /* discard space */
3950                 continue;
3951             }
3952         }
3953         else
3954         {
3955             TY_(AddCharToLexer)(lexer, c);
3956             lexer->waswhite = no;
3957         }
3958
3959         switch(state)
3960         {
3961         case DT_INTERMEDIATE:
3962             /* determine what's next */
3963             if (TY_(ToUpper)(c) == 'P' || TY_(ToUpper)(c) == 'S')
3964             {
3965                 start = lexer->lexsize - 1;
3966                 state = DT_PUBLICSYSTEM;
3967                 continue;
3968             }
3969             else if (c == '[')
3970             {
3971                 start = lexer->lexsize;
3972                 state = DT_INTSUBSET;
3973                 continue;
3974             }
3975             else if (c == '\'' || c == '"')
3976             {
3977                 start = lexer->lexsize;
3978                 delim = c;
3979                 state = DT_QUOTEDSTRING;
3980                 continue;
3981             }
3982             else if (c == '>')
3983             {
3984                 AttVal* si;
3985
3986                 node->end = --(lexer->lexsize);
3987
3988                 si = TY_(GetAttrByName)(node, "SYSTEM");
3989                 if (si)
3990                     TY_(CheckUrl)(doc, node, si);
3991
3992                 if (!node->element || !IsValidXMLElemName(node->element))
3993                 {
3994                     TY_(ReportError)(doc, NULL, NULL, MALFORMED_DOCTYPE);
3995                     TY_(FreeNode)(doc, node);
3996                     return NULL;
3997                 }
3998 #ifdef TIDY_STORE_ORIGINAL_TEXT
3999                 StoreOriginalTextInToken(doc, node, 0);
4000 #endif
4001                 return node;
4002             }
4003             else
4004             {
4005                 /* error */
4006             }
4007             break;
4008         case DT_DOCTYPENAME:
4009             /* read document type name */
4010             if (TY_(IsWhite)(c) || c == '>' || c == '[')
4011             {
4012                 node->element = TY_(tmbstrndup)(doc->allocator,
4013                                                 lexer->lexbuf + start,
4014                                                 lexer->lexsize - start - 1);
4015                 if (c == '>' || c == '[')
4016                 {
4017                     --(lexer->lexsize);
4018                     TY_(UngetChar)(c, doc->docIn);
4019                 }
4020
4021                 state = DT_INTERMEDIATE;
4022                 continue;
4023             }
4024             break;
4025         case DT_PUBLICSYSTEM:
4026             /* read PUBLIC/SYSTEM */
4027             if (TY_(IsWhite)(c) || c == '>')
4028             {
4029                 char *attname = TY_(tmbstrndup)(doc->allocator,
4030                                                 lexer->lexbuf + start,
4031                                                 lexer->lexsize - start - 1);
4032                 hasfpi = !(TY_(tmbstrcasecmp)(attname, "SYSTEM") == 0);
4033
4034                 TidyDocFree(doc, attname);
4035
4036                 /* todo: report an error if SYSTEM/PUBLIC not uppercase */
4037
4038                 if (c == '>')
4039                 {
4040                     --(lexer->lexsize);
4041                     TY_(UngetChar)(c, doc->docIn);
4042                 }
4043
4044                 state = DT_INTERMEDIATE;
4045                 continue;
4046             }
4047             break;
4048         case DT_QUOTEDSTRING:
4049             /* read quoted string */
4050             if (c == delim)
4051             {
4052                 char *value = TY_(tmbstrndup)(doc->allocator,
4053                                               lexer->lexbuf + start,
4054                                               lexer->lexsize - start - 1);
4055                 AttVal* att = TY_(AddAttribute)(doc, node, hasfpi ? "PUBLIC" : "SYSTEM", value);
4056                 TidyDocFree(doc, value);
4057                 att->delim = delim;
4058                 hasfpi = no;
4059                 state = DT_INTERMEDIATE;
4060                 delim = 0;
4061                 continue;
4062             }
4063             break;
4064         case DT_INTSUBSET:
4065             /* read internal subset */
4066             if (c == ']')
4067             {
4068                 Node* subset;
4069                 lexer->txtstart = start;
4070                 lexer->txtend = lexer->lexsize - 1;
4071                 subset = TY_(TextToken)(lexer);
4072                 TY_(InsertNodeAtEnd)(node, subset);
4073                 state = DT_INTERMEDIATE;
4074             }
4075             break;
4076         }
4077     }
4078
4079     /* document type declaration not finished */
4080     TY_(ReportError)(doc, NULL, NULL, MALFORMED_DOCTYPE);
4081     TY_(FreeNode)(doc, node);
4082     return NULL;
4083 }
4084
4085 /*
4086  * local variables:
4087  * mode: c
4088  * indent-tabs-mode: nil
4089  * c-basic-offset: 4
4090  * eval: (c-set-offset 'substatement-open 0)
4091  * end:
4092  */