dffbf38474c8066a9333702f3b679cc71bb74666
[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 == SpacePending) || (pending == TabPending))
396         addPending();
397     pending = NonePending;
398
399     prePos = 0;
400
401     pre = old_pre;
402 }
403
404 void HTMLTokenizer::parseSpecial(TokenizerString &src)
405 {
406     assert( textarea || title || !Entity );
407     assert( !tag );
408     assert( xmp+textarea+title+style+script == 1 );
409     if (script)
410         scriptStartLineno = lineno+src.lineCount();
411
412     if ( comment ) parseComment( src );
413
414     while ( !src.isEmpty() ) {
415         checkScriptBuffer();
416         unsigned char ch = src->latin1();
417         if ( !scriptCodeResync && !brokenComments && !textarea && !xmp && !title && ch == '-' && scriptCodeSize >= 3 && !src.escaped() && scriptCode[scriptCodeSize-3] == '<' && scriptCode[scriptCodeSize-2] == '!' && scriptCode[scriptCodeSize-1] == '-' ) {
418             comment = true;
419             parseComment( src );
420             continue;
421         }
422         if ( scriptCodeResync && !tquote && ( ch == '>' ) ) {
423             ++src;
424             scriptCodeSize = scriptCodeResync-1;
425             scriptCodeResync = 0;
426             scriptCode[ scriptCodeSize ] = scriptCode[ scriptCodeSize + 1 ] = 0;
427             if ( script )
428                 scriptHandler();
429             else {
430                 processListing(TokenizerString(scriptCode, scriptCodeSize));
431                 processToken();
432                 if ( style )         { currToken.id = ID_STYLE + ID_CLOSE_TAG; }
433                 else if ( textarea ) { currToken.id = ID_TEXTAREA + ID_CLOSE_TAG; }
434                 else if ( title ) { currToken.id = ID_TITLE + ID_CLOSE_TAG; }
435                 else if ( xmp )  { currToken.id = ID_XMP + ID_CLOSE_TAG; }
436                 processToken();
437                 style = script = style = textarea = title = xmp = false;
438                 tquote = NoQuote;
439                 scriptCodeSize = scriptCodeResync = 0;
440             }
441             return;
442         }
443         // possible end of tagname, lets check.
444         if ( !scriptCodeResync && !escaped && !src.escaped() && ( ch == '>' || ch == '/' || ch <= ' ' ) && ch &&
445              scriptCodeSize >= searchStopperLen &&
446              tagMatch( searchStopper, scriptCode+scriptCodeSize-searchStopperLen, searchStopperLen )) {
447             scriptCodeResync = scriptCodeSize-searchStopperLen+1;
448             tquote = NoQuote;
449             continue;
450         }
451         if ( scriptCodeResync && !escaped ) {
452             if(ch == '\"')
453                 tquote = (tquote == NoQuote) ? DoubleQuote : ((tquote == SingleQuote) ? SingleQuote : NoQuote);
454             else if(ch == '\'')
455                 tquote = (tquote == NoQuote) ? SingleQuote : (tquote == DoubleQuote) ? DoubleQuote : NoQuote;
456             else if (tquote != NoQuote && (ch == '\r' || ch == '\n'))
457                 tquote = NoQuote;
458         }
459         escaped = ( !escaped && ch == '\\' );
460         if (!scriptCodeResync && (textarea||title) && !src.escaped() && ch == '&') {
461             QChar *scriptCodeDest = scriptCode+scriptCodeSize;
462             ++src;
463             parseEntity(src,scriptCodeDest,true);
464             scriptCodeSize = scriptCodeDest-scriptCode;
465         }
466         else {
467             scriptCode[scriptCodeSize] = *src;
468             fixUpChar(scriptCode[scriptCodeSize]);
469             ++scriptCodeSize;
470             ++src;
471         }
472     }
473 }
474
475 void HTMLTokenizer::scriptHandler()
476 {
477     // We are inside a <script>
478     bool doScriptExec = false;
479     CachedScript* cs = 0;
480     // don't load external scripts for standalone documents (for now)
481     if (!scriptSrc.isEmpty() && parser->doc()->part()) {
482         // forget what we just got; load from src url instead
483         if ( !parser->skipMode() ) {
484             if ( (cs = parser->doc()->docLoader()->requestScript(scriptSrc, scriptSrcCharset) ))
485                 cachedScript.enqueue(cs);
486         }
487         scriptSrc=QString::null;
488     }
489     else {
490 #ifdef TOKEN_DEBUG
491         kdDebug( 6036 ) << "---START SCRIPT---" << endl;
492         kdDebug( 6036 ) << QString(scriptCode, scriptCodeSize) << endl;
493         kdDebug( 6036 ) << "---END SCRIPT---" << endl;
494 #endif
495         // Parse scriptCode containing <script> info
496         doScriptExec = true;
497     }
498     processListing(TokenizerString(scriptCode, scriptCodeSize));
499     QString exScript( buffer, dest-buffer );
500     processToken();
501     currToken.id = ID_SCRIPT + ID_CLOSE_TAG;
502     processToken();
503
504     TokenizerString *savedPrependingSrc = currentPrependingSrc;
505     TokenizerString prependingSrc;
506     currentPrependingSrc = &prependingSrc;
507     if ( !parser->skipMode() ) {
508         if (cs) {
509              //kdDebug( 6036 ) << "cachedscript extern!" << endl;
510              //kdDebug( 6036 ) << "src: *" << QString( src.current(), src.length() ).latin1() << "*" << endl;
511              //kdDebug( 6036 ) << "pending: *" << pendingSrc.latin1() << "*" << endl;
512             if (savedPrependingSrc) {
513                 savedPrependingSrc->append(src);
514             } else {
515                 pendingSrc.prepend(src);
516             }
517             setSrc(TokenizerString());
518             scriptCodeSize = scriptCodeResync = 0;
519             cs->ref(this);
520             // will be 0 if script was already loaded and ref() executed it
521             if (cachedScript.count())
522                 loadingExtScript = true;
523         }
524         else if (view && doScriptExec && javascript ) {
525             if (!m_executingScript)
526                 pendingSrc.prepend(src);
527             else
528                 prependingSrc = src;
529             setSrc(TokenizerString());
530             scriptCodeSize = scriptCodeResync = 0;
531             //QTime dt;
532             //dt.start();
533             scriptExecution( exScript, QString::null, scriptStartLineno );
534             //kdDebug( 6036 ) << "script execution time:" << dt.elapsed() << endl;
535         }
536     }
537
538     script = false;
539     scriptCodeSize = scriptCodeResync = 0;
540
541     if ( !m_executingScript && !loadingExtScript ) {
542         // kdDebug( 6036 ) << "adding pending Output to parsed string" << endl;
543         src.append(pendingSrc);
544         pendingSrc.clear();
545     } else if (!prependingSrc.isEmpty()) {
546         // restore first so that the write appends in the right place
547         // (does not hurt to do it again below)
548         currentPrependingSrc = savedPrependingSrc;
549
550         // we need to do this slightly modified bit of one of the write() cases
551         // because we want to prepend to pendingSrc rather than appending
552         // if there's no previous prependingSrc
553         if (loadingExtScript) {
554             if (currentPrependingSrc) {
555                 currentPrependingSrc->append(prependingSrc);
556             } else {
557                 pendingSrc.prepend(prependingSrc);
558             }
559         } else {
560             write(prependingSrc, false);
561         }
562     }
563
564     currentPrependingSrc = savedPrependingSrc;
565 }
566
567 void HTMLTokenizer::scriptExecution( const QString& str, QString scriptURL,
568                                      int baseLine)
569 {
570 #if APPLE_CHANGES
571     if (!view || !view->part())
572         return;
573 #endif
574     bool oldscript = script;
575     m_executingScript++;
576     script = false;
577     QString url;    
578     if (scriptURL.isNull())
579       url = static_cast<DocumentImpl*>(view->part()->document().handle())->URL();
580     else
581       url = scriptURL;
582
583     TokenizerString *savedPrependingSrc = currentPrependingSrc;
584     TokenizerString prependingSrc;
585     currentPrependingSrc = &prependingSrc;
586
587     view->part()->executeScript(url,baseLine,Node(),str);
588
589     m_executingScript--;
590     script = oldscript;
591
592     if ( !m_executingScript && !loadingExtScript ) {
593         // kdDebug( 6036 ) << "adding pending Output to parsed string" << endl;
594         src.append(pendingSrc);
595         pendingSrc.clear();
596     } else if (!prependingSrc.isEmpty()) {
597         // restore first so that the write appends in the right place
598         // (does not hurt to do it again below)
599         currentPrependingSrc = savedPrependingSrc;
600
601         // we need to do this slightly modified bit of one of the write() cases
602         // because we want to prepend to pendingSrc rather than appending
603         // if there's no previous prependingSrc
604         if (loadingExtScript) {
605             if (currentPrependingSrc) {
606                 currentPrependingSrc->append(prependingSrc);
607             } else {
608                 pendingSrc.prepend(prependingSrc);
609             }
610         } else {
611             write(prependingSrc, false);
612         }
613     }
614
615     currentPrependingSrc = savedPrependingSrc;
616 }
617
618 void HTMLTokenizer::parseComment(TokenizerString &src)
619 {
620     checkScriptBuffer(src.length());
621     while ( !src.isEmpty() ) {
622         scriptCode[ scriptCodeSize++ ] = *src;
623 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
624         qDebug("comment is now: *%s*",
625                QConstString((QChar*)src.current(), QMIN(16, src.length())).string().latin1());
626 #endif
627         if (src->unicode() == '>' &&
628             ( ( brokenComments && !( script || style ) ) ||
629               ( scriptCodeSize > 2 && scriptCode[scriptCodeSize-3] == '-' &&
630                 scriptCode[scriptCodeSize-2] == '-' ) ||
631               // Other browsers will accept --!> as a close comment, even though it's
632               // not technically valid.
633               ( scriptCodeSize > 3 && scriptCode[scriptCodeSize-4] == '-' &&
634                 scriptCode[scriptCodeSize-3] == '-' &&
635                 scriptCode[scriptCodeSize-2] == '!' ) ) ) {
636             ++src;
637             if ( !( script || xmp || textarea || style) ) {
638 #ifdef COMMENTS_IN_DOM
639                 checkScriptBuffer();
640                 scriptCode[ scriptCodeSize ] = 0;
641                 scriptCode[ scriptCodeSize + 1 ] = 0;
642                 currToken.id = ID_COMMENT;
643                 processListing(TokenizerString(scriptCode, scriptCodeSize - 2));
644                 processToken();
645                 currToken.id = ID_COMMENT + ID_CLOSE_TAG;
646                 processToken();
647 #endif
648                 scriptCodeSize = 0;
649             }
650             comment = false;
651             return; // Finished parsing comment
652         }
653         ++src;
654     }
655 }
656
657 void HTMLTokenizer::parseServer(TokenizerString &src)
658 {
659     checkScriptBuffer(src.length());
660     while ( !src.isEmpty() ) {
661         scriptCode[ scriptCodeSize++ ] = *src;
662         if (src->unicode() == '>' &&
663             scriptCodeSize > 1 && scriptCode[scriptCodeSize-2] == '%') {
664             ++src;
665             server = false;
666             scriptCodeSize = 0;
667             return; // Finished parsing server include
668         }
669         ++src;
670     }
671 }
672
673 void HTMLTokenizer::parseProcessingInstruction(TokenizerString &src)
674 {
675     char oldchar = 0;
676     while ( !src.isEmpty() )
677     {
678         unsigned char chbegin = src->latin1();
679         if(chbegin == '\'') {
680             tquote = tquote == SingleQuote ? NoQuote : SingleQuote;
681         }
682         else if(chbegin == '\"') {
683             tquote = tquote == DoubleQuote ? NoQuote : DoubleQuote;
684         }
685         // Look for '?>'
686         // some crappy sites omit the "?" before it, so
687         // we look for an unquoted '>' instead. (IE compatible)
688         else if ( chbegin == '>' && ( !tquote || oldchar == '?' ) )
689         {
690             // We got a '?>' sequence
691             processingInstruction = false;
692             ++src;
693             discard=LFDiscard;
694             return; // Finished parsing comment!
695         }
696         ++src;
697         oldchar = chbegin;
698     }
699 }
700
701 void HTMLTokenizer::parseText(TokenizerString &src)
702 {
703     while ( !src.isEmpty() )
704     {
705         // do we need to enlarge the buffer?
706         checkBuffer();
707
708         // ascii is okay because we only do ascii comparisons
709         unsigned char chbegin = src->latin1();
710
711         if (skipLF && ( chbegin != '\n' ))
712         {
713             skipLF = false;
714         }
715
716         if (skipLF)
717         {
718             skipLF = false;
719             ++src;
720         }
721         else if (( chbegin == '\n' ) || ( chbegin == '\r' ))
722         {
723             if (chbegin == '\r')
724                 skipLF = true;
725
726             *dest++ = '\n';
727             ++src;
728         }
729         else {
730             *dest = *src;
731             fixUpChar(*dest);
732             ++dest;
733             ++src;
734         }
735     }
736 }
737
738
739 void HTMLTokenizer::parseEntity(TokenizerString &src, QChar *&dest, bool start)
740 {
741     if( start )
742     {
743         cBufferPos = 0;
744         Entity = SearchEntity;
745         EntityUnicodeValue = 0;
746     }
747
748     while( !src.isEmpty() )
749     {
750         ushort cc = src->unicode();
751         switch(Entity) {
752         case NoEntity:
753             assert(Entity != NoEntity);
754             return;
755         
756         case SearchEntity:
757             if(cc == '#') {
758                 cBuffer[cBufferPos++] = cc;
759                 ++src;
760                 Entity = NumericSearch;
761             }
762             else
763                 Entity = EntityName;
764
765             break;
766
767         case NumericSearch:
768             if(cc == 'x' || cc == 'X') {
769                 cBuffer[cBufferPos++] = cc;
770                 ++src;
771                 Entity = Hexadecimal;
772             }
773             else if(cc >= '0' && cc <= '9')
774                 Entity = Decimal;
775             else
776                 Entity = SearchSemicolon;
777
778             break;
779
780         case Hexadecimal:
781         {
782             int ll = kMin(src.length(), 8);
783             while(ll--) {
784                 QChar csrc(src->lower());
785                 cc = csrc.cell();
786
787                 if(csrc.row() || !((cc >= '0' && cc <= '9') || (cc >= 'a' && cc <= 'f'))) {
788                     break;
789                 }
790                 EntityUnicodeValue = EntityUnicodeValue*16 + (cc - ( cc < 'a' ? '0' : 'a' - 10));
791                 cBuffer[cBufferPos++] = cc;
792                 ++src;
793             }
794             Entity = SearchSemicolon;
795             break;
796         }
797         case Decimal:
798         {
799             int ll = kMin(src.length(), 9-cBufferPos);
800             while(ll--) {
801                 cc = src->cell();
802
803                 if(src->row() || !(cc >= '0' && cc <= '9')) {
804                     Entity = SearchSemicolon;
805                     break;
806                 }
807
808                 EntityUnicodeValue = EntityUnicodeValue * 10 + (cc - '0');
809                 cBuffer[cBufferPos++] = cc;
810                 ++src;
811             }
812             if(cBufferPos == 9)  Entity = SearchSemicolon;
813             break;
814         }
815         case EntityName:
816         {
817             int ll = kMin(src.length(), 9-cBufferPos);
818             while(ll--) {
819                 QChar csrc = *src;
820                 cc = csrc.cell();
821
822                 if(csrc.row() || !((cc >= 'a' && cc <= 'z') ||
823                                    (cc >= '0' && cc <= '9') || (cc >= 'A' && cc <= 'Z'))) {
824                     Entity = SearchSemicolon;
825                     break;
826                 }
827
828                 cBuffer[cBufferPos++] = cc;
829                 ++src;
830             }
831             if(cBufferPos == 9) Entity = SearchSemicolon;
832             if(Entity == SearchSemicolon) {
833                 if(cBufferPos > 1) {
834                     const entity *e = findEntity(cBuffer, cBufferPos);
835                     if(e)
836                         EntityUnicodeValue = e->code;
837
838                     // be IE compatible
839                     if(tag && EntityUnicodeValue > 255 && *src != ';')
840                         EntityUnicodeValue = 0;
841                 }
842             }
843             else
844                 break;
845         }
846         case SearchSemicolon:
847
848             //kdDebug( 6036 ) << "ENTITY " << EntityUnicodeValue << ", " << res << endl;
849
850             // Don't allow surrogate code points, or values that are more than 21 bits.
851             if ((EntityUnicodeValue > 0 && EntityUnicodeValue < 0xD800)
852                     || (EntityUnicodeValue >= 0xE000 && EntityUnicodeValue <= 0x1FFFFF)) {
853             
854                 if (*src == ';')
855                     ++src;
856
857                 if (EntityUnicodeValue <= 0xFFFF) {
858                     QChar c(EntityUnicodeValue);
859                     fixUpChar(c);
860                     checkBuffer();
861                     src.push(c);
862                 } else {
863                     // Convert to UTF-16, using surrogate code points.
864                     QChar c1(0xD800 | (((EntityUnicodeValue >> 16) - 1) << 6) | ((EntityUnicodeValue >> 10) & 0x3F));
865                     QChar c2(0xDC00 | (EntityUnicodeValue & 0x3FF));
866                     checkBuffer(2);
867                     src.push(c1);
868                     src.push(c2);
869                 }
870
871             } else {
872 #ifdef TOKEN_DEBUG
873                 kdDebug( 6036 ) << "unknown entity!" << endl;
874 #endif
875                 checkBuffer(10);
876                 // ignore the sequence, add it to the buffer as plaintext
877                 *dest++ = '&';
878                 for(unsigned int i = 0; i < cBufferPos; i++)
879                     dest[i] = cBuffer[i];
880                 dest += cBufferPos;
881                 if (pre)
882                     prePos += cBufferPos+1;
883             }
884
885             Entity = NoEntity;
886             return;
887         }
888     }
889 }
890
891 void HTMLTokenizer::parseTag(TokenizerString &src)
892 {
893     assert(!Entity );
894
895     while ( !src.isEmpty() )
896     {
897         checkBuffer();
898 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
899         uint l = 0;
900         while(l < src.length() && (*(src.current()+l)).latin1() != '>')
901             l++;
902         qDebug("src is now: *%s*, tquote: %d",
903                QConstString((QChar*)src.current(), l).string().latin1(), tquote);
904 #endif
905         switch(tag) {
906         case NoTag:
907         {
908             return;
909         }
910         case TagName:
911         {
912 #if defined(TOKEN_DEBUG) &&  TOKEN_DEBUG > 1
913             qDebug("TagName");
914 #endif
915             if (searchCount > 0)
916             {
917                 if (*src == commentStart[searchCount])
918                 {
919                     searchCount++;
920                     if (searchCount == 4)
921                     {
922 #ifdef TOKEN_DEBUG
923                         kdDebug( 6036 ) << "Found comment" << endl;
924 #endif
925                         // Found '<!--' sequence
926                         ++src;
927                         dest = buffer; // ignore the previous part of this tag
928                         comment = true;
929                         tag = NoTag;
930
931                         // Fix bug 34302 at kde.bugs.org.  Go ahead and treat
932                         // <!--> as a valid comment, since both mozilla and IE on windows
933                         // can handle this case.  Only do this in quirks mode. -dwh
934                         if (!src.isEmpty() && *src == '>' && parser->doc()->inCompatMode()) {
935                           comment = false;
936                           ++src;
937                           if (!src.isEmpty())
938                               cBuffer[cBufferPos++] = src->cell();
939                         }
940                         else
941                           parseComment(src);
942
943                         return; // Finished parsing tag!
944                     }
945                     // cuts of high part, is okay
946                     cBuffer[cBufferPos++] = src->cell();
947                     ++src;
948                     break;
949                 }
950                 else
951                     searchCount = 0; // Stop looking for '<!--' sequence
952             }
953
954             bool finish = false;
955             unsigned int ll = kMin(src.length(), CBUFLEN-cBufferPos);
956             while(ll--) {
957                 ushort curchar = *src;
958                 if(curchar <= ' ' || curchar == '>' ) {
959                     finish = true;
960                     break;
961                 }
962                 // Use tolower() instead of | 0x20 to lowercase the char because there is no 
963                 // performance gain in using | 0x20 since tolower() is optimized and 
964                 // | 0x20 turns characters such as '_' into junk.
965                 cBuffer[cBufferPos++] = tolower(curchar);
966                 ++src;
967             }
968
969             // Disadvantage: we add the possible rest of the tag
970             // as attribute names. ### judge if this causes problems
971             if(finish || CBUFLEN == cBufferPos) {
972                 bool beginTag;
973                 char* ptr = cBuffer;
974                 unsigned int len = cBufferPos;
975                 cBuffer[cBufferPos] = '\0';
976                 if ((cBufferPos > 0) && (*ptr == '/'))
977                 {
978                     // End Tag
979                     beginTag = false;
980                     ptr++;
981                     len--;
982                 }
983                 else
984                     // Start Tag
985                     beginTag = true;
986
987                 // Accept empty xml tags like <br/>.  We trim off the "/" so that when we call
988                 // getTagID, we'll look up "br" as the tag name and not "br/".
989                 if(len > 1 && ptr[len-1] == '/' )
990                     ptr[--len] = '\0';
991
992                 // Look up the tagID for the specified tag name (now that we've shaved off any
993                 // invalid / that might have followed the name).
994                 uint tagID = getTagID(ptr, len);
995                 if (!tagID) {
996 #ifdef TOKEN_DEBUG
997                     QCString tmp(ptr, len+1);
998                     kdDebug( 6036 ) << "Unknown tag: \"" << tmp.data() << "\"" << endl;
999 #endif
1000                     dest = buffer;
1001                 }
1002                 else
1003                 {
1004 #ifdef TOKEN_DEBUG
1005                     QCString tmp(ptr, len+1);
1006                     kdDebug( 6036 ) << "found tag id=" << tagID << ": " << tmp.data() << endl;
1007 #endif
1008                     currToken.id = beginTag ? tagID : tagID + ID_CLOSE_TAG;
1009                     dest = buffer;
1010                 }
1011                 tag = SearchAttribute;
1012                 cBufferPos = 0;
1013             }
1014             break;
1015         }
1016         case SearchAttribute:
1017         {
1018 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1019                 qDebug("SearchAttribute");
1020 #endif
1021             bool atespace = false;
1022             ushort curchar;
1023             while(!src.isEmpty()) {
1024                 curchar = *src;
1025                 if(curchar > ' ') {
1026                     if (curchar == '<' || curchar == '>')
1027                         tag = SearchEnd;
1028                     else if(atespace && (curchar == '\'' || curchar == '"'))
1029                     {
1030                         tag = SearchValue;
1031                         *dest++ = 0;
1032                         attrName = QString::null;
1033                         attrNamePresent = false;
1034                     }
1035                     else
1036                         tag = AttributeName;
1037
1038                     cBufferPos = 0;
1039                     break;
1040                 }
1041                 atespace = true;
1042                 ++src;
1043             }
1044             break;
1045         }
1046         case AttributeName:
1047         {
1048 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1049                 qDebug("AttributeName");
1050 #endif
1051             ushort curchar;
1052             int ll = kMin(src.length(), CBUFLEN-cBufferPos);
1053
1054             while(ll--) {
1055                 curchar = *src;
1056                 if(curchar <= '>') {
1057                     if(curchar <= ' ' || curchar == '=' || curchar == '>') {
1058                         unsigned int a;
1059                         cBuffer[cBufferPos] = '\0';
1060                         a = getAttrID(cBuffer, cBufferPos);
1061                         if (a)
1062                             attrNamePresent = true;
1063                         else {
1064                             attrName = QString::fromLatin1(QCString(cBuffer, cBufferPos+1).data());
1065                             attrNamePresent = !attrName.isEmpty();
1066
1067                             // This is a deliberate quirk to match Mozilla and Opera.  We have to do this
1068                             // since sites that use the "standards-compliant" path sometimes send
1069                             // <script src="foo.js"/>.  Both Moz and Opera will honor this, despite it
1070                             // being bogus HTML.  They do not honor the "/" for other tags.  This behavior
1071                             // also deviates from WinIE, but in this case we'll just copy Moz and Opera.
1072                             if (currToken.id == ID_SCRIPT && curchar == '>' &&
1073                                 attrName == "/")
1074                                 currToken.flat = true;
1075                         }
1076                         
1077                         dest = buffer;
1078                         *dest++ = a;
1079 #ifdef TOKEN_DEBUG
1080                         if (!a || (cBufferPos && *cBuffer == '!'))
1081                             kdDebug( 6036 ) << "Unknown attribute: *" << QCString(cBuffer, cBufferPos+1).data() << "*" << endl;
1082                         else
1083                             kdDebug( 6036 ) << "Known attribute: " << QCString(cBuffer, cBufferPos+1).data() << endl;
1084 #endif
1085
1086                         tag = SearchEqual;
1087                         break;
1088                     }
1089                 }
1090                 // Use tolower() instead of | 0x20 to lowercase the char because there is no 
1091                 // performance gain in using | 0x20 since tolower() is optimized and 
1092                 // | 0x20 turns characters such as '_' into junk.
1093                 cBuffer[cBufferPos++] = tolower(curchar);
1094                 ++src;
1095             }
1096             if ( cBufferPos == CBUFLEN ) {
1097                 cBuffer[cBufferPos] = '\0';
1098                 attrName = QString::fromLatin1(QCString(cBuffer, cBufferPos+1).data());
1099                 attrNamePresent = !attrName.isEmpty();
1100                 dest = buffer;
1101                 *dest++ = 0;
1102                 tag = SearchEqual;
1103             }
1104             break;
1105         }
1106         case SearchEqual:
1107         {
1108 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1109                 qDebug("SearchEqual");
1110 #endif
1111             ushort curchar;
1112             bool atespace = false;
1113             while(!src.isEmpty()) {
1114                 curchar = src->unicode();
1115                 if(curchar > ' ') {
1116                     if(curchar == '=') {
1117 #ifdef TOKEN_DEBUG
1118                         kdDebug(6036) << "found equal" << endl;
1119 #endif
1120                         tag = SearchValue;
1121                         ++src;
1122                     }
1123                     else if(atespace && (curchar == '\'' || curchar == '"'))
1124                     {
1125                         tag = SearchValue;
1126                         *dest++ = 0;
1127                         attrName = QString::null;
1128                         attrNamePresent = false;
1129                     }
1130                     else {
1131                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, emptyAtom);
1132                         dest = buffer;
1133                         tag = SearchAttribute;
1134                     }
1135                     break;
1136                 }
1137                 atespace = true;
1138                 ++src;
1139             }
1140             break;
1141         }
1142         case SearchValue:
1143         {
1144             ushort curchar;
1145             while(!src.isEmpty()) {
1146                 curchar = src->unicode();
1147                 if(curchar > ' ') {
1148                     if(( curchar == '\'' || curchar == '\"' )) {
1149                         tquote = curchar == '\"' ? DoubleQuote : SingleQuote;
1150                         tag = QuotedValue;
1151                         ++src;
1152                     } else
1153                         tag = Value;
1154
1155                     break;
1156                 }
1157                 ++src;
1158             }
1159             break;
1160         }
1161         case QuotedValue:
1162         {
1163 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1164                 qDebug("QuotedValue");
1165 #endif
1166             ushort curchar;
1167             while(!src.isEmpty()) {
1168                 checkBuffer();
1169
1170                 curchar = src->unicode();
1171                 if (curchar == '>' && !attrNamePresent) {
1172                     // Handle a case like <img '>.  Just go ahead and be willing
1173                     // to close the whole tag.  Don't consume the character and
1174                     // just go back into SearchEnd while ignoring the whole
1175                     // value.
1176                     // FIXME: Note that this is actually not a very good solution. It's
1177                     // an interim hack and doesn't handle the general case of
1178                     // unmatched quotes among attributes that have names. -dwh
1179                     while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1180                         dest--; // remove trailing newlines
1181                     AtomicString v(buffer+1, dest-buffer-1);
1182                     attrName.setUnicode(buffer+1,dest-buffer-1); 
1183                     currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1184                     tag = SearchAttribute;
1185                     dest = buffer;
1186                     tquote = NoQuote;
1187                     break;
1188                 }
1189                 
1190                 if(curchar <= '\'' && !src.escaped()) {
1191                     // ### attributes like '&{blaa....};' are supposed to be treated as jscript.
1192                     if ( curchar == '&' )
1193                     {
1194                         ++src;
1195                         parseEntity(src, dest, true);
1196                         break;
1197                     }
1198                     else if ( (tquote == SingleQuote && curchar == '\'') ||
1199                               (tquote == DoubleQuote && curchar == '\"') )
1200                     {
1201                         // some <input type=hidden> rely on trailing spaces. argh
1202                         while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1203                             dest--; // remove trailing newlines
1204                         AtomicString v(buffer+1, dest-buffer-1);
1205                         if (!attrNamePresent)
1206                             attrName.setUnicode(buffer+1,dest-buffer-1); 
1207                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1208
1209                         dest = buffer;
1210                         tag = SearchAttribute;
1211                         tquote = NoQuote;
1212                         ++src;
1213                         break;
1214                     }
1215                 }
1216                 *dest = *src;
1217                 fixUpChar(*dest);
1218                 ++dest;
1219                 ++src;
1220             }
1221             break;
1222         }
1223         case Value:
1224         {
1225 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1226             qDebug("Value");
1227 #endif
1228             ushort curchar;
1229             while(!src.isEmpty()) {
1230                 checkBuffer();
1231                 curchar = src->unicode();
1232                 if(curchar <= '>' && !src.escaped()) {
1233                     // parse Entities
1234                     if ( curchar == '&' )
1235                     {
1236                         ++src;
1237                         parseEntity(src, dest, true);
1238                         break;
1239                     }
1240                     // no quotes. Every space means end of value
1241                     // '/' does not delimit in IE!
1242                     if ( curchar <= ' ' || curchar == '>' )
1243                     {
1244                         AtomicString v(buffer+1, dest-buffer-1);
1245                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1246                         dest = buffer;
1247                         tag = SearchAttribute;
1248                         break;
1249                     }
1250                 }
1251
1252                 *dest = *src;
1253                 fixUpChar(*dest);
1254                 ++dest;
1255                 ++src;
1256             }
1257             break;
1258         }
1259         case SearchEnd:
1260         {
1261 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1262                 qDebug("SearchEnd");
1263 #endif
1264             while(!src.isEmpty()) {
1265                 if (*src == '>' || *src == '<')
1266                     break;
1267
1268                 if (*src == '/')
1269                     currToken.flat = true;
1270
1271                 ++src;
1272             }
1273             if (src.isEmpty()) break;
1274
1275             searchCount = 0; // Stop looking for '<!--' sequence
1276             tag = NoTag;
1277             tquote = NoQuote;
1278
1279             if (*src != '<')
1280                 ++src;
1281
1282             if ( !currToken.id ) //stop if tag is unknown
1283                 return;
1284
1285             uint tagID = currToken.id;
1286 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 0
1287             kdDebug( 6036 ) << "appending Tag: " << tagID << endl;
1288 #endif
1289             bool beginTag = !currToken.flat && (tagID < ID_CLOSE_TAG);
1290
1291             if (tagID >= ID_CLOSE_TAG)
1292                 tagID -= ID_CLOSE_TAG;
1293             else if (tagID == ID_SCRIPT) {
1294                 AttributeImpl* a = 0;
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                     if (!(a = currToken.attrs->getAttributeItem( ATTR_LANGUAGE )))
1309                         a = currToken.attrs->getAttributeItem(ATTR_TYPE);
1310                 }
1311                 javascript = true;
1312                 if( a ) {
1313                     QString lang = a->value().string();
1314                     lang = lang.lower();
1315                     if( !lang.contains("javascript") &&
1316                         !lang.contains("ecmascript") &&
1317                         !lang.contains("livescript") &&
1318                         !lang.contains("jscript") )
1319                         javascript = false;
1320                 }
1321             }
1322
1323             processToken();
1324
1325             // we have to take care to close the pre block in
1326             // case we encounter an unallowed element....
1327             if(pre && beginTag && !DOM::checkChild(ID_PRE, tagID)) {
1328                 kdDebug(6036) << " not allowed in <pre> " << (int)tagID << endl;
1329                 pre = false;
1330             }
1331
1332             switch( tagID ) {
1333             case ID_PRE:
1334                 prePos = 0;
1335                 pre = beginTag;
1336                 break;
1337             case ID_SCRIPT:
1338                 if (beginTag) {
1339                     searchStopper = scriptEnd;
1340                     searchStopperLen = 8;
1341                     script = true;
1342                     parseSpecial(src);
1343                 }
1344                 else if (tagID < ID_CLOSE_TAG) // Handle <script src="foo"/>
1345                     scriptHandler();
1346                 break;
1347             case ID_STYLE:
1348                 if (beginTag) {
1349                     searchStopper = styleEnd;
1350                     searchStopperLen = 7;
1351                     style = true;
1352                     parseSpecial(src);
1353                 }
1354                 break;
1355             case ID_TEXTAREA:
1356                 if(beginTag) {
1357                     searchStopper = textareaEnd;
1358                     searchStopperLen = 10;
1359                     textarea = true;
1360                     parseSpecial(src);
1361                 }
1362                 break;
1363             case ID_TITLE:
1364                 if (beginTag) {
1365                     searchStopper = titleEnd;
1366                     searchStopperLen = 7;
1367                     title = true;
1368                     parseSpecial(src);
1369                 }
1370                 break;
1371             case ID_XMP:
1372                 if (beginTag) {
1373                     searchStopper = xmpEnd;
1374                     searchStopperLen = 5;
1375                     xmp = true;
1376                     parseSpecial(src);
1377                 }
1378                 break;
1379             case ID_SELECT:
1380                 select = beginTag;
1381                 break;
1382             case ID_PLAINTEXT:
1383                 plaintext = beginTag;
1384                 break;
1385             }
1386             
1387             if (beginTag && endTag[tagID] == FORBIDDEN)
1388                 // Don't discard LFs since this element has no end tag.
1389                 discard = NoneDiscard;
1390                 
1391             return; // Finished parsing tag!
1392         }
1393         } // end switch
1394     }
1395     return;
1396 }
1397
1398 void HTMLTokenizer::addPending()
1399 {
1400     if ( select && !script )
1401     {
1402         *dest++ = ' ';
1403     }
1404     else if ( textarea || script )
1405     {
1406         switch(pending) {
1407         case LFPending:  *dest++ = '\n'; prePos = 0; break;
1408         case SpacePending: *dest++ = ' '; ++prePos; break;
1409         case TabPending: *dest++ = '\t'; prePos += TAB_SIZE - (prePos % TAB_SIZE); break;
1410         case NonePending:
1411             assert(0);
1412         }
1413     }
1414     else
1415     {
1416         int p;
1417
1418         switch (pending)
1419         {
1420         case SpacePending:
1421             // Insert a breaking space
1422             *dest++ = QChar(' ');
1423             prePos++;
1424             break;
1425
1426         case LFPending:
1427             *dest = '\n';
1428             dest++;
1429             prePos = 0;
1430             break;
1431
1432         case TabPending:
1433             p = TAB_SIZE - ( prePos % TAB_SIZE );
1434 #ifdef TOKEN_DEBUG
1435             qDebug("tab pending, prePos: %d, toadd: %d", prePos, p);
1436 #endif
1437
1438             for ( int x = 0; x < p; x++ )
1439                 *dest++ = QChar(' ');
1440             prePos += p;
1441             break;
1442
1443         case NonePending:
1444             assert(0);
1445             break;
1446         }
1447     }
1448     
1449     pending = NonePending;
1450 }
1451
1452 void HTMLTokenizer::write(const TokenizerString &str, bool appendData)
1453 {
1454 #ifdef TOKEN_DEBUG
1455     kdDebug( 6036 ) << this << " Tokenizer::write(\"" << str << "\"," << appendData << ")" << endl;
1456 #endif
1457
1458     if ( !buffer )
1459         return;
1460
1461     if ( ( m_executingScript && appendData ) || 
1462          loadingExtScript) {
1463         // don't parse; we will do this later
1464         if (currentPrependingSrc) {
1465             currentPrependingSrc->append(str);
1466         } else {
1467             pendingSrc.append(str);
1468         }
1469         return;
1470     }
1471
1472     if ( onHold ) {
1473         src.append(str);
1474         return;
1475     }
1476     
1477     setSrc(str);
1478
1479 #ifndef NDEBUG
1480     inWrite = true;
1481 #endif
1482     
1483 //     if (Entity)
1484 //         parseEntity(src, dest);
1485
1486     while (!src.isEmpty() && (!parser->doc()->part() || !parser->doc()->part()->isImmediateRedirectPending())) {
1487         // do we need to enlarge the buffer?
1488         checkBuffer();
1489
1490         ushort cc = src->unicode();
1491
1492         if (skipLF && (cc != '\n'))
1493             skipLF = false;
1494
1495         if (skipLF) {
1496             skipLF = false;
1497             ++src;
1498         }
1499         else if ( Entity )
1500             parseEntity( src, dest );
1501         else if ( plaintext )
1502             parseText( src );
1503         else if (script)
1504             parseSpecial(src);
1505         else if (style)
1506             parseSpecial(src);
1507         else if (xmp)
1508             parseSpecial(src);
1509         else if (textarea)
1510             parseSpecial(src);
1511         else if (title)
1512             parseSpecial(src);
1513         else if (comment)
1514             parseComment(src);
1515         else if (server)
1516             parseServer(src);
1517         else if (processingInstruction)
1518             parseProcessingInstruction(src);
1519         else if (tag)
1520             parseTag(src);
1521         else if ( startTag )
1522         {
1523             startTag = false;
1524
1525             switch(cc) {
1526             case '/':
1527                 break;
1528             case '!':
1529             {
1530                 // <!-- comment -->
1531                 searchCount = 1; // Look for '<!--' sequence to start comment
1532
1533                 break;
1534             }
1535             case '?':
1536             {
1537                 // xml processing instruction
1538                 processingInstruction = true;
1539                 tquote = NoQuote;
1540                 parseProcessingInstruction(src);
1541                 continue;
1542
1543                 break;
1544             }
1545             case '%':
1546                 if (!brokenServer) {
1547                     // <% server stuff, handle as comment %>
1548                     server = true;
1549                     tquote = NoQuote;
1550                     parseServer(src);
1551                     continue;
1552                 }
1553                 // else fall through
1554             default:
1555             {
1556                 if( ((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z')))
1557                 {
1558                     // Start of a Start-Tag
1559                 }
1560                 else
1561                 {
1562                     // Invalid tag
1563                     // Add as is
1564                     if (pending)
1565                         addPending();
1566                     *dest = '<';
1567                     dest++;
1568                     continue;
1569                 }
1570             }
1571             }; // end case
1572
1573             if ( pending ) {
1574                 // pre context always gets its spaces/linefeeds
1575                 if ( pre || script || (!parser->selectMode() &&
1576                              (!parser->noSpaces() || dest > buffer ))) {
1577                     addPending();
1578                     discard = AllDiscard; // So we discard the first LF after the open tag.
1579                 }
1580                 // just forget it
1581                 else
1582                     pending = NonePending;
1583             }
1584
1585             if (cc == '/' && discard == AllDiscard)
1586                 discard = NoneDiscard; // A close tag. No need to discard LF.
1587                     
1588             processToken();
1589
1590             cBufferPos = 0;
1591             tag = TagName;
1592             parseTag(src);
1593         }
1594         else if ( cc == '&' && !src.escaped())
1595         {
1596             ++src;
1597             if ( pending )
1598                 addPending();
1599             parseEntity(src, dest, true);
1600         }
1601         else if ( cc == '<' && !src.escaped())
1602         {
1603             tagStartLineno = lineno+src.lineCount();
1604             ++src;
1605             startTag = true;
1606         }
1607         else if (( cc == '\n' ) || ( cc == '\r' ))
1608         {
1609             if (select && !script)
1610             {
1611                 if (discard == LFDiscard)
1612                 {
1613                     // Ignore this LF
1614                     discard = NoneDiscard; // We have discarded 1 LF
1615                 }
1616                 else if(discard == AllDiscard)
1617                 {
1618                 }
1619                 else
1620                  {
1621                      // Process this LF
1622                     if (pending == NonePending)
1623                          pending = LFPending;
1624                 }
1625             }
1626             else {
1627                 if (discard == LFDiscard || discard == AllDiscard)
1628                 {
1629                     // Ignore this LF
1630                     discard = NoneDiscard; // We have discarded 1 LF
1631                 }
1632                 else
1633                 {
1634                     // Process this LF
1635                     if (pending)
1636                         addPending();
1637                     pending = LFPending;
1638                 }
1639             }
1640             
1641             /* Check for MS-DOS CRLF sequence */
1642             if (cc == '\r')
1643             {
1644                 skipLF = true;
1645             }
1646             ++src;
1647         }
1648         else if (( cc == ' ' ) || ( cc == '\t' ))
1649         {
1650             if (select && !script) {
1651                 if(discard == SpaceDiscard)
1652                     discard = NoneDiscard;
1653                  else if(discard == AllDiscard)
1654                  { }
1655                  else
1656                      pending = SpacePending;
1657             
1658             }
1659             else {
1660                 if (discard == AllDiscard)
1661                     discard = NoneDiscard;
1662             
1663                 if (pending)
1664                     addPending();
1665                 if (cc == ' ')
1666                     pending = SpacePending;
1667                 else
1668                     pending = TabPending;
1669             }
1670             
1671             ++src;
1672         }
1673         else
1674         {
1675             if (pending)
1676                 addPending();
1677
1678             discard = NoneDiscard;
1679             if ( pre )
1680             {
1681                 prePos++;
1682             }
1683 #if QT_VERSION < 300
1684             unsigned char row = src->row();
1685             if ( row > 0x05 && row < 0x10 || row > 0xfd )
1686                     currToken.complexText = true;
1687 #endif
1688             *dest = *src;
1689             fixUpChar( *dest );
1690             ++dest;
1691             ++src;
1692         }
1693     }
1694     
1695 #ifndef NDEBUG
1696     inWrite = false;
1697 #endif
1698
1699     if (noMoreData && !loadingExtScript && !m_executingScript )
1700         end(); // this actually causes us to be deleted
1701 }
1702
1703 void HTMLTokenizer::end()
1704 {
1705     if ( buffer == 0 ) {
1706         parser->finished();
1707         emit finishedParsing();
1708         return;
1709     }
1710
1711     // parseTag is using the buffer for different matters
1712     if ( !tag )
1713         processToken();
1714
1715     if(buffer)
1716         KHTML_DELETE_QCHAR_VEC(buffer);
1717
1718     if(scriptCode)
1719         KHTML_DELETE_QCHAR_VEC(scriptCode);
1720
1721     scriptCode = 0;
1722     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1723     buffer = 0;
1724     parser->finished();
1725     emit finishedParsing();
1726 }
1727
1728 void HTMLTokenizer::finish()
1729 {
1730     // do this as long as we don't find matching comment ends
1731     while((comment || server) && scriptCode && scriptCodeSize)
1732     {
1733         // we've found an unmatched comment start
1734         if (comment)
1735             brokenComments = true;
1736         else
1737             brokenServer = true;
1738         checkScriptBuffer();
1739         scriptCode[ scriptCodeSize ] = 0;
1740         scriptCode[ scriptCodeSize + 1 ] = 0;
1741         int pos;
1742         QString food;
1743         if (script || style) {
1744             food.setUnicode(scriptCode, scriptCodeSize);
1745         }
1746         else if (server) {
1747             food = "<";
1748             food += QString(scriptCode, scriptCodeSize);
1749         }
1750         else {
1751             pos = QConstString(scriptCode, scriptCodeSize).string().find('>');
1752             food.setUnicode(scriptCode+pos+1, scriptCodeSize-pos-1); // deep copy
1753         }
1754         KHTML_DELETE_QCHAR_VEC(scriptCode);
1755         scriptCode = 0;
1756         scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1757         comment = server = false;
1758         if ( !food.isEmpty() )
1759             write(food, true);
1760     }
1761     // this indicates we will not recieve any more data... but if we are waiting on
1762     // an external script to load, we can't finish parsing until that is done
1763     noMoreData = true;
1764     if (!loadingExtScript && !m_executingScript && !onHold)
1765         end(); // this actually causes us to be deleted
1766 }
1767
1768 void HTMLTokenizer::processToken()
1769 {
1770     KJSProxy *jsProxy = (view && view->part()) ? view->part()->jScript() : 0L;    
1771     if (jsProxy)
1772         jsProxy->setEventHandlerLineno(tagStartLineno);
1773     if ( dest > buffer )
1774     {
1775 #ifdef TOKEN_DEBUG
1776         if(currToken.id) {
1777             qDebug( "unexpected token id: %d, str: *%s*", currToken.id,QConstString( buffer,dest-buffer ).string().latin1() );
1778             assert(0);
1779         }
1780
1781 #endif
1782         currToken.text = new DOMStringImpl( buffer, dest - buffer );
1783         currToken.text->ref();
1784         currToken.id = ID_TEXT;
1785     }
1786     else if(!currToken.id) {
1787         currToken.reset();
1788         if (jsProxy)
1789             jsProxy->setEventHandlerLineno(lineno+src.lineCount());
1790         return;
1791     }
1792
1793     dest = buffer;
1794
1795 #ifdef TOKEN_DEBUG
1796     QString name = getTagName(currToken.id).string();
1797     QString text;
1798     if(currToken.text)
1799         text = QConstString(currToken.text->s, currToken.text->l).string();
1800
1801     kdDebug( 6036 ) << "Token --> " << name << "   id = " << currToken.id << endl;
1802     if (currToken.flat)
1803         kdDebug( 6036 ) << "Token is FLAT!" << endl;
1804     if(!text.isNull())
1805         kdDebug( 6036 ) << "text: \"" << text << "\"" << endl;
1806     unsigned long l = currToken.attrs ? currToken.attrs->length() : 0;
1807     if(l) {
1808         kdDebug( 6036 ) << "Attributes: " << l << endl;
1809         for (unsigned long i = 0; i < l; ++i) {
1810             AttributeImpl* c = currToken.attrs->attributeItem(i);
1811             kdDebug( 6036 ) << "    " << c->id() << " " << parser->doc()->getDocument()->attrName(c->id()).string()
1812                             << "=\"" << c->value().string() << "\"" << endl;
1813         }
1814     }
1815     kdDebug( 6036 ) << endl;
1816 #endif
1817     // pass the token over to the parser, the parser DOES NOT delete the token
1818     parser->parseToken(&currToken);
1819
1820     currToken.reset();
1821     if (jsProxy)
1822         jsProxy->setEventHandlerLineno(0);
1823 }
1824
1825 HTMLTokenizer::~HTMLTokenizer()
1826 {
1827     assert(!inWrite);
1828     reset();
1829     delete parser;
1830 }
1831
1832
1833 void HTMLTokenizer::enlargeBuffer(int len)
1834 {
1835     int newsize = kMax(size*2, size+len);
1836     int oldoffs = (dest - buffer);
1837
1838     buffer = (QChar*)realloc(buffer, newsize*sizeof(QChar));
1839     dest = buffer + oldoffs;
1840     size = newsize;
1841 }
1842
1843 void HTMLTokenizer::enlargeScriptBuffer(int len)
1844 {
1845     int newsize = kMax(scriptCodeMaxSize*2, scriptCodeMaxSize+len);
1846     scriptCode = (QChar*)realloc(scriptCode, newsize*sizeof(QChar));
1847     scriptCodeMaxSize = newsize;
1848 }
1849
1850 void HTMLTokenizer::notifyFinished(CachedObject */*finishedObj*/)
1851 {
1852     assert(!cachedScript.isEmpty());
1853     bool finished = false;
1854     while (!finished && cachedScript.head()->isLoaded()) {
1855 #ifdef TOKEN_DEBUG
1856         kdDebug( 6036 ) << "Finished loading an external script" << endl;
1857 #endif
1858         CachedScript* cs = cachedScript.dequeue();
1859         DOMString scriptSource = cs->script();
1860 #ifdef TOKEN_DEBUG
1861         kdDebug( 6036 ) << "External script is:" << endl << scriptSource.string() << endl;
1862 #endif
1863         setSrc(TokenizerString());
1864
1865         // make sure we forget about the script before we execute the new one
1866         // infinite recursion might happen otherwise
1867         QString cachedScriptUrl( cs->url().string() );
1868         cs->deref(this);
1869
1870         scriptExecution( scriptSource.string(), cachedScriptUrl );
1871         // cachedScript.isEmpty() can change inside the scriptExecution() call above,
1872         // so don't test it until afterwards.
1873         finished = cachedScript.isEmpty();
1874         if (finished) loadingExtScript = false;
1875
1876         // 'script' is true when we are called synchronously from
1877         // parseScript(). In that case parseScript() will take care
1878         // of 'scriptOutput'.
1879         if ( !script ) {
1880             TokenizerString rest = pendingSrc;
1881             pendingSrc.clear();
1882             write(rest, false);
1883             // we might be deleted at this point, do not
1884             // access any members.
1885         }
1886     }
1887 }
1888
1889 bool HTMLTokenizer::isWaitingForScripts()
1890 {
1891     return loadingExtScript;
1892 }
1893
1894 void HTMLTokenizer::setSrc(const TokenizerString &source)
1895 {
1896     lineno += src.lineCount();
1897     src = source;
1898     src.resetLineCount();
1899 }
1900
1901 void HTMLTokenizer::setOnHold(bool _onHold)
1902 {
1903     if (onHold == _onHold) return;
1904     onHold = _onHold;
1905 }
1906
1907 }