5121c244e793ec4ccffa366ace3829552cac83e2
[WebKit-https.git] / WebCore / khtml / html / htmltokenizer.cpp
1 /*
2     This file is part of the KDE libraries
3
4     Copyright (C) 1997 Martin Jones (mjones@kde.org)
5               (C) 1997 Torben Weis (weis@kde.org)
6               (C) 1998 Waldo Bastian (bastian@kde.org)
7               (C) 1999 Lars Knoll (knoll@kde.org)
8               (C) 1999 Antti Koivisto (koivisto@kde.org)
9               (C) 2001 Dirk Mueller (mueller@kde.org)
10     Copyright (C) 2004 Apple Computer, Inc.
11
12     This library is free software; you can redistribute it and/or
13     modify it under the terms of the GNU Library General Public
14     License as published by the Free Software Foundation; either
15     version 2 of the License, or (at your option) any later version.
16
17     This library is distributed in the hope that it will be useful,
18     but WITHOUT ANY WARRANTY; without even the implied warranty of
19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20     Library General Public License for more details.
21
22     You should have received a copy of the GNU Library General Public License
23     along with this library; see the file COPYING.LIB.  If not, write to
24     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25     Boston, MA 02111-1307, USA.
26 */
27 //----------------------------------------------------------------------------
28 //
29 // KDE HTML Widget - Tokenizers
30
31 //#define TOKEN_DEBUG 1
32 //#define TOKEN_DEBUG 2
33
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37
38 //#include <string.h>
39 #include "html/htmltokenizer.h"
40 #include "html/html_documentimpl.h"
41 #include "html/htmlparser.h"
42 #include "html/dtd.h"
43
44 #include "misc/loader.h"
45 #include "misc/htmlhashes.h"
46
47 #include "khtmlview.h"
48 #include "khtml_part.h"
49 #include "xml/dom_docimpl.h"
50 #include "css/csshelper.h"
51 #include "ecma/kjs_proxy.h"
52 #include <kcharsets.h>
53 #include <kglobal.h>
54 #include <ctype.h>
55 #include <assert.h>
56 #include <qvariant.h>
57 #include <kdebug.h>
58 #include <stdlib.h>
59
60 using DOM::AtomicString;
61 using DOM::AttributeImpl;
62 using DOM::DOMString;
63 using DOM::DOMStringImpl;
64 using DOM::DocumentImpl;
65 using DOM::emptyAtom;
66 using DOM::endTagRequirement;
67
68 // turn off inlining to void warning with newer gcc
69 #undef __inline
70 #define __inline
71 #include "kentities.c"
72 #undef __inline
73
74 // #define INSTRUMENT_LAYOUT_SCHEDULING 1
75
76 #define TOKENIZER_CHUNK_SIZE  4096
77
78 // FIXME: We would like this constant to be 200ms.  Yielding more aggressively results in increased
79 // responsiveness and better incremental rendering.  It slows down overall page-load on slower machines,
80 // though, so for now we set a value of 500.
81 #define TOKENIZER_TIME_DELAY  500
82
83 namespace khtml {
84
85 static const char commentStart [] = "<!--";
86 static const char scriptEnd [] = "</script";
87 static const char xmpEnd [] = "</xmp";
88 static const char styleEnd [] =  "</style";
89 static const char textareaEnd [] = "</textarea";
90 static const char titleEnd [] = "</title";
91
92 #define KHTML_ALLOC_QCHAR_VEC( N ) (QChar*) malloc( sizeof(QChar)*( N ) )
93 #define KHTML_REALLOC_QCHAR_VEC(P, N ) (QChar*) P = realloc(p, sizeof(QChar)*( N ))
94 #define KHTML_DELETE_QCHAR_VEC( P ) free((char*)( P ))
95
96 // Full support for MS Windows extensions to Latin-1.
97 // Technically these extensions should only be activated for pages
98 // marked "windows-1252" or "cp1252", but
99 // in the standard Microsoft way, these extensions infect hundreds of thousands
100 // of web pages.  Note that people with non-latin-1 Microsoft extensions
101 // are SOL.
102 //
103 // See: http://www.microsoft.com/globaldev/reference/WinCP.asp
104 //      http://www.bbsinc.com/iso8859.html
105 //      http://www.obviously.com/
106 //
107 // There may be better equivalents
108
109 #if APPLE_CHANGES
110
111 // Note that we have more Unicode characters than Qt, so we use the
112 // official mapping table from the Unicode 2.0 standard here instead of
113 // one with hacks to avoid certain Unicode characters. Also, we don't
114 // need the unrelated hacks to avoid Unicode characters that are in the
115 // original version.
116
117 // We need this for entities at least. For non-entity text, we could
118 // handle this in the text codec.
119
120 // To cover non-entity text, I think this function would need to be called
121 // in more places. There seem to be many places that don't call fixUpChar.
122
123 inline void fixUpChar(QChar& c) {
124     switch (c.unicode()) {
125         case 0x0080: c = 0x20AC; break;
126         case 0x0081: break;
127         case 0x0082: c = 0x201A; break;
128         case 0x0083: c = 0x0192; break;
129         case 0x0084: c = 0x201E; break;
130         case 0x0085: c = 0x2026; break;
131         case 0x0086: c = 0x2020; break;
132         case 0x0087: c = 0x2021; break;
133         case 0x0088: c = 0x02C6; break;
134         case 0x0089: c = 0x2030; break;
135         case 0x008A: c = 0x0160; break;
136         case 0x008B: c = 0x2039; break;
137         case 0x008C: c = 0x0152; break;
138         case 0x008D: break;
139         case 0x008E: c = 0x017D; break;
140         case 0x008F: break;
141         case 0x0090: break;
142         case 0x0091: c = 0x2018; break;
143         case 0x0092: c = 0x2019; break;
144         case 0x0093: c = 0x201C; break;
145         case 0x0094: c = 0x201D; break;
146         case 0x0095: c = 0x2022; break;
147         case 0x0096: c = 0x2013; break;
148         case 0x0097: c = 0x2014; break;
149         case 0x0098: c = 0x02DC; break;
150         case 0x0099: c = 0x2122; break;
151         case 0x009A: c = 0x0161; break;
152         case 0x009B: c = 0x203A; break;
153         case 0x009C: c = 0x0153; break;
154         case 0x009D: break;
155         case 0x009E: c = 0x017E; break;
156         case 0x009F: c = 0x0178; break;
157     }
158 }
159
160 #else // APPLE_CHANGES
161
162 #define fixUpChar(x) \
163             if (!(x).row() ) { \
164                 switch ((x).cell()) \
165                 { \
166                 /* ALL of these should be changed to Unicode SOON */ \
167                 case 0x80: (x) = 0x20ac; break; \
168                 case 0x82: (x) = ',';    break; \
169                 case 0x83: (x) = 0x0192; break; \
170                 case 0x84: (x) = '"';    break; \
171                 case 0x85: (x) = 0x2026; break; \
172                 case 0x86: (x) = 0x2020; break; \
173                 case 0x87: (x) = 0x2021; break; \
174                 case 0x88: (x) = 0x02C6; break; \
175                 case 0x89: (x) = 0x2030; break; \
176                 case 0x8A: (x) = 0x0160; break; \
177                 case 0x8b: (x) = '<';    break; \
178                 case 0x8C: (x) = 0x0152; break; \
179 \
180                 case 0x8E: (x) = 0x017D; break; \
181 \
182 \
183                 case 0x91: (x) = '\'';   break; \
184                 case 0x92: (x) = '\'';   break; \
185                 case 0x93: (x) = '"';    break; \
186                 case 0x94: (x) = '"';    break; \
187                 case 0x95: (x) = '*';    break; \
188                 case 0x96: (x) = '-';    break; \
189                 case 0x97: (x) = '-';    break; \
190                 case 0x98: (x) = '~';    break; \
191                 case 0x99: (x) = 0x2122; break; \
192                 case 0x9A: (x) = 0x0161; break; \
193                 case 0x9b: (x) = '>';    break; \
194                 case 0x9C: (x) = 0x0153; break; \
195 \
196                 case 0x9E: (x) = 0x017E; break; \
197                 case 0x9F: (x) = 0x0178; break; \
198                 /* This one should die */ \
199                 case 0xb7: (x) = '*';    break; \
200                 default: break; \
201                 } \
202             } \
203             else { \
204                 /* These should all die sooner rather than later */ \
205                 switch( (x).unicode() ) { \
206                 case 0x2013: (x) = '-'; break; \
207                 case 0x2014: (x) = '-'; break; \
208                 case 0x2018: (x) = '\''; break; \
209                 case 0x2019: (x) = '\''; break; \
210                 case 0x201c: (x) = '"'; break; \
211                 case 0x201d: (x) = '"'; break; \
212                 case 0x2022: (x) = '*'; break; \
213                 case 0x2122: (x) = 0x2122; break; \
214                 default: break; \
215                 } \
216             }
217
218 #endif // APPLE_CHANGES
219
220 inline bool tagMatch(const char *s1, const QChar *s2, uint length)
221 {
222     for (uint i = 0; i != length; ++i) {
223         char c1 = s1[i];
224         char uc1 = toupper(c1);
225         QChar c2 = s2[i];
226         if (c1 != c2 && uc1 != c2)
227             return false;
228     }
229     return true;
230 }
231
232 // ----------------------------------------------------------------------------
233
234 HTMLTokenizer::HTMLTokenizer(DOM::DocumentPtr *_doc, KHTMLView *_view, bool includesComments)
235     : inWrite(false)
236 {
237     view = _view;
238     buffer = 0;
239     scriptCode = 0;
240     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
241     charsets = KGlobal::charsets();
242     parser = new KHTMLParser(_view, _doc, includesComments);
243     m_executingScript = 0;
244     loadingExtScript = false;
245     onHold = false;
246     attrNamePresent = false;
247     timerId = 0;
248     includesCommentsInDOM = includesComments;
249     loadStopped = false;
250     
251     begin();
252 }
253
254 HTMLTokenizer::HTMLTokenizer(DOM::DocumentPtr *_doc, DOM::DocumentFragmentImpl *i, bool includesComments)
255     : inWrite(false)
256 {
257     view = 0;
258     buffer = 0;
259     scriptCode = 0;
260     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
261     charsets = KGlobal::charsets();
262     parser = new KHTMLParser(i, _doc, includesComments);
263     m_executingScript = 0;
264     loadingExtScript = false;
265     onHold = false;
266     timerId = 0;
267     includesCommentsInDOM = includesComments;
268     loadStopped = false;
269
270     begin();
271 }
272
273 void HTMLTokenizer::reset()
274 {
275     assert(m_executingScript == 0);
276     assert(onHold == false);
277
278     while (!cachedScript.isEmpty())
279         cachedScript.dequeue()->deref(this);
280
281     if ( buffer )
282         KHTML_DELETE_QCHAR_VEC(buffer);
283     buffer = dest = 0;
284     size = 0;
285
286     if ( scriptCode )
287         KHTML_DELETE_QCHAR_VEC(scriptCode);
288     scriptCode = 0;
289     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
290
291     if (timerId) {
292         killTimer(timerId);
293         timerId = 0;
294     }
295     timerId = 0;
296     allowYield = false;
297     forceSynchronous = false;
298
299     currToken.reset();
300 }
301
302 void HTMLTokenizer::begin()
303 {
304     m_executingScript = 0;
305     loadingExtScript = false;
306     onHold = false;
307     reset();
308     size = 254;
309     buffer = KHTML_ALLOC_QCHAR_VEC( 255 );
310     dest = buffer;
311     tag = NoTag;
312     pending = NonePending;
313     discard = NoneDiscard;
314     plaintext = false;
315     xmp = false;
316     processingInstruction = false;
317     script = false;
318     escaped = false;
319     style = false;
320     skipLF = false;
321     select = false;
322     comment = false;
323     server = false;
324     textarea = false;
325     title = false;
326     startTag = false;
327     tquote = NoQuote;
328     searchCount = 0;
329     Entity = NoEntity;
330     loadingExtScript = false;
331     scriptSrc = QString::null;
332     pendingSrc.clear();
333     currentPrependingSrc = 0;
334     noMoreData = false;
335     brokenComments = false;
336     brokenServer = false;
337     lineno = 0;
338     scriptStartLineno = 0;
339     tagStartLineno = 0;
340     forceSynchronous = false;
341 }
342
343 void HTMLTokenizer::setForceSynchronous(bool force)
344 {
345     forceSynchronous = force;
346 }
347
348 void HTMLTokenizer::processListing(TokenizerString list)
349 {
350     // This function adds the listing 'list' as
351     // preformatted text-tokens to the token-collection
352     while ( !list.isEmpty() )
353     {
354         checkBuffer();
355
356         if (skipLF && ( *list != '\n' ))
357         {
358             skipLF = false;
359         }
360
361         if (skipLF)
362         {
363             skipLF = false;
364             ++list;
365         }
366         else if (( *list == '\n' ) || ( *list == '\r' ))
367         {
368             if (discard == LFDiscard)
369             {
370                 // Ignore this LF
371                 discard = NoneDiscard; // We have discarded 1 LF
372             }
373             else
374             {
375                 // Process this LF
376                 if (pending)
377                     addPending();
378                 pending = LFPending;
379             }
380             /* Check for MS-DOS CRLF sequence */
381             if (*list == '\r')
382             {
383                 skipLF = true;
384             }
385             ++list;
386         }
387         else if ( *list == ' ' )
388         {
389             if (pending)
390                 addPending();
391             pending = SpacePending;
392
393             ++list;
394         }
395         else
396         {
397             discard = NoneDiscard;
398             if (pending)
399                 addPending();
400
401             *dest++ = *list;
402             ++list;
403         }
404
405     }
406
407     if (pending)
408         addPending();
409 }
410
411 void HTMLTokenizer::parseSpecial(TokenizerString &src)
412 {
413     assert( textarea || title || !Entity );
414     assert( !tag );
415     assert( xmp+textarea+title+style+script == 1 );
416     if (script)
417         scriptStartLineno = lineno+src.lineCount();
418
419     if ( comment ) parseComment( src );
420
421     while ( !src.isEmpty() ) {
422         checkScriptBuffer();
423         unsigned char ch = src->latin1();
424         if ( !scriptCodeResync && !brokenComments && !textarea && !xmp && !title && ch == '-' && scriptCodeSize >= 3 && !src.escaped() && scriptCode[scriptCodeSize-3] == '<' && scriptCode[scriptCodeSize-2] == '!' && scriptCode[scriptCodeSize-1] == '-' ) {
425             comment = true;
426             parseComment( src );
427             continue;
428         }
429         if ( scriptCodeResync && !tquote && ( ch == '>' ) ) {
430             ++src;
431             scriptCodeSize = scriptCodeResync-1;
432             scriptCodeResync = 0;
433             scriptCode[ scriptCodeSize ] = scriptCode[ scriptCodeSize + 1 ] = 0;
434             if ( script )
435                 scriptHandler();
436             else {
437                 processListing(TokenizerString(scriptCode, scriptCodeSize));
438                 processToken();
439                 if ( style )         { currToken.id = ID_STYLE + ID_CLOSE_TAG; }
440                 else if ( textarea ) { currToken.id = ID_TEXTAREA + ID_CLOSE_TAG; }
441                 else if ( title ) { currToken.id = ID_TITLE + ID_CLOSE_TAG; }
442                 else if ( xmp )  { currToken.id = ID_XMP + ID_CLOSE_TAG; }
443                 processToken();
444                 style = script = style = textarea = title = xmp = false;
445                 tquote = NoQuote;
446                 scriptCodeSize = scriptCodeResync = 0;
447             }
448             return;
449         }
450         // possible end of tagname, lets check.
451         if ( !scriptCodeResync && !escaped && !src.escaped() && ( ch == '>' || ch == '/' || ch <= ' ' ) && ch &&
452              scriptCodeSize >= searchStopperLen &&
453              tagMatch( searchStopper, scriptCode+scriptCodeSize-searchStopperLen, searchStopperLen )) {
454             scriptCodeResync = scriptCodeSize-searchStopperLen+1;
455             tquote = NoQuote;
456             continue;
457         }
458         if ( scriptCodeResync && !escaped ) {
459             if(ch == '\"')
460                 tquote = (tquote == NoQuote) ? DoubleQuote : ((tquote == SingleQuote) ? SingleQuote : NoQuote);
461             else if(ch == '\'')
462                 tquote = (tquote == NoQuote) ? SingleQuote : (tquote == DoubleQuote) ? DoubleQuote : NoQuote;
463             else if (tquote != NoQuote && (ch == '\r' || ch == '\n'))
464                 tquote = NoQuote;
465         }
466         escaped = ( !escaped && ch == '\\' );
467         if (!scriptCodeResync && (textarea||title) && !src.escaped() && ch == '&') {
468             QChar *scriptCodeDest = scriptCode+scriptCodeSize;
469             ++src;
470             parseEntity(src,scriptCodeDest,true);
471             scriptCodeSize = scriptCodeDest-scriptCode;
472         }
473         else {
474             scriptCode[scriptCodeSize] = *src;
475             fixUpChar(scriptCode[scriptCodeSize]);
476             ++scriptCodeSize;
477             ++src;
478         }
479     }
480 }
481
482 void HTMLTokenizer::scriptHandler()
483 {
484     // We are inside a <script>
485     bool doScriptExec = false;
486     CachedScript* cs = 0;
487     // don't load external scripts for standalone documents (for now)
488     if (!scriptSrc.isEmpty() && parser->doc()->part()) {
489         // forget what we just got; load from src url instead
490         if ( !parser->skipMode() ) {
491 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
492             if (!parser->doc()->ownerElement())
493                 printf("Requesting script at time %d\n", parser->doc()->elapsedTime());
494 #endif
495             if ( (cs = parser->doc()->docLoader()->requestScript(scriptSrc, scriptSrcCharset) ))
496                 cachedScript.enqueue(cs);
497         }
498         scriptSrc=QString::null;
499     }
500     else {
501 #ifdef TOKEN_DEBUG
502         kdDebug( 6036 ) << "---START SCRIPT---" << endl;
503         kdDebug( 6036 ) << QString(scriptCode, scriptCodeSize) << endl;
504         kdDebug( 6036 ) << "---END SCRIPT---" << endl;
505 #endif
506         // Parse scriptCode containing <script> info
507         doScriptExec = true;
508     }
509     processListing(TokenizerString(scriptCode, scriptCodeSize));
510     QString exScript( buffer, dest-buffer );
511     processToken();
512     currToken.id = ID_SCRIPT + ID_CLOSE_TAG;
513     processToken();
514
515     TokenizerString *savedPrependingSrc = currentPrependingSrc;
516     TokenizerString prependingSrc;
517     currentPrependingSrc = &prependingSrc;
518     if ( !parser->skipMode() ) {
519         if (cs) {
520              //kdDebug( 6036 ) << "cachedscript extern!" << endl;
521              //kdDebug( 6036 ) << "src: *" << QString( src.current(), src.length() ).latin1() << "*" << endl;
522              //kdDebug( 6036 ) << "pending: *" << pendingSrc.latin1() << "*" << endl;
523             if (savedPrependingSrc) {
524                 savedPrependingSrc->append(src);
525             } else {
526                 pendingSrc.prepend(src);
527             }
528             setSrc(TokenizerString());
529             scriptCodeSize = scriptCodeResync = 0;
530             cs->ref(this);
531             // will be 0 if script was already loaded and ref() executed it
532             if (!cachedScript.isEmpty())
533                 loadingExtScript = true;
534         }
535         else if (view && doScriptExec && javascript ) {
536             if (!m_executingScript)
537                 pendingSrc.prepend(src);
538             else
539                 prependingSrc = src;
540             setSrc(TokenizerString());
541             scriptCodeSize = scriptCodeResync = 0;
542             //QTime dt;
543             //dt.start();
544             scriptExecution( exScript, QString::null, scriptStartLineno );
545             //kdDebug( 6036 ) << "script execution time:" << dt.elapsed() << endl;
546         }
547     }
548
549     script = false;
550     scriptCodeSize = scriptCodeResync = 0;
551
552     if ( !m_executingScript && !loadingExtScript ) {
553         // kdDebug( 6036 ) << "adding pending Output to parsed string" << endl;
554         src.append(pendingSrc);
555         pendingSrc.clear();
556     } else if (!prependingSrc.isEmpty()) {
557         // restore first so that the write appends in the right place
558         // (does not hurt to do it again below)
559         currentPrependingSrc = savedPrependingSrc;
560
561         // we need to do this slightly modified bit of one of the write() cases
562         // because we want to prepend to pendingSrc rather than appending
563         // if there's no previous prependingSrc
564         if (loadingExtScript) {
565             if (currentPrependingSrc) {
566                 currentPrependingSrc->append(prependingSrc);
567             } else {
568                 pendingSrc.prepend(prependingSrc);
569             }
570         } else {
571             write(prependingSrc, false);
572         }
573     }
574
575     currentPrependingSrc = savedPrependingSrc;
576 }
577
578 void HTMLTokenizer::scriptExecution( const QString& str, QString scriptURL,
579                                      int baseLine)
580 {
581 #if APPLE_CHANGES
582     if (!view || !view->part())
583         return;
584 #endif
585     bool oldscript = script;
586     m_executingScript++;
587     script = false;
588     QString url;    
589     if (scriptURL.isNull())
590       url = view->part()->xmlDocImpl()->URL();
591     else
592       url = scriptURL;
593
594     TokenizerString *savedPrependingSrc = currentPrependingSrc;
595     TokenizerString prependingSrc;
596     currentPrependingSrc = &prependingSrc;
597
598 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
599     if (!parser->doc()->ownerElement())
600         printf("beginning script execution at %d\n", parser->doc()->elapsedTime());
601 #endif
602
603     view->part()->executeScript(url,baseLine,0,str);
604
605     allowYield = true;
606
607 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
608     if (!parser->doc()->ownerElement())
609         printf("ending script execution at %d\n", parser->doc()->elapsedTime());
610 #endif
611     
612     m_executingScript--;
613     script = oldscript;
614
615     if ( !m_executingScript && !loadingExtScript ) {
616         // kdDebug( 6036 ) << "adding pending Output to parsed string" << endl;
617         src.append(pendingSrc);
618         pendingSrc.clear();
619     } else if (!prependingSrc.isEmpty()) {
620         // restore first so that the write appends in the right place
621         // (does not hurt to do it again below)
622         currentPrependingSrc = savedPrependingSrc;
623
624         // we need to do this slightly modified bit of one of the write() cases
625         // because we want to prepend to pendingSrc rather than appending
626         // if there's no previous prependingSrc
627         if (loadingExtScript) {
628             if (currentPrependingSrc) {
629                 currentPrependingSrc->append(prependingSrc);
630             } else {
631                 pendingSrc.prepend(prependingSrc);
632             }
633         } else {
634             write(prependingSrc, false);
635         }
636     }
637
638     currentPrependingSrc = savedPrependingSrc;
639 }
640
641 void HTMLTokenizer::parseComment(TokenizerString &src)
642 {
643     // FIXME: Why does this code even run for comments inside <script>? This seems bogus.
644     bool strict = !parser->doc()->inCompatMode() && !script;
645     int delimiterCount = 0;
646     bool canClose = false;
647     checkScriptBuffer(src.length());
648     while ( !src.isEmpty() ) {
649         scriptCode[ scriptCodeSize++ ] = *src;
650 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
651         qDebug("comment is now: *%s*",
652                QConstString((QChar*)src.current(), kMin(16, src.length())).string().latin1());
653 #endif
654
655         if (strict) {
656             if (src->unicode() == '-') {
657                 delimiterCount++;
658                 if (delimiterCount == 2) {
659                     delimiterCount = 0;
660                     canClose = !canClose;
661                 }
662             }
663             else
664                 delimiterCount = 0;
665         }
666
667         if ((!strict || canClose) && src->unicode() == '>') {
668             bool handleBrokenComments = brokenComments && !(script || style);
669             int endCharsCount = 1; // start off with one for the '>' character
670             if (!strict) {
671                 // In quirks mode just check for -->
672                 if (scriptCodeSize > 2 && scriptCode[scriptCodeSize-3] == '-' && scriptCode[scriptCodeSize-2] == '-') {
673                     endCharsCount = 3;
674                 }
675                 else if (scriptCodeSize > 3 && scriptCode[scriptCodeSize-4] == '-' && scriptCode[scriptCodeSize-3] == '-' && 
676                     scriptCode[scriptCodeSize-2] == '!') {
677                     // Other browsers will accept --!> as a close comment, even though it's
678                     // not technically valid.
679                     endCharsCount = 4;
680                 }
681             }
682             if (canClose || handleBrokenComments || endCharsCount > 1) {
683                 ++src;
684                 if (!( script || xmp || textarea || style)) {
685                     if (includesCommentsInDOM) {
686                         checkScriptBuffer();
687                         scriptCode[ scriptCodeSize ] = 0;
688                         scriptCode[ scriptCodeSize + 1 ] = 0;
689                         currToken.id = ID_COMMENT;
690                         processListing(TokenizerString(scriptCode, scriptCodeSize - endCharsCount));
691                         processToken();
692                         currToken.id = ID_COMMENT + ID_CLOSE_TAG;
693                         processToken();
694                     }
695                     scriptCodeSize = 0;
696                 }
697                 comment = false;
698                 return; // Finished parsing comment
699             }
700         }
701         ++src;
702     }
703 }
704
705 void HTMLTokenizer::parseServer(TokenizerString &src)
706 {
707     checkScriptBuffer(src.length());
708     while ( !src.isEmpty() ) {
709         scriptCode[ scriptCodeSize++ ] = *src;
710         if (src->unicode() == '>' &&
711             scriptCodeSize > 1 && scriptCode[scriptCodeSize-2] == '%') {
712             ++src;
713             server = false;
714             scriptCodeSize = 0;
715             return; // Finished parsing server include
716         }
717         ++src;
718     }
719 }
720
721 void HTMLTokenizer::parseProcessingInstruction(TokenizerString &src)
722 {
723     char oldchar = 0;
724     while ( !src.isEmpty() )
725     {
726         unsigned char chbegin = src->latin1();
727         if(chbegin == '\'') {
728             tquote = tquote == SingleQuote ? NoQuote : SingleQuote;
729         }
730         else if(chbegin == '\"') {
731             tquote = tquote == DoubleQuote ? NoQuote : DoubleQuote;
732         }
733         // Look for '?>'
734         // some crappy sites omit the "?" before it, so
735         // we look for an unquoted '>' instead. (IE compatible)
736         else if ( chbegin == '>' && ( !tquote || oldchar == '?' ) )
737         {
738             // We got a '?>' sequence
739             processingInstruction = false;
740             ++src;
741             discard=LFDiscard;
742             return; // Finished parsing comment!
743         }
744         ++src;
745         oldchar = chbegin;
746     }
747 }
748
749 void HTMLTokenizer::parseText(TokenizerString &src)
750 {
751     while ( !src.isEmpty() )
752     {
753         // do we need to enlarge the buffer?
754         checkBuffer();
755
756         // ascii is okay because we only do ascii comparisons
757         unsigned char chbegin = src->latin1();
758
759         if (skipLF && ( chbegin != '\n' ))
760         {
761             skipLF = false;
762         }
763
764         if (skipLF)
765         {
766             skipLF = false;
767             ++src;
768         }
769         else if (( chbegin == '\n' ) || ( chbegin == '\r' ))
770         {
771             if (chbegin == '\r')
772                 skipLF = true;
773
774             *dest++ = '\n';
775             ++src;
776         }
777         else {
778             *dest = *src;
779             fixUpChar(*dest);
780             ++dest;
781             ++src;
782         }
783     }
784 }
785
786
787 void HTMLTokenizer::parseEntity(TokenizerString &src, QChar *&dest, bool start)
788 {
789     if( start )
790     {
791         cBufferPos = 0;
792         Entity = SearchEntity;
793         EntityUnicodeValue = 0;
794     }
795
796     while( !src.isEmpty() )
797     {
798         ushort cc = src->unicode();
799         switch(Entity) {
800         case NoEntity:
801             assert(Entity != NoEntity);
802             return;
803         
804         case SearchEntity:
805             if(cc == '#') {
806                 cBuffer[cBufferPos++] = cc;
807                 ++src;
808                 Entity = NumericSearch;
809             }
810             else
811                 Entity = EntityName;
812
813             break;
814
815         case NumericSearch:
816             if(cc == 'x' || cc == 'X') {
817                 cBuffer[cBufferPos++] = cc;
818                 ++src;
819                 Entity = Hexadecimal;
820             }
821             else if(cc >= '0' && cc <= '9')
822                 Entity = Decimal;
823             else
824                 Entity = SearchSemicolon;
825
826             break;
827
828         case Hexadecimal:
829         {
830             int ll = kMin(src.length(), 8U);
831             while(ll--) {
832                 QChar csrc(src->lower());
833                 cc = csrc.cell();
834
835                 if(csrc.row() || !((cc >= '0' && cc <= '9') || (cc >= 'a' && cc <= 'f'))) {
836                     break;
837                 }
838                 EntityUnicodeValue = EntityUnicodeValue*16 + (cc - ( cc < 'a' ? '0' : 'a' - 10));
839                 cBuffer[cBufferPos++] = cc;
840                 ++src;
841             }
842             Entity = SearchSemicolon;
843             break;
844         }
845         case Decimal:
846         {
847             int ll = kMin(src.length(), 9-cBufferPos);
848             while(ll--) {
849                 cc = src->cell();
850
851                 if(src->row() || !(cc >= '0' && cc <= '9')) {
852                     Entity = SearchSemicolon;
853                     break;
854                 }
855
856                 EntityUnicodeValue = EntityUnicodeValue * 10 + (cc - '0');
857                 cBuffer[cBufferPos++] = cc;
858                 ++src;
859             }
860             if(cBufferPos == 9)  Entity = SearchSemicolon;
861             break;
862         }
863         case EntityName:
864         {
865             int ll = kMin(src.length(), 9-cBufferPos);
866             while(ll--) {
867                 QChar csrc = *src;
868                 cc = csrc.cell();
869
870                 if(csrc.row() || !((cc >= 'a' && cc <= 'z') ||
871                                    (cc >= '0' && cc <= '9') || (cc >= 'A' && cc <= 'Z'))) {
872                     Entity = SearchSemicolon;
873                     break;
874                 }
875
876                 cBuffer[cBufferPos++] = cc;
877                 ++src;
878             }
879             if(cBufferPos == 9) Entity = SearchSemicolon;
880             if(Entity == SearchSemicolon) {
881                 if(cBufferPos > 1) {
882                     const entity *e = findEntity(cBuffer, cBufferPos);
883                     if(e)
884                         EntityUnicodeValue = e->code;
885
886                     // be IE compatible
887                     if(tag && EntityUnicodeValue > 255 && *src != ';')
888                         EntityUnicodeValue = 0;
889                 }
890             }
891             else
892                 break;
893         }
894         case SearchSemicolon:
895
896             //kdDebug( 6036 ) << "ENTITY " << EntityUnicodeValue << ", " << res << endl;
897
898             // Don't allow surrogate code points, or values that are more than 21 bits.
899             if ((EntityUnicodeValue > 0 && EntityUnicodeValue < 0xD800)
900                     || (EntityUnicodeValue >= 0xE000 && EntityUnicodeValue <= 0x1FFFFF)) {
901             
902                 if (*src == ';')
903                     ++src;
904
905                 if (EntityUnicodeValue <= 0xFFFF) {
906                     QChar c(EntityUnicodeValue);
907                     fixUpChar(c);
908                     checkBuffer();
909                     src.push(c);
910                 } else {
911                     // Convert to UTF-16, using surrogate code points.
912                     QChar c1(0xD800 | (((EntityUnicodeValue >> 16) - 1) << 6) | ((EntityUnicodeValue >> 10) & 0x3F));
913                     QChar c2(0xDC00 | (EntityUnicodeValue & 0x3FF));
914                     checkBuffer(2);
915                     src.push(c1);
916                     src.push(c2);
917                 }
918
919             } else {
920 #ifdef TOKEN_DEBUG
921                 kdDebug( 6036 ) << "unknown entity!" << endl;
922 #endif
923                 checkBuffer(10);
924                 // ignore the sequence, add it to the buffer as plaintext
925                 *dest++ = '&';
926                 for(unsigned int i = 0; i < cBufferPos; i++)
927                     dest[i] = cBuffer[i];
928                 dest += cBufferPos;
929             }
930
931             Entity = NoEntity;
932             return;
933         }
934     }
935 }
936
937 void HTMLTokenizer::parseTag(TokenizerString &src)
938 {
939     assert(!Entity );
940
941     while ( !src.isEmpty() )
942     {
943         checkBuffer();
944 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
945         uint l = 0;
946         while(l < src.length() && (*(src.current()+l)).latin1() != '>')
947             l++;
948         qDebug("src is now: *%s*, tquote: %d",
949                QConstString((QChar*)src.current(), l).string().latin1(), tquote);
950 #endif
951         switch(tag) {
952         case NoTag:
953         {
954             return;
955         }
956         case TagName:
957         {
958 #if defined(TOKEN_DEBUG) &&  TOKEN_DEBUG > 1
959             qDebug("TagName");
960 #endif
961             if (searchCount > 0)
962             {
963                 if (*src == commentStart[searchCount])
964                 {
965                     searchCount++;
966                     if (searchCount == 4)
967                     {
968 #ifdef TOKEN_DEBUG
969                         kdDebug( 6036 ) << "Found comment" << endl;
970 #endif
971                         // Found '<!--' sequence
972                         ++src;
973                         dest = buffer; // ignore the previous part of this tag
974                         comment = true;
975                         tag = NoTag;
976
977                         // Fix bug 34302 at kde.bugs.org.  Go ahead and treat
978                         // <!--> as a valid comment, since both mozilla and IE on windows
979                         // can handle this case.  Only do this in quirks mode. -dwh
980                         if (!src.isEmpty() && *src == '>' && parser->doc()->inCompatMode()) {
981                           comment = false;
982                           ++src;
983                           if (!src.isEmpty())
984                               cBuffer[cBufferPos++] = src->cell();
985                         }
986                         else
987                           parseComment(src);
988
989                         return; // Finished parsing tag!
990                     }
991                     // cuts of high part, is okay
992                     cBuffer[cBufferPos++] = src->cell();
993                     ++src;
994                     break;
995                 }
996                 else
997                     searchCount = 0; // Stop looking for '<!--' sequence
998             }
999
1000             bool finish = false;
1001             unsigned int ll = kMin(src.length(), CBUFLEN-cBufferPos);
1002             while(ll--) {
1003                 ushort curchar = *src;
1004                 if(curchar <= ' ' || curchar == '>' ) {
1005                     finish = true;
1006                     break;
1007                 }
1008                 // Use tolower() instead of | 0x20 to lowercase the char because there is no 
1009                 // performance gain in using | 0x20 since tolower() is optimized and 
1010                 // | 0x20 turns characters such as '_' into junk.
1011                 cBuffer[cBufferPos++] = tolower(curchar);
1012                 ++src;
1013             }
1014
1015             // Disadvantage: we add the possible rest of the tag
1016             // as attribute names. ### judge if this causes problems
1017             if(finish || CBUFLEN == cBufferPos) {
1018                 bool beginTag;
1019                 char* ptr = cBuffer;
1020                 unsigned int len = cBufferPos;
1021                 cBuffer[cBufferPos] = '\0';
1022                 if ((cBufferPos > 0) && (*ptr == '/'))
1023                 {
1024                     // End Tag
1025                     beginTag = false;
1026                     ptr++;
1027                     len--;
1028                 }
1029                 else
1030                     // Start Tag
1031                     beginTag = true;
1032
1033                 // Accept empty xml tags like <br/>.  We trim off the "/" so that when we call
1034                 // getTagID, we'll look up "br" as the tag name and not "br/".
1035                 if(len > 1 && ptr[len-1] == '/' )
1036                     ptr[--len] = '\0';
1037
1038                 // Look up the tagID for the specified tag name (now that we've shaved off any
1039                 // invalid / that might have followed the name).
1040                 unsigned short tagID = getTagID(ptr, len);
1041                 if (!tagID) {
1042                     DOMString tagName(ptr);
1043                     DocumentImpl *doc = parser->docPtr()->document();
1044                     if (doc->isValidName(tagName))
1045                         tagID = parser->docPtr()->document()->tagId(0, tagName.implementation(), false);
1046                 }
1047                 if (tagID) {
1048 #ifdef TOKEN_DEBUG
1049                     QCString tmp(ptr, len+1);
1050                     kdDebug( 6036 ) << "found tag id=" << tagID << ": " << tmp.data() << endl;
1051 #endif
1052                     currToken.id = beginTag ? tagID : tagID + ID_CLOSE_TAG;
1053                 }
1054                 dest = buffer;
1055                 tag = SearchAttribute;
1056                 cBufferPos = 0;
1057             }
1058             break;
1059         }
1060         case SearchAttribute:
1061         {
1062 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1063                 qDebug("SearchAttribute");
1064 #endif
1065             bool atespace = false;
1066             ushort curchar;
1067             while(!src.isEmpty()) {
1068                 curchar = *src;
1069                 // In this mode just ignore any quotes we encounter and treat them like spaces.
1070                 if (curchar > ' ' && curchar != '\'' && curchar != '"') {
1071                     if (curchar == '<' || curchar == '>')
1072                         tag = SearchEnd;
1073                     else
1074                         tag = AttributeName;
1075
1076                     cBufferPos = 0;
1077                     break;
1078                 }
1079                 atespace = true;
1080                 ++src;
1081             }
1082             break;
1083         }
1084         case AttributeName:
1085         {
1086 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1087                 qDebug("AttributeName");
1088 #endif
1089             ushort curchar;
1090             int ll = kMin(src.length(), CBUFLEN-cBufferPos);
1091
1092             while(ll--) {
1093                 curchar = *src;
1094                 if(curchar <= '>') {
1095                     if(curchar <= ' ' || curchar == '=' || curchar == '>') {
1096                         unsigned int a;
1097                         cBuffer[cBufferPos] = '\0';
1098                         a = getAttrID(cBuffer, cBufferPos);
1099                         if (a)
1100                             attrNamePresent = true;
1101                         else {
1102                             attrName = QString::fromLatin1(QCString(cBuffer, cBufferPos+1).data());
1103                             attrNamePresent = !attrName.isEmpty();
1104
1105                             // This is a deliberate quirk to match Mozilla and Opera.  We have to do this
1106                             // since sites that use the "standards-compliant" path sometimes send
1107                             // <script src="foo.js"/>.  Both Moz and Opera will honor this, despite it
1108                             // being bogus HTML.  They do not honor the "/" for other tags.  This behavior
1109                             // also deviates from WinIE, but in this case we'll just copy Moz and Opera.
1110                             if (currToken.id == ID_SCRIPT && curchar == '>' &&
1111                                 attrName == "/")
1112                                 currToken.flat = true;
1113                         }
1114                         
1115                         dest = buffer;
1116                         *dest++ = a;
1117 #ifdef TOKEN_DEBUG
1118                         if (!a || (cBufferPos && *cBuffer == '!'))
1119                             kdDebug( 6036 ) << "Unknown attribute: *" << QCString(cBuffer, cBufferPos+1).data() << "*" << endl;
1120                         else
1121                             kdDebug( 6036 ) << "Known attribute: " << QCString(cBuffer, cBufferPos+1).data() << endl;
1122 #endif
1123
1124                         tag = SearchEqual;
1125                         break;
1126                     }
1127                 }
1128                 // Use tolower() instead of | 0x20 to lowercase the char because there is no 
1129                 // performance gain in using | 0x20 since tolower() is optimized and 
1130                 // | 0x20 turns characters such as '_' into junk.
1131                 cBuffer[cBufferPos++] = tolower(curchar);
1132                 ++src;
1133             }
1134             if ( cBufferPos == CBUFLEN ) {
1135                 cBuffer[cBufferPos] = '\0';
1136                 attrName = QString::fromLatin1(QCString(cBuffer, cBufferPos+1).data());
1137                 attrNamePresent = !attrName.isEmpty();
1138                 dest = buffer;
1139                 *dest++ = 0;
1140                 tag = SearchEqual;
1141             }
1142             break;
1143         }
1144         case SearchEqual:
1145         {
1146 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1147                 qDebug("SearchEqual");
1148 #endif
1149             ushort curchar;
1150             bool atespace = false;
1151             while(!src.isEmpty()) {
1152                 curchar = src->unicode();
1153                 // In this mode just ignore any quotes we encounter and treat them like spaces.
1154                 if (curchar > ' ' && curchar != '\'' && curchar != '"') {
1155                     if(curchar == '=') {
1156 #ifdef TOKEN_DEBUG
1157                         kdDebug(6036) << "found equal" << endl;
1158 #endif
1159                         tag = SearchValue;
1160                         ++src;
1161                     }
1162                     else {
1163                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, emptyAtom);
1164                         dest = buffer;
1165                         tag = SearchAttribute;
1166                     }
1167                     break;
1168                 }
1169                 atespace = true;
1170                 ++src;
1171             }
1172             break;
1173         }
1174         case SearchValue:
1175         {
1176             ushort curchar;
1177             while(!src.isEmpty()) {
1178                 curchar = src->unicode();
1179                 if(curchar > ' ') {
1180                     if(( curchar == '\'' || curchar == '\"' )) {
1181                         tquote = curchar == '\"' ? DoubleQuote : SingleQuote;
1182                         tag = QuotedValue;
1183                         ++src;
1184                     } else
1185                         tag = Value;
1186
1187                     break;
1188                 }
1189                 ++src;
1190             }
1191             break;
1192         }
1193         case QuotedValue:
1194         {
1195 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1196                 qDebug("QuotedValue");
1197 #endif
1198             ushort curchar;
1199             while(!src.isEmpty()) {
1200                 checkBuffer();
1201
1202                 curchar = src->unicode();
1203                 if (curchar == '>' && !attrNamePresent) {
1204                     // Handle a case like <img '>.  Just go ahead and be willing
1205                     // to close the whole tag.  Don't consume the character and
1206                     // just go back into SearchEnd while ignoring the whole
1207                     // value.
1208                     // FIXME: Note that this is actually not a very good solution. It's
1209                     // an interim hack and doesn't handle the general case of
1210                     // unmatched quotes among attributes that have names. -dwh
1211                     while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1212                         dest--; // remove trailing newlines
1213                     AtomicString v(buffer+1, dest-buffer-1);
1214                     attrName.setUnicode(buffer+1,dest-buffer-1); 
1215                     currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1216                     tag = SearchAttribute;
1217                     dest = buffer;
1218                     tquote = NoQuote;
1219                     break;
1220                 }
1221                 
1222                 if(curchar <= '\'' && !src.escaped()) {
1223                     // ### attributes like '&{blaa....};' are supposed to be treated as jscript.
1224                     if ( curchar == '&' )
1225                     {
1226                         ++src;
1227                         parseEntity(src, dest, true);
1228                         break;
1229                     }
1230                     else if ( (tquote == SingleQuote && curchar == '\'') ||
1231                               (tquote == DoubleQuote && curchar == '\"') )
1232                     {
1233                         // some <input type=hidden> rely on trailing spaces. argh
1234                         while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1235                             dest--; // remove trailing newlines
1236                         AtomicString v(buffer+1, dest-buffer-1);
1237                         if (!attrNamePresent)
1238                             attrName.setUnicode(buffer+1,dest-buffer-1); 
1239                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1240
1241                         dest = buffer;
1242                         tag = SearchAttribute;
1243                         tquote = NoQuote;
1244                         ++src;
1245                         break;
1246                     }
1247                 }
1248                 *dest = *src;
1249                 fixUpChar(*dest);
1250                 ++dest;
1251                 ++src;
1252             }
1253             break;
1254         }
1255         case Value:
1256         {
1257 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1258             qDebug("Value");
1259 #endif
1260             ushort curchar;
1261             while(!src.isEmpty()) {
1262                 checkBuffer();
1263                 curchar = src->unicode();
1264                 if(curchar <= '>' && !src.escaped()) {
1265                     // parse Entities
1266                     if ( curchar == '&' )
1267                     {
1268                         ++src;
1269                         parseEntity(src, dest, true);
1270                         break;
1271                     }
1272                     // no quotes. Every space means end of value
1273                     // '/' does not delimit in IE!
1274                     if ( curchar <= ' ' || curchar == '>' )
1275                     {
1276                         AtomicString v(buffer+1, dest-buffer-1);
1277                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1278                         dest = buffer;
1279                         tag = SearchAttribute;
1280                         break;
1281                     }
1282                 }
1283
1284                 *dest = *src;
1285                 fixUpChar(*dest);
1286                 ++dest;
1287                 ++src;
1288             }
1289             break;
1290         }
1291         case SearchEnd:
1292         {
1293 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1294                 qDebug("SearchEnd");
1295 #endif
1296             while(!src.isEmpty()) {
1297                 if (*src == '>' || *src == '<')
1298                     break;
1299
1300                 if (*src == '/')
1301                     currToken.flat = true;
1302
1303                 ++src;
1304             }
1305             if (src.isEmpty()) break;
1306
1307             searchCount = 0; // Stop looking for '<!--' sequence
1308             tag = NoTag;
1309             tquote = NoQuote;
1310
1311             if (*src != '<')
1312                 ++src;
1313
1314             if ( !currToken.id ) //stop if tag is unknown
1315                 return;
1316
1317             uint tagID = currToken.id;
1318 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 0
1319             kdDebug( 6036 ) << "appending Tag: " << tagID << endl;
1320 #endif
1321             bool beginTag = !currToken.flat && (tagID <= ID_CLOSE_TAG);
1322
1323             if (tagID > ID_CLOSE_TAG)
1324                 tagID -= ID_CLOSE_TAG;
1325             else if (tagID == ID_SCRIPT) {
1326                 AttributeImpl* a = 0;
1327                 bool foundTypeAttribute = false;
1328                 scriptSrc = QString::null;
1329                 scriptSrcCharset = QString::null;
1330                 if ( currToken.attrs && /* potentially have a ATTR_SRC ? */
1331                      parser->doc()->part() &&
1332                      parser->doc()->part()->jScriptEnabled() && /* jscript allowed at all? */
1333                      view /* are we a regular tokenizer or just for innerHTML ? */
1334                     ) {
1335                     if ( ( a = currToken.attrs->getAttributeItem( ATTR_SRC ) ) )
1336                         scriptSrc = parser->doc()->completeURL(parseURL( a->value() ).string() );
1337                     if ( ( a = currToken.attrs->getAttributeItem( ATTR_CHARSET ) ) )
1338                         scriptSrcCharset = a->value().string().stripWhiteSpace();
1339                     if ( scriptSrcCharset.isEmpty() )
1340                         scriptSrcCharset = parser->doc()->part()->encoding();
1341                     /* Check type before language, since language is deprecated */
1342                     if ((a = currToken.attrs->getAttributeItem(ATTR_TYPE)) != 0 && !a->value().string().isEmpty())
1343                         foundTypeAttribute = true;
1344                     else
1345                         a = currToken.attrs->getAttributeItem(ATTR_LANGUAGE);
1346                 }
1347                 javascript = true;
1348
1349                 if( foundTypeAttribute ) {
1350                     /* 
1351                         Mozilla 1.5 accepts application/x-javascript, and some web references claim it is the only
1352                         correct variation, but WinIE 6 doesn't accept it.
1353                         Neither Mozilla 1.5 nor WinIE 6 accept application/javascript, application/ecmascript, or
1354                         application/x-ecmascript.
1355                         Mozilla 1.5 doesn't accept the text/javascript1.x formats, but WinIE 6 does.
1356                         Mozilla 1.5 doesn't accept text/jscript, text/ecmascript, and text/livescript, but WinIE 6 does.
1357                         Mozilla 1.5 allows leading and trailing whitespace, but WinIE 6 doesn't.
1358                         Mozilla 1.5 and WinIE 6 both accept the empty string, but neither accept a whitespace-only string.
1359                         We want to accept all the values that either of these browsers accept, but not other values.
1360                      */
1361                     QString type = a->value().string().stripWhiteSpace().lower();
1362                     if( type.compare("application/x-javascript") != 0 &&
1363                         type.compare("text/javascript") != 0 &&
1364                         type.compare("text/javascript1.0") != 0 &&
1365                         type.compare("text/javascript1.1") != 0 &&
1366                         type.compare("text/javascript1.2") != 0 &&
1367                         type.compare("text/javascript1.3") != 0 &&
1368                         type.compare("text/javascript1.4") != 0 &&
1369                         type.compare("text/javascript1.5") != 0 &&
1370                         type.compare("text/jscript") != 0 &&
1371                         type.compare("text/ecmascript") != 0 &&
1372                         type.compare("text/livescript") )
1373                         javascript = false;
1374                 } else if( a ) {
1375                     /* 
1376                      Mozilla 1.5 doesn't accept jscript or ecmascript, but WinIE 6 does.
1377                      Mozilla 1.5 accepts javascript1.0, javascript1.4, and javascript1.5, but WinIE 6 accepts only 1.1 - 1.3.
1378                      Neither Mozilla 1.5 nor WinIE 6 accept leading or trailing whitespace.
1379                      We want to accept all the values that either of these browsers accept, but not other values.
1380                      */
1381                     QString lang = a->value().string();
1382                     lang = lang.lower();
1383                     if( lang.compare("") != 0 &&
1384                         lang.compare("javascript") != 0 &&
1385                         lang.compare("javascript1.0") != 0 &&
1386                         lang.compare("javascript1.1") != 0 &&
1387                         lang.compare("javascript1.2") != 0 &&
1388                         lang.compare("javascript1.3") != 0 &&
1389                         lang.compare("javascript1.4") != 0 &&
1390                         lang.compare("javascript1.5") != 0 &&
1391                         lang.compare("ecmascript") != 0 &&
1392                         lang.compare("livescript") != 0 &&
1393                         lang.compare("jscript") )
1394                         javascript = false;
1395                 }
1396             }
1397
1398             processToken();
1399
1400             switch( tagID ) {
1401             case ID_PRE:
1402                 discard = LFDiscard; // Discard the first LF after we open a pre.
1403                 break;
1404             case ID_SCRIPT:
1405                 if (beginTag) {
1406                     searchStopper = scriptEnd;
1407                     searchStopperLen = 8;
1408                     script = true;
1409                     parseSpecial(src);
1410                 }
1411                 else if (tagID <= ID_CLOSE_TAG) { // Handle <script src="foo"/>
1412                     script = true;
1413                     scriptHandler();
1414                 }
1415                 break;
1416             case ID_STYLE:
1417                 if (beginTag) {
1418                     searchStopper = styleEnd;
1419                     searchStopperLen = 7;
1420                     style = true;
1421                     parseSpecial(src);
1422                 }
1423                 break;
1424             case ID_TEXTAREA:
1425                 if(beginTag) {
1426                     searchStopper = textareaEnd;
1427                     searchStopperLen = 10;
1428                     textarea = true;
1429                     parseSpecial(src);
1430                 }
1431                 break;
1432             case ID_TITLE:
1433                 if (beginTag) {
1434                     searchStopper = titleEnd;
1435                     searchStopperLen = 7;
1436                     title = true;
1437                     parseSpecial(src);
1438                 }
1439                 break;
1440             case ID_XMP:
1441                 if (beginTag) {
1442                     searchStopper = xmpEnd;
1443                     searchStopperLen = 5;
1444                     xmp = true;
1445                     parseSpecial(src);
1446                 }
1447                 break;
1448             case ID_SELECT:
1449                 select = beginTag;
1450                 break;
1451             case ID_PLAINTEXT:
1452                 plaintext = beginTag;
1453                 break;
1454             }
1455
1456             return; // Finished parsing tag!
1457         }
1458         } // end switch
1459     }
1460     return;
1461 }
1462
1463 void HTMLTokenizer::addPending()
1464 {
1465     if ( select && !script )
1466     {
1467         *dest++ = ' ';
1468     }
1469     else if ( textarea || script )
1470     {
1471         switch(pending) {
1472         case LFPending:  *dest++ = '\n'; break;
1473         case SpacePending: *dest++ = ' '; break;
1474         case NonePending:
1475             assert(0);
1476         }
1477     }
1478     else
1479     {
1480         switch (pending)
1481         {
1482         case SpacePending:
1483             // Insert a breaking space
1484             *dest++ = QChar(' ');
1485             break;
1486
1487         case LFPending:
1488             *dest = '\n';
1489             dest++;
1490             break;
1491
1492         case NonePending:
1493             assert(0);
1494             break;
1495         }
1496     }
1497     
1498     pending = NonePending;
1499 }
1500
1501 void HTMLTokenizer::write(const TokenizerString &str, bool appendData)
1502 {
1503 #ifdef TOKEN_DEBUG
1504     kdDebug( 6036 ) << this << " Tokenizer::write(\"" << str << "\"," << appendData << ")" << endl;
1505 #endif
1506
1507     if (!buffer)
1508         return;
1509     
1510     if (loadStopped)
1511         return;
1512
1513     if ( ( m_executingScript && appendData ) || !cachedScript.isEmpty() ) {
1514         // don't parse; we will do this later
1515         if (currentPrependingSrc) {
1516             currentPrependingSrc->append(str);
1517         } else {
1518             pendingSrc.append(str);
1519         }
1520         return;
1521     }
1522
1523     if ( onHold ) {
1524         src.append(str);
1525         return;
1526     }
1527     
1528     if (!src.isEmpty())
1529         src.append(str);
1530     else
1531         setSrc(str);
1532
1533     // Once a timer is set, it has control of when the tokenizer continues.
1534     if (timerId)
1535         return;
1536
1537     bool wasInWrite = inWrite;
1538     inWrite = true;
1539     
1540 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1541     if (!parser->doc()->ownerElement())
1542         printf("Beginning write at time %d\n", parser->doc()->elapsedTime());
1543 #endif
1544     
1545 //     if (Entity)
1546 //         parseEntity(src, dest);
1547
1548     int processedCount = 0;
1549     QTime startTime;
1550     startTime.start();
1551     KWQUIEventTime eventTime;
1552
1553     while (!src.isEmpty() && (!parser->doc()->part() || !parser->doc()->part()->isScheduledLocationChangePending())) {
1554         if (!continueProcessing(processedCount, startTime, eventTime))
1555             break;
1556
1557         // do we need to enlarge the buffer?
1558         checkBuffer();
1559
1560         ushort cc = src->unicode();
1561
1562         if (skipLF && (cc != '\n'))
1563             skipLF = false;
1564
1565         if (skipLF) {
1566             skipLF = false;
1567             ++src;
1568         }
1569         else if ( Entity )
1570             parseEntity( src, dest );
1571         else if ( plaintext )
1572             parseText( src );
1573         else if (script)
1574             parseSpecial(src);
1575         else if (style)
1576             parseSpecial(src);
1577         else if (xmp)
1578             parseSpecial(src);
1579         else if (textarea)
1580             parseSpecial(src);
1581         else if (title)
1582             parseSpecial(src);
1583         else if (comment)
1584             parseComment(src);
1585         else if (server)
1586             parseServer(src);
1587         else if (processingInstruction)
1588             parseProcessingInstruction(src);
1589         else if (tag)
1590             parseTag(src);
1591         else if ( startTag )
1592         {
1593             startTag = false;
1594
1595             switch(cc) {
1596             case '/':
1597                 break;
1598             case '!':
1599             {
1600                 // <!-- comment -->
1601                 searchCount = 1; // Look for '<!--' sequence to start comment
1602
1603                 break;
1604             }
1605             case '?':
1606             {
1607                 // xml processing instruction
1608                 processingInstruction = true;
1609                 tquote = NoQuote;
1610                 parseProcessingInstruction(src);
1611                 continue;
1612
1613                 break;
1614             }
1615             case '%':
1616                 if (!brokenServer) {
1617                     // <% server stuff, handle as comment %>
1618                     server = true;
1619                     tquote = NoQuote;
1620                     parseServer(src);
1621                     continue;
1622                 }
1623                 // else fall through
1624             default:
1625             {
1626                 if( ((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z')))
1627                 {
1628                     // Start of a Start-Tag
1629                 }
1630                 else
1631                 {
1632                     // Invalid tag
1633                     // Add as is
1634                     if (pending)
1635                         addPending();
1636                     *dest = '<';
1637                     dest++;
1638                     continue;
1639                 }
1640             }
1641             }; // end case
1642
1643             if ( pending ) {
1644                 if ( script || (!parser->selectMode() && (!parser->noSpaces() || dest > buffer )))
1645                     addPending();
1646                 // just forget it
1647                 else
1648                     pending = NonePending;
1649             }
1650
1651             if (cc == '/' && discard == AllDiscard)
1652                 discard = NoneDiscard; // A close tag. No need to discard LF.
1653                     
1654             processToken();
1655
1656             cBufferPos = 0;
1657             tag = TagName;
1658             parseTag(src);
1659         }
1660         else if ( cc == '&' && !src.escaped())
1661         {
1662             ++src;
1663             if ( pending )
1664                 addPending();
1665             parseEntity(src, dest, true);
1666         }
1667         else if ( cc == '<' && !src.escaped())
1668         {
1669             tagStartLineno = lineno+src.lineCount();
1670             ++src;
1671             startTag = true;
1672         }
1673         else if (( cc == '\n' ) || ( cc == '\r' ))
1674         {
1675             if (select && !script)
1676             {
1677                 if (discard == LFDiscard)
1678                 {
1679                     // Ignore this LF
1680                     discard = NoneDiscard; // We have discarded 1 LF
1681                 }
1682                 else if(discard == AllDiscard)
1683                 {
1684                 }
1685                 else
1686                  {
1687                      // Process this LF
1688                     if (pending == NonePending)
1689                          pending = LFPending;
1690                 }
1691             }
1692             else {
1693                 if (discard == LFDiscard || discard == AllDiscard)
1694                 {
1695                     // Ignore this LF
1696                     discard = NoneDiscard; // We have discarded 1 LF
1697                 }
1698                 else
1699                 {
1700                     // Process this LF
1701                     if (pending)
1702                         addPending();
1703                     pending = LFPending;
1704                 }
1705             }
1706             
1707             /* Check for MS-DOS CRLF sequence */
1708             if (cc == '\r')
1709             {
1710                 skipLF = true;
1711             }
1712             ++src;
1713         }
1714         else if (cc == ' ')
1715         {
1716             if (select && !script) {
1717                 if(discard == SpaceDiscard)
1718                     discard = NoneDiscard;
1719                  else if(discard == AllDiscard)
1720                  { }
1721                  else
1722                      pending = SpacePending;
1723             
1724             }
1725             else {
1726                 if (discard == AllDiscard)
1727                     discard = NoneDiscard;
1728             
1729                 if (pending)
1730                     addPending();
1731                 pending = SpacePending;
1732             }
1733             
1734             ++src;
1735         }
1736         else
1737         {
1738             if (pending)
1739                 addPending();
1740
1741             discard = NoneDiscard;
1742 #if QT_VERSION < 300
1743             unsigned char row = src->row();
1744             if ( row > 0x05 && row < 0x10 || row > 0xfd )
1745                     currToken.complexText = true;
1746 #endif
1747             *dest = *src;
1748             fixUpChar( *dest );
1749             ++dest;
1750             ++src;
1751         }
1752     }
1753     
1754 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1755     if (!parser->doc()->ownerElement())
1756         printf("Ending write at time %d\n", parser->doc()->elapsedTime());
1757 #endif
1758     
1759     inWrite = wasInWrite;
1760
1761     if (noMoreData && !inWrite && !loadingExtScript && !m_executingScript && !timerId)
1762         end(); // this actually causes us to be deleted
1763 }
1764
1765 void HTMLTokenizer::stopped()
1766 {
1767     if (timerId) {
1768         killTimer(timerId);
1769         timerId = 0;
1770     }
1771 }
1772
1773 bool HTMLTokenizer::processingData() const
1774 {
1775     return timerId != 0;
1776 }
1777
1778 bool HTMLTokenizer::continueProcessing(int& processedCount, const QTime& startTime, const KWQUIEventTime& eventTime)
1779 {
1780     // We don't want to be checking elapsed time with every character, so we only check after we've
1781     // processed a certain number of characters.
1782     bool allowedYield = allowYield;
1783     allowYield = false;
1784     if (!loadingExtScript && !forceSynchronous && !m_executingScript && (processedCount > TOKENIZER_CHUNK_SIZE || allowedYield)) {
1785         processedCount = 0;
1786         if (startTime.elapsed() > TOKENIZER_TIME_DELAY) {
1787             /* FIXME: We'd like to yield aggressively to give stylesheets the opportunity to
1788                load, but this hurts overall performance on slower machines.  For now turn this
1789                off.
1790             || (!parser->doc()->haveStylesheetsLoaded() && 
1791                 (parser->doc()->documentElement()->id() != ID_HTML || parser->doc()->body()))) {*/
1792             // Schedule the timer to keep processing as soon as possible.
1793             if (!timerId)
1794                 timerId = startTimer(0);
1795 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1796             if (eventTime.uiEventPending())
1797                 printf("Deferring processing of data because of UI event.\n");
1798             else if (startTime.elapsed() > TOKENIZER_TIME_DELAY)
1799                 printf("Deferring processing of data because 200ms elapsed away from event loop.\n");
1800 #endif
1801             return false;
1802         }
1803     }
1804     
1805     processedCount++;
1806     return true;
1807 }
1808
1809 void HTMLTokenizer::timerEvent(QTimerEvent* e)
1810 {
1811     if (e->timerId() == timerId) {
1812         // Kill the timer.
1813         killTimer(timerId);
1814         timerId = 0;
1815
1816 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1817         if (!parser->doc()->ownerElement())
1818             printf("Beginning timer write at time %d\n", parser->doc()->elapsedTime());
1819 #endif
1820
1821         if (parser->doc()->view() && parser->doc()->view()->layoutPending() && !parser->doc()->minimumLayoutDelay()) {
1822             // Restart the timer and let layout win.  This is basically a way of ensuring that the layout
1823             // timer has higher priority than our timer.
1824             timerId = startTimer(0);
1825             return;
1826         }
1827         
1828         // Invoke write() as though more data came in.
1829         bool oldNoMoreData = noMoreData;
1830         noMoreData = false;  // This prevents write() from deleting the tokenizer.
1831         write(TokenizerString(), true);
1832         noMoreData = oldNoMoreData;
1833         
1834         // If the timer dies (and stays dead after the write),  we need to let WebKit know that we're done processing the data.
1835         allDataProcessed();
1836     }
1837 }
1838
1839 void HTMLTokenizer::allDataProcessed()
1840 {
1841     if (noMoreData && !inWrite && !loadingExtScript && !m_executingScript && !onHold && !timerId) {
1842         QGuardedPtr<KHTMLView> savedView = view;
1843         end();
1844         if (savedView) {
1845             KHTMLPart *part = savedView->part();
1846             if (part) {
1847                 part->tokenizerProcessedData();
1848             }
1849         }
1850     }
1851 }
1852
1853 void HTMLTokenizer::end()
1854 {
1855     assert(timerId == 0);
1856     if (timerId) {
1857         // Clean up anyway.
1858         killTimer(timerId);
1859         timerId = 0;
1860     }
1861
1862     if ( buffer == 0 ) {
1863         parser->finished();
1864         emit finishedParsing();
1865         return;
1866     }
1867
1868     // parseTag is using the buffer for different matters
1869     if ( !tag )
1870         processToken();
1871
1872     if(buffer)
1873         KHTML_DELETE_QCHAR_VEC(buffer);
1874
1875     if(scriptCode)
1876         KHTML_DELETE_QCHAR_VEC(scriptCode);
1877
1878     scriptCode = 0;
1879     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1880     buffer = 0;
1881     parser->finished();
1882     emit finishedParsing();
1883 }
1884
1885 void HTMLTokenizer::finish()
1886 {
1887     // do this as long as we don't find matching comment ends
1888     while((comment || server) && scriptCode && scriptCodeSize)
1889     {
1890         // we've found an unmatched comment start
1891         if (comment)
1892             brokenComments = true;
1893         else
1894             brokenServer = true;
1895         checkScriptBuffer();
1896         scriptCode[ scriptCodeSize ] = 0;
1897         scriptCode[ scriptCodeSize + 1 ] = 0;
1898         int pos;
1899         QString food;
1900         if (script || style) {
1901             food.setUnicode(scriptCode, scriptCodeSize);
1902         }
1903         else if (server) {
1904             food = "<";
1905             food += QString(scriptCode, scriptCodeSize);
1906         }
1907         else {
1908             pos = QConstString(scriptCode, scriptCodeSize).string().find('>');
1909             food.setUnicode(scriptCode+pos+1, scriptCodeSize-pos-1); // deep copy
1910         }
1911         KHTML_DELETE_QCHAR_VEC(scriptCode);
1912         scriptCode = 0;
1913         scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1914         comment = server = false;
1915         if ( !food.isEmpty() )
1916             write(food, true);
1917     }
1918     // this indicates we will not receive any more data... but if we are waiting on
1919     // an external script to load, we can't finish parsing until that is done
1920     noMoreData = true;
1921     if (!inWrite && !loadingExtScript && !m_executingScript && !onHold && !timerId)
1922         end(); // this actually causes us to be deleted
1923 }
1924
1925 void HTMLTokenizer::processToken()
1926 {
1927     KJSProxy *jsProxy = (view && view->part()) ? view->part()->jScript() : 0L;    
1928     if (jsProxy)
1929         jsProxy->setEventHandlerLineno(tagStartLineno);
1930     if ( dest > buffer )
1931     {
1932 #ifdef TOKEN_DEBUG
1933         if(currToken.id) {
1934             qDebug( "unexpected token id: %d, str: *%s*", currToken.id,QConstString( buffer,dest-buffer ).string().latin1() );
1935             assert(0);
1936         }
1937
1938 #endif
1939         currToken.text = new DOMStringImpl( buffer, dest - buffer );
1940         currToken.text->ref();
1941         if (currToken.id != ID_COMMENT)
1942             currToken.id = ID_TEXT;
1943     }
1944     else if(!currToken.id) {
1945         currToken.reset();
1946         if (jsProxy)
1947             jsProxy->setEventHandlerLineno(lineno+src.lineCount());
1948         return;
1949     }
1950
1951     dest = buffer;
1952
1953 #ifdef TOKEN_DEBUG
1954     QString name = getTagName(currToken.id).string();
1955     QString text;
1956     if(currToken.text)
1957         text = QConstString(currToken.text->s, currToken.text->l).string();
1958
1959     kdDebug( 6036 ) << "Token --> " << name << "   id = " << currToken.id << endl;
1960     if (currToken.flat)
1961         kdDebug( 6036 ) << "Token is FLAT!" << endl;
1962     if(!text.isNull())
1963         kdDebug( 6036 ) << "text: \"" << text << "\"" << endl;
1964     unsigned long l = currToken.attrs ? currToken.attrs->length() : 0;
1965     if(l) {
1966         kdDebug( 6036 ) << "Attributes: " << l << endl;
1967         for (unsigned long i = 0; i < l; ++i) {
1968             AttributeImpl* c = currToken.attrs->attributeItem(i);
1969             kdDebug( 6036 ) << "    " << c->id() << " " << parser->doc()->getDocument()->attrName(c->id()).string()
1970                             << "=\"" << c->value().string() << "\"" << endl;
1971         }
1972     }
1973     kdDebug( 6036 ) << endl;
1974 #endif
1975     
1976     if (!loadStopped) {
1977         // pass the token over to the parser, the parser DOES NOT delete the token
1978         parser->parseToken(&currToken);
1979     }
1980     
1981     currToken.reset();
1982     if (jsProxy)
1983         jsProxy->setEventHandlerLineno(0);
1984 }
1985
1986 HTMLTokenizer::~HTMLTokenizer()
1987 {
1988     assert(!inWrite);
1989     reset();
1990     delete parser;
1991 }
1992
1993
1994 void HTMLTokenizer::enlargeBuffer(int len)
1995 {
1996     int newsize = kMax(size*2, size+len);
1997     int oldoffs = (dest - buffer);
1998
1999     buffer = (QChar*)realloc(buffer, newsize*sizeof(QChar));
2000     dest = buffer + oldoffs;
2001     size = newsize;
2002 }
2003
2004 void HTMLTokenizer::enlargeScriptBuffer(int len)
2005 {
2006     int newsize = kMax(scriptCodeMaxSize*2, scriptCodeMaxSize+len);
2007     scriptCode = (QChar*)realloc(scriptCode, newsize*sizeof(QChar));
2008     scriptCodeMaxSize = newsize;
2009 }
2010
2011 void HTMLTokenizer::notifyFinished(CachedObject */*finishedObj*/)
2012 {
2013 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
2014     if (!parser->doc()->ownerElement())
2015         printf("script loaded at %d\n", parser->doc()->elapsedTime());
2016 #endif
2017
2018     assert(!cachedScript.isEmpty());
2019     bool finished = false;
2020     while (!finished && cachedScript.head()->isLoaded()) {
2021 #ifdef TOKEN_DEBUG
2022         kdDebug( 6036 ) << "Finished loading an external script" << endl;
2023 #endif
2024         CachedScript* cs = cachedScript.dequeue();
2025         DOMString scriptSource = cs->script();
2026 #ifdef TOKEN_DEBUG
2027         kdDebug( 6036 ) << "External script is:" << endl << scriptSource.string() << endl;
2028 #endif
2029         setSrc(TokenizerString());
2030
2031         // make sure we forget about the script before we execute the new one
2032         // infinite recursion might happen otherwise
2033         QString cachedScriptUrl( cs->url().string() );
2034         cs->deref(this);
2035
2036 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
2037         if (!parser->doc()->ownerElement())
2038             printf("external script beginning execution at %d\n", parser->doc()->elapsedTime());
2039 #endif
2040
2041         scriptExecution( scriptSource.string(), cachedScriptUrl );
2042
2043         // The state of cachedScript.isEmpty() can change inside the scriptExecution()
2044         // call above, so test afterwards.
2045         finished = cachedScript.isEmpty();
2046         if (finished) {
2047             loadingExtScript = false;
2048 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
2049             if (!parser->doc()->ownerElement())
2050                 printf("external script finished execution at %d\n", parser->doc()->elapsedTime());
2051 #endif
2052         }
2053
2054         // 'script' is true when we are called synchronously from
2055         // parseScript(). In that case parseScript() will take care
2056         // of 'scriptOutput'.
2057         if ( !script ) {
2058             TokenizerString rest = pendingSrc;
2059             pendingSrc.clear();
2060             write(rest, false);
2061             // we might be deleted at this point, do not
2062             // access any members.
2063         }
2064     }
2065 }
2066
2067 bool HTMLTokenizer::isWaitingForScripts() const
2068 {
2069     return loadingExtScript;
2070 }
2071
2072 void HTMLTokenizer::setSrc(const TokenizerString &source)
2073 {
2074     lineno += src.lineCount();
2075     src = source;
2076     src.resetLineCount();
2077 }
2078
2079 void HTMLTokenizer::setOnHold(bool _onHold)
2080 {
2081     onHold = _onHold;
2082 }
2083
2084 }