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