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