source: trunk/third/gtkhtml3/src/htmltokenizer.c @ 19539

Revision 19539, 31.6 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r19538, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2/*
3    Copyright (C) 1997 Martin Jones (mjones@kde.org)
4              (C) 1997 Torben Weis (weis@kde.org)
5              (C) 1999 Anders Carlsson (andersca@gnu.org)
6              (C) 2000 Helix Code, Inc., Radek Doulik (rodo@helixcode.com)
7              (C) 2001 Ximian, Inc.
8
9    This library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public
11    License as published by the Free Software Foundation; either
12    version 2 of the License, or (at your option) any later version.
13
14    This library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18
19    You should have received a copy of the GNU Library General Public License
20    along with this library; see the file COPYING.LIB.  If not, write to
21    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22    Boston, MA 02111-1307, USA.
23*/
24
25/* The HTML Tokenizer */
26#include <config.h>
27#include <ctype.h>
28#include <string.h>
29#include <gnome.h>
30#include "htmltokenizer.h"
31#include "htmlentity.h"
32
33enum {
34        HTML_TOKENIZER_BEGIN_SIGNAL,
35        HTML_TOKENIZER_END_SIGNAL,
36        HTML_TOKENIZER_LAST_SIGNAL
37};
38
39static guint html_tokenizer_signals[HTML_TOKENIZER_LAST_SIGNAL] = { 0 };
40
41#define TOKEN_BUFFER_SIZE (1 << 10)
42#define INVALID_CHARACTER_MARKER '?'
43
44typedef struct _HTMLBlockingToken HTMLBlockingToken;
45typedef struct _HTMLTokenBuffer   HTMLTokenBuffer;
46typedef enum { Table }            HTMLTokenType;
47
48struct _HTMLTokenBuffer {
49        gint size;
50        gint used;
51        gchar * data;
52};
53struct _HTMLTokenizerPrivate {
54
55        /* token buffers list */
56        GList *token_buffers;
57
58        /* current read_buf position in list */
59        GList *read_cur;
60
61        /* current read buffer */
62        HTMLTokenBuffer * read_buf;
63        HTMLTokenBuffer * write_buf;
64
65        /* position in the read_buf */
66        gint read_pos;
67
68        /* non-blocking and blocking unreaded tokens in tokenizer */
69        gint tokens_num;
70        gint blocking_tokens_num;
71
72        gchar *dest;
73        gchar *buffer;
74        gint size;
75
76        gboolean skipLF; /* Skip the LF par of a CRLF sequence */
77
78        gboolean tag; /* Are we in an html tag? */
79        gboolean tquote; /* Are we in quotes in an html tag? */
80        gboolean startTag;
81        gboolean comment; /* Are we in a comment block? */
82        gboolean title; /* Are we in a <title> block? */
83        gboolean style; /* Are we in a <style> block? */
84        gboolean script; /* Are we in a <script> block? */
85        gboolean textarea; /* Are we in a <textarea> block? */
86        gint     pre; /* Are we in a <pre> block? */
87        gboolean select; /* Are we in a <select> block? */
88        gboolean charEntity; /* Are we in an &... sequence? */
89        gboolean extension; /* Are we in an <!-- +GtkHTML: sequence? */
90 
91        enum {
92                NoneDiscard = 0,
93                SpaceDiscard,
94                LFDiscard
95        } discard;
96
97        enum {
98                NonePending = 0,
99                SpacePending,
100                LFPending,
101                TabPending
102        } pending;
103
104
105        gchar searchBuffer[20];
106        gint searchCount;
107        gint searchGtkHTMLCount;
108        gint searchExtensionEndCount;
109
110        gchar *scriptCode;
111        gint scriptCodeSize;
112        gint scriptCodeMaxSize;
113
114        GList *blocking; /* Blocking tokens */
115
116        const gchar *searchFor;
117        gboolean utf8;
118        gchar utf8_buffer[7];
119        gint utf8_length;
120};
121
122static const gchar *commentStart = "<!--";
123static const gchar *scriptEnd = "</script>";
124static const gchar *styleEnd = "</style>";
125static const gchar *gtkhtmlStart = "+gtkhtml:";
126
127enum quoteEnum {
128        NO_QUOTE = 0,
129        SINGLE_QUOTE,
130        DOUBLE_QUOTE
131};
132
133/* private tokenizer functions */
134static void           html_tokenizer_reset        (HTMLTokenizer *t);
135static void           html_tokenizer_add_pending  (HTMLTokenizer *t);
136static void           html_tokenizer_append_token (HTMLTokenizer *t,
137                                                   const gchar *string,
138                                                   gint len);
139static void           html_tokenizer_append_token_buffer (HTMLTokenizer *t,
140                                                          gint min_size);
141
142/* default implementations of tokenization functions */
143static void     html_tokenizer_finalize             (GObject *);
144static void     html_tokenizer_real_begin           (HTMLTokenizer *, gchar *content_type);
145static void     html_tokenizer_real_write           (HTMLTokenizer *, const gchar *str, size_t size);
146static void     html_tokenizer_real_end             (HTMLTokenizer *);
147static gchar   *html_tokenizer_real_peek_token      (HTMLTokenizer *);
148static gchar   *html_tokenizer_real_next_token      (HTMLTokenizer *);
149static gboolean html_tokenizer_real_has_more_tokens (HTMLTokenizer *);
150
151static HTMLTokenizer *html_tokenizer_real_clone     (HTMLTokenizer *);
152
153/* blocking tokens */
154static gchar             *html_tokenizer_blocking_get_name   (HTMLTokenizer  *t);
155static void               html_tokenizer_blocking_pop        (HTMLTokenizer  *t);
156static void               html_tokenizer_blocking_push       (HTMLTokenizer  *t,
157                                                              HTMLTokenType   tt);
158static void               html_tokenizer_tokenize_one_char   (HTMLTokenizer  *t,
159                                                              const gchar  **src);
160
161static void               add_unichar(HTMLTokenizer *t, gunichar wc);
162
163static GObjectClass *parent_class = NULL;
164
165static void
166html_tokenizer_class_init (HTMLTokenizerClass *klass)
167{
168        GObjectClass *object_class = (GObjectClass *) klass;
169
170        parent_class = g_type_class_ref (G_TYPE_OBJECT);
171
172        html_tokenizer_signals[HTML_TOKENIZER_BEGIN_SIGNAL] =
173                g_signal_new ("begin",
174                              G_TYPE_FROM_CLASS (klass),
175                              G_SIGNAL_RUN_LAST,
176                              G_STRUCT_OFFSET (HTMLTokenizerClass, begin),
177                              NULL, NULL,
178                              g_cclosure_marshal_VOID__POINTER,
179                              G_TYPE_NONE,
180                              1, G_TYPE_POINTER);
181
182        html_tokenizer_signals [HTML_TOKENIZER_END_SIGNAL] =
183                g_signal_new ("end",
184                              G_TYPE_FROM_CLASS (klass),
185                              G_SIGNAL_RUN_LAST,
186                              G_STRUCT_OFFSET (HTMLTokenizerClass, end),
187                              NULL, NULL,
188                              g_cclosure_marshal_VOID__VOID,
189                              G_TYPE_NONE,
190                              0);
191
192        object_class->finalize = html_tokenizer_finalize;
193
194        klass->begin      = html_tokenizer_real_begin;
195        klass->end        = html_tokenizer_real_end;
196
197        klass->write      = html_tokenizer_real_write;
198        klass->peek_token = html_tokenizer_real_peek_token;
199        klass->next_token = html_tokenizer_real_next_token;
200        klass->has_more   = html_tokenizer_real_has_more_tokens;
201        klass->clone      = html_tokenizer_real_clone;
202}
203
204static void
205html_tokenizer_init (HTMLTokenizer *t)
206{
207        struct _HTMLTokenizerPrivate *p;
208
209        t->priv = p = g_new0 (struct _HTMLTokenizerPrivate, 1);
210
211        p->token_buffers = NULL;
212        p->read_cur  = NULL;
213        p->read_buf  = NULL;
214        p->write_buf = NULL;
215        p->read_pos  = 0;
216
217        p->dest = NULL;
218        p->buffer = NULL;
219        p->size = 0;
220
221        p->skipLF = FALSE;
222        p->tag = FALSE;
223        p->tquote = FALSE;
224        p->startTag = FALSE;
225        p->comment = FALSE;
226        p->title = FALSE;
227        p->style = FALSE;
228        p->script = FALSE;
229        p->textarea = FALSE;
230        p->pre = 0;
231        p->select = FALSE;
232        p->charEntity = FALSE;
233        p->extension = FALSE;
234
235        p->discard = NoneDiscard;
236        p->pending = NonePending;
237
238        memset (p->searchBuffer, 0, sizeof (p->searchBuffer));
239        p->searchCount = 0;
240        p->searchGtkHTMLCount = 0;
241
242        p->scriptCode = NULL;
243        p->scriptCodeSize = 0;
244        p->scriptCodeMaxSize = 0;
245
246        p->blocking = NULL;
247
248        p->searchFor = NULL;
249}
250
251static void
252html_tokenizer_finalize (GObject *obj)
253{
254        HTMLTokenizer *t = HTML_TOKENIZER (obj);
255
256        html_tokenizer_reset (t);
257
258        g_free (t->priv);
259        t->priv = NULL;
260}
261
262GtkType
263html_tokenizer_get_type (void)
264{
265        static GtkType html_tokenizer_type = 0;
266
267        if (!html_tokenizer_type) {
268                static const GTypeInfo html_tokenizer_info = {
269                        sizeof (HTMLTokenizerClass),
270                        NULL,
271                        NULL,
272                        (GClassInitFunc) html_tokenizer_class_init,
273                        NULL,
274                        NULL,
275                        sizeof (HTMLTokenizer),
276                        1,
277                        (GInstanceInitFunc) html_tokenizer_init,
278                };
279                html_tokenizer_type = g_type_register_static (G_TYPE_OBJECT, "HTMLTokenizer", &html_tokenizer_info, 0);
280        }
281
282        return html_tokenizer_type;
283}
284
285static HTMLTokenBuffer *
286html_token_buffer_new (gint size)
287{
288        HTMLTokenBuffer *nb = g_new (HTMLTokenBuffer, 1);
289
290        nb->data = g_new (gchar, size);
291        nb->size = size;
292        nb->used = 0;
293
294        return nb;
295}
296
297static void
298html_token_buffer_destroy (HTMLTokenBuffer *tb)
299{
300        g_free (tb->data);
301        g_free (tb);
302}
303
304static gboolean
305html_token_buffer_append_token (HTMLTokenBuffer * buf, const gchar *token, gint len)
306{
307
308        /* check if we have enough free space */
309        if (len + 1 > buf->size - buf->used) {
310                return FALSE;
311        }
312
313        /* copy token and terminate with zero */
314        strncpy (buf->data + buf->used, token, len);
315        buf->used += len;
316        buf->data [buf->used] = 0;
317        buf->used ++;
318
319        return TRUE;
320}
321
322HTMLTokenizer *
323html_tokenizer_new (void)
324{
325        return (HTMLTokenizer *) g_object_new (HTML_TYPE_TOKENIZER, NULL);
326}
327
328void
329html_tokenizer_destroy (HTMLTokenizer *t)
330{
331        g_return_if_fail (t && HTML_IS_TOKENIZER (t));
332       
333        g_object_unref (G_OBJECT (t));
334}
335
336static gchar *
337html_tokenizer_real_peek_token (HTMLTokenizer *t)
338{
339        struct _HTMLTokenizerPrivate *p = t->priv;
340        gchar *token;
341
342        g_assert (p->read_buf);
343
344        if (p->read_buf->used > p->read_pos) {
345                token = p->read_buf->data + p->read_pos;
346        } else {
347                GList *next;
348                HTMLTokenBuffer *buffer;
349
350                g_assert (p->read_cur);
351                g_assert (p->read_buf);
352
353                /* lookup for next buffer */
354                next = p->read_cur->next;
355                g_assert (next);
356
357                buffer = (HTMLTokenBuffer *) next->data;
358
359                g_return_val_if_fail (buffer->used != 0, NULL);
360
361                /* finally get first token */
362                token = buffer->data;
363        }
364       
365        return token;
366}
367       
368static gchar *
369html_tokenizer_real_next_token (HTMLTokenizer *t)
370{
371        struct _HTMLTokenizerPrivate *p = t->priv;
372        gchar *token;
373
374        g_assert (p->read_buf);
375
376        /* token is in current read_buf */
377        if (p->read_buf->used > p->read_pos) {
378                token = p->read_buf->data + p->read_pos;
379                p->read_pos += strlen (token) + 1;
380        } else {
381                GList *new;
382
383                g_assert (p->read_cur);
384                g_assert (p->read_buf);
385
386                /* lookup for next buffer */
387                new = p->read_cur->next;
388                g_assert (new);
389
390                /* destroy current buffer */
391                p->token_buffers = g_list_remove (p->token_buffers, p->read_buf);
392                html_token_buffer_destroy (p->read_buf);
393
394                p->read_cur = new;
395                p->read_buf = (HTMLTokenBuffer *) new->data;
396
397                g_return_val_if_fail (p->read_buf->used != 0, NULL);
398
399                /* finally get first token */
400                token = p->read_buf->data;
401                p->read_pos = strlen (token) + 1;
402        }
403
404        p->tokens_num--;
405        g_assert (p->tokens_num >= 0);
406
407        return token;
408}
409
410static gboolean
411html_tokenizer_real_has_more_tokens (HTMLTokenizer *t)
412{
413        return t->priv->tokens_num > 0;
414}
415
416static HTMLTokenizer *
417html_tokenizer_real_clone (HTMLTokenizer *t)
418{
419        return html_tokenizer_new ();
420}
421
422static void
423html_tokenizer_reset (HTMLTokenizer *t)
424{
425        struct _HTMLTokenizerPrivate *p = t->priv;
426        GList *cur = p->token_buffers;
427
428        /* free remaining token buffers */
429        while (cur) {
430                g_assert (cur->data);
431                html_token_buffer_destroy ((HTMLTokenBuffer *) cur->data);
432                cur = cur->next;
433        }
434
435        /* reset buffer list */
436        g_list_free (p->token_buffers);
437        p->token_buffers = p->read_cur = NULL;
438        p->read_buf = p->write_buf = NULL;
439        p->read_pos = 0;
440
441        /* reset token counters */
442        p->tokens_num = p->blocking_tokens_num = 0;
443
444        if (p->buffer)
445                g_free (p->buffer);
446        p->buffer = NULL;
447        p->dest = NULL;
448        p->size = 0;
449
450        if (p->scriptCode)
451                g_free (p->scriptCode);
452        p->scriptCode = NULL;
453}
454
455static gint
456charset_is_utf8 (gchar *content_type)
457{
458        return content_type && strstr (content_type, "charset=utf-8") != NULL;
459}
460
461static void
462html_tokenizer_real_begin (HTMLTokenizer *t, gchar *content_type)
463{
464        struct _HTMLTokenizerPrivate *p = t->priv;
465       
466        html_tokenizer_reset (t);
467
468        p->dest = p->buffer;
469        p->tag = FALSE;
470        p->pending = NonePending;
471        p->discard = NoneDiscard;
472        p->pre = 0;
473        p->script = FALSE;
474        p->style = FALSE;
475        p->skipLF = FALSE;
476        p->select = FALSE;
477        p->comment = FALSE;
478        p->textarea = FALSE;
479        p->startTag = FALSE;
480        p->extension = FALSE;
481        p->tquote = NO_QUOTE;
482        p->searchCount = 0;
483        p->searchGtkHTMLCount = 0;
484        p->title = FALSE;
485        p->charEntity = FALSE;
486       
487        p->utf8 = charset_is_utf8 (content_type);
488        p->utf8_length = 0;
489#if 0
490        if (p->utf8)
491                g_warning ("Trying UTF-8");
492        else
493                g_warning ("Trying ISO-8859-1");
494#endif
495
496}
497
498static void
499destroy_blocking (gpointer data, gpointer user_data)
500{
501        g_free (data);
502}
503
504static void
505html_tokenizer_real_end (HTMLTokenizer *t)
506{
507        struct _HTMLTokenizerPrivate *p = t->priv;
508
509        if (p->buffer == 0)
510                return;
511
512        if (p->dest > p->buffer) {
513                html_tokenizer_append_token (t, p->buffer, p->dest - p->buffer);
514        }
515
516        g_free (p->buffer);     
517
518        p->buffer = NULL;
519        p->dest = NULL;
520        p->size = 0;
521
522        if (p->blocking) {
523                g_list_foreach (p->blocking, destroy_blocking, NULL);
524                p->tokens_num += p->blocking_tokens_num;
525                p->blocking_tokens_num = 0;
526        }
527        p->blocking = NULL;
528}
529
530static void
531html_tokenizer_append_token (HTMLTokenizer *t, const gchar *string, gint len)
532{
533        struct _HTMLTokenizerPrivate *p = t->priv;
534       
535        if (len < 1)
536                return;
537
538        /* allocate first buffer */
539        if (p->write_buf == NULL)
540                html_tokenizer_append_token_buffer (t, len);
541
542        /* try append token to current buffer, if not successful, create append new token buffer */
543        if (!html_token_buffer_append_token (p->write_buf, string, len)) {
544                html_tokenizer_append_token_buffer (t, len+1);
545                /* now it must pass as we have enough space */
546                g_assert (html_token_buffer_append_token (p->write_buf, string, len));
547        }
548
549        if (p->blocking) {
550                p->blocking_tokens_num++;
551        } else {
552                p->tokens_num++;
553        }
554}
555
556static void
557html_tokenizer_append_token_buffer (HTMLTokenizer *t, gint min_size)
558{
559        struct _HTMLTokenizerPrivate *p = t->priv;
560        HTMLTokenBuffer *nb;
561        gint size = TOKEN_BUFFER_SIZE;
562
563        if (min_size > size)
564                size = min_size + (min_size >> 2);
565
566        /* create new buffer and add it to list */
567        nb = html_token_buffer_new (size);
568        p->token_buffers = g_list_append (p->token_buffers, nb);
569
570        /* this one is now write_buf */
571        p->write_buf = nb;
572
573        /* if we don't have read_buf already set it to this one */
574        if (p->read_buf == NULL) {
575                p->read_buf = nb;
576                p->read_cur = p->token_buffers;
577        }
578}
579
580/* EP CHECK: OK.  */
581static void
582html_tokenizer_add_pending (HTMLTokenizer *t)
583{
584        struct _HTMLTokenizerPrivate *p = t->priv;
585       
586        if (p->tag || p->select) {
587                add_unichar (t, ' ');
588        }
589        else if (p->textarea) {
590                if (p->pending == LFPending)
591                        add_unichar (t, '\n');
592                else
593                        add_unichar (t, ' ');
594        }
595        else if (p->pre) {
596                switch (p->pending) {
597                case SpacePending:
598                        add_unichar (t, ' ');
599                        break;
600                case LFPending:
601                        if (p->dest > p->buffer) {
602                                html_tokenizer_append_token (t, p->buffer, p->dest - p->buffer);
603                        }
604                        p->dest = p->buffer;
605                        add_unichar (t, TAG_ESCAPE);
606                        add_unichar (t, '\n');
607                        html_tokenizer_append_token (t, p->buffer, 2);
608                        p->dest = p->buffer;
609                        break;
610                case TabPending:
611                        add_unichar (t, '\t');
612                        break;
613                default:
614                        g_warning ("Unknown pending type: %d\n", (gint) p->pending);
615                        break;
616                }
617        }
618        else {
619                add_unichar (t, ' ');
620        }
621       
622        p->pending = NonePending;
623}
624
625static void
626prepare_enough_space (HTMLTokenizer *t)
627{
628        struct _HTMLTokenizerPrivate *p = t->priv;
629       
630        if ((p->dest - p->buffer + 32) > p->size) {
631                guint off = p->dest - p->buffer;
632
633                p->size  += (p->size >> 2) + 32;
634                p->buffer = g_realloc (p->buffer, p->size);
635                p->dest   = p->buffer + off;
636        }
637}
638
639static void
640in_comment (HTMLTokenizer *t, const gchar **src)
641{
642        struct _HTMLTokenizerPrivate *p = t->priv;
643
644        if (**src == '-') {                  /* Look for "-->" */
645                if (p->searchCount < 2)
646                        p->searchCount++;
647        } else if (p->searchCount == 2 && (**src == '>')) {
648                p->comment = FALSE;          /* We've got a "-->" sequence */
649        } else if (tolower (**src) == gtkhtmlStart [p->searchGtkHTMLCount]) {
650                if (p->searchGtkHTMLCount == 8) {
651                        p->extension    = TRUE;
652                        p->comment = FALSE;
653                        p->searchCount = 0;
654                        p->searchExtensionEndCount = 0;
655                        p->searchGtkHTMLCount = 0;
656                } else
657                        p->searchGtkHTMLCount ++;
658        } else {
659                p->searchGtkHTMLCount = 0;
660                if (p->searchCount < 2)
661                        p->searchCount = 0;
662        }
663
664        (*src)++;
665}
666
667static inline void
668extension_one_char (HTMLTokenizer *t, const gchar **src)
669{
670        struct _HTMLTokenizerPrivate *p = t->priv;
671       
672        p->extension = FALSE;
673        html_tokenizer_tokenize_one_char (t, src);
674        p->extension = TRUE;
675}
676
677static void
678in_extension (HTMLTokenizer *t, const gchar **src)
679{
680        struct _HTMLTokenizerPrivate *p = t->priv;
681
682        /* check for "-->" */
683        if (!p->tquote && **src == '-') {
684                if (p->searchExtensionEndCount < 2)
685                        p->searchExtensionEndCount ++;
686                (*src) ++;
687        } else if (!p->tquote && p->searchExtensionEndCount == 2 && **src == '>') {
688                p->extension = FALSE;
689                (*src) ++;
690        } else {
691                if (p->searchExtensionEndCount > 0) {
692                        if (p->extension) {
693                                const gchar *c = "-->";
694
695                                while (p->searchExtensionEndCount) {
696                                        extension_one_char (t, &c);
697                                        p->searchExtensionEndCount --;
698                                }
699                        }
700                }
701                extension_one_char (t, src);
702        }
703}
704
705static void
706in_script_or_style (HTMLTokenizer *t, const gchar **src)
707{
708        struct _HTMLTokenizerPrivate *p = t->priv;
709       
710        /* Allocate memory to store the script or style */
711        if (p->scriptCodeSize + 11 > p->scriptCodeMaxSize)
712                p->scriptCode = g_realloc (p->scriptCode, p->scriptCodeMaxSize += 1024);
713                       
714        if ((**src == '>' ) && ( p->searchFor [p->searchCount] == '>')) {
715                (*src)++;
716                p->scriptCode [p->scriptCodeSize] = 0;
717                p->scriptCode [p->scriptCodeSize + 1] = 0;
718                if (p->script) {
719                        p->script = FALSE;
720                }
721                else {
722                        p->style = FALSE;
723                }
724                g_free (p->scriptCode);
725                p->scriptCode = NULL;
726        }
727        /* Check if a </script> tag is on its way */
728        else if (p->searchCount > 0) {
729                if (tolower (**src) == p->searchFor [p->searchCount]) {
730                        p->searchBuffer [p->searchCount] = **src;
731                        p->searchCount++;
732                        (*src)++;
733                }
734                else {
735                        gchar *c;
736
737                        p->searchBuffer [p->searchCount] = 0;
738                        c = p->searchBuffer;
739                        while (*c)
740                                p->scriptCode [p->scriptCodeSize++] = *c++;
741                        p->scriptCode [p->scriptCodeSize] = **src; (*src)++;
742                        p->searchCount = 0;
743                }
744        }
745        else if (**src == '<') {
746                p->searchCount = 1;
747                p->searchBuffer [0] = '<';
748                (*src)++;
749        }
750        else {
751                p->scriptCode [p->scriptCodeSize] = **src;
752                (*src)++;
753        }
754}
755
756static void
757add_unichar (HTMLTokenizer *t, gunichar wc)
758{
759        struct _HTMLTokenizerPrivate *p = t->priv;
760
761        p->utf8_length = 0;
762
763        if (wc != '\0') {
764                p->dest += g_unichar_to_utf8 (wc, p->dest);
765                *(p->dest) = 0;
766        }
767}
768
769static void
770add_byte (HTMLTokenizer *t, const gchar **src)
771{
772        gunichar wc;
773        struct _HTMLTokenizerPrivate *p = t->priv;
774
775        if (p->utf8) {
776                p->utf8_buffer[p->utf8_length] = **src;
777                p->utf8_length++;
778
779                wc = g_utf8_get_char_validated ((const gchar *)p->utf8_buffer, p->utf8_length);
780                if (wc == -1 || p->utf8_length >= (sizeof(p->utf8_buffer)/sizeof(p->utf8_buffer[0]))) {
781                        add_unichar (t, INVALID_CHARACTER_MARKER);
782                        (*src)++;
783                        return;
784                } else if (wc == -2) {
785                        /* incomplete character check again */
786                        (*src)++;
787                        return;
788                }
789        } else {
790                wc = (guchar)**src;
791        }
792
793        add_unichar (t, wc);
794        (*src)++;
795}
796
797static void
798flush_entity (HTMLTokenizer *t)
799{
800        struct _HTMLTokenizerPrivate *p = t->priv;
801        /* ignore the TAG_ESCAPE when flushing */
802        const char *str = p->searchBuffer + 1;
803
804         while (p->searchCount--) {
805                add_byte (t, &str);
806        }
807}
808
809static gboolean
810add_unichar_validated (HTMLTokenizer *t, gunichar uc)
811{
812        char tmp[8];
813
814        tmp [g_unichar_to_utf8 (uc, tmp)] = '\0';
815
816        if (g_utf8_validate (tmp, -1, NULL)) {
817                add_unichar (t, uc);
818                return TRUE;
819        }
820               
821        g_warning ("invalid character value: x%xd", uc);
822        return FALSE;
823}
824
825static void
826in_entity (HTMLTokenizer *t, const gchar **src)
827{
828        struct _HTMLTokenizerPrivate *p = t->priv;
829        gunichar entityValue = 0;
830
831        /* See http://www.mozilla.org/newlayout/testcases/layout/entities.html for a complete entity list,
832           ftp://ftp.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT
833           (or 'man iso_8859_1') for the character encodings. */
834
835        p->searchBuffer [p->searchCount + 1] = **src;
836        p->searchBuffer [p->searchCount + 2] = '\0';
837                       
838        /* Check for &#0000 sequence */
839        if (p->searchBuffer[2] == '#') {
840                if ((p->searchCount > 1) &&
841                    (!isdigit (**src)) &&
842                    (p->searchBuffer[3] != 'x')) {
843                        /* &#123 */
844                        p->searchBuffer [p->searchCount + 1] = '\0';
845                        entityValue = strtoul (&(p->searchBuffer [3]),
846                                               NULL, 10);
847                        p->charEntity = FALSE;
848                }
849                if ((p->searchCount > 1) &&
850                    (!isalnum (**src)) &&
851                    (p->searchBuffer[3] == 'x')) {
852                        /* &x12AB */
853                        p->searchBuffer [p->searchCount + 1] = '\0';
854                       
855                        entityValue = strtoul (&(p->searchBuffer [4]),
856                                               NULL, 16);
857                        p->charEntity = FALSE;
858                }
859        }
860        else {
861                /* Check for &abc12 sequence */
862                if (!isalnum (**src)) {
863                        p->charEntity = FALSE;
864                        if ((p->searchBuffer [p->searchCount + 1] == ';') ||
865                            (!p->tag)) {
866                                char *ename = p->searchBuffer + 2;
867                                               
868                                p->searchBuffer [p->searchCount + 1] = '\0'; /* FIXME sucks */
869                                entityValue = html_entity_parse (ename, 0);
870                        }
871                }
872                               
873        }
874
875        if (p->searchCount > 13) {
876                /* Ignore this sequence since it's too long */
877                p->charEntity = FALSE;
878                flush_entity (t);
879        }
880        else if (p->charEntity) {
881                                /* Keep searching for end of character entity */
882                p->searchCount++;
883                (*src)++;
884        }
885        else {
886                /*
887                 * my reading of http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.2 makes
888                 * seem correct to always collapse entity references, even in element names
889                 * and attributes.
890                 */
891                if (entityValue) {
892                        if (entityValue != TAG_ESCAPE)
893                                /* make sure the entity value is a valid character value */
894                                if (!add_unichar_validated (t, entityValue))
895                                        add_unichar (t, INVALID_CHARACTER_MARKER);
896
897                        if (**src == ';')
898                                (*src)++;
899                } else {
900                        /* Ignore the sequence, just add it as plaintext */
901                        flush_entity (t);
902                }
903        }
904}
905
906static void
907in_tag (HTMLTokenizer *t, const gchar **src)
908{
909        struct _HTMLTokenizerPrivate *p = t->priv;
910
911        p->startTag = FALSE;
912        if (**src == '/') {
913                if (p->pending == LFPending) {
914                        p->pending = NonePending;
915                }
916        }
917        else if (((**src >= 'a') && (**src <= 'z'))
918                 || ((**src >= 'A') && (**src <= 'Z'))) {
919                                /* Start of a start tag */
920        }
921        else if (**src == '!') {
922                                /* <!-- comment --> */
923        }
924        else if (**src == '?') {
925                                /* <? meta ?> */
926        }
927        else {
928                                /* Invalid tag, just add it */
929                if (p->pending)
930                        html_tokenizer_add_pending (t);
931                add_unichar (t, '<');
932                add_byte (t, src);
933                return;
934        }
935                       
936        if (p->pending)
937                html_tokenizer_add_pending (t);
938
939        if (p->dest > p->buffer) {
940                html_tokenizer_append_token (t, p->buffer, p->dest - p->buffer);
941                p->dest = p->buffer;
942        }
943        add_unichar (t, TAG_ESCAPE);
944        add_unichar (t, '<');
945        p->tag = TRUE;
946        p->searchCount = 1; /* Look for <!-- to start comment */
947}
948
949static void
950start_entity (HTMLTokenizer *t, const gchar **src)
951{
952        struct _HTMLTokenizerPrivate *p = t->priv;
953
954        (*src)++;
955                       
956        p->discard = NoneDiscard;
957                       
958        if (p->pending)
959                html_tokenizer_add_pending (t);
960
961        p->charEntity      = TRUE;
962        p->searchBuffer[0] = TAG_ESCAPE;
963        p->searchBuffer[1] = '&';
964        p->searchCount     = 1;
965}
966
967static void
968start_tag (HTMLTokenizer *t, const gchar **src)
969{
970        (*src)++;
971        t->priv->startTag = TRUE;
972        t->priv->discard  = NoneDiscard;
973}
974
975static void
976end_tag (HTMLTokenizer *t, const gchar **src)
977{
978        struct _HTMLTokenizerPrivate *p = t->priv;
979        gchar *ptr;
980
981        p->searchCount = 0; /* Stop looking for <!-- sequence */
982
983        add_unichar (t, '>');
984       
985        /* Make the tag lower case */
986        ptr = p->buffer + 2;
987        if (p->pre || *ptr == '/') {
988                /* End tag */
989                p->discard = NoneDiscard;
990        }
991        else {
992                /* Start tag */
993                /* Ignore CRLFs after a start tag */
994                p->discard = LFDiscard;
995        }
996
997        while (*ptr && *ptr !=' ') {
998                *ptr = tolower (*ptr);
999                ptr++;
1000        }
1001        html_tokenizer_append_token (t, p->buffer, p->dest - p->buffer);
1002        p->dest = p->buffer;
1003                       
1004        p->tag = FALSE;
1005        p->pending = NonePending;
1006        (*src)++;
1007                       
1008        if (strncmp (p->buffer + 2, "pre", 3) == 0) {
1009                p->pre++;
1010        }
1011        else if (strncmp (p->buffer + 2, "/pre", 4) == 0) {
1012                p->pre--;
1013        }
1014        else if (strncmp (p->buffer + 2, "textarea", 8) == 0) {
1015                p->textarea = TRUE;
1016        }
1017        else if (strncmp (p->buffer + 2, "/textarea", 9) == 0) {
1018                p->textarea = FALSE;
1019        }
1020        else if (strncmp (p->buffer + 2, "title", 5) == 0) {
1021                p->title = TRUE;
1022        }
1023        else if (strncmp (p->buffer + 2, "/title", 6) == 0) {
1024                p->title = FALSE;
1025        }
1026        else if (strncmp (p->buffer + 2, "script", 6) == 0) {
1027                p->script = TRUE;
1028                p->searchCount = 0;
1029                p->searchFor = scriptEnd;
1030                p->scriptCode = g_malloc (1024);
1031                p->scriptCodeSize = 0;
1032                p->scriptCodeMaxSize = 1024;
1033        }
1034        else if (strncmp (p->buffer + 2, "style", 5) == 0) {
1035                p->style = TRUE;
1036                p->searchCount = 0;
1037                p->searchFor = styleEnd;
1038                p->scriptCode = g_malloc (1024);
1039                p->scriptCodeSize = 0;
1040                p->scriptCodeMaxSize = 1024;
1041        }
1042        else if (strncmp (p->buffer + 2, "select", 6) == 0) {
1043                p->select = TRUE;
1044        }
1045        else if (strncmp (p->buffer + 2, "/select", 7) == 0) {
1046                p->select = FALSE;
1047        }
1048        else if (strncmp (p->buffer + 2, "cell", 4) == 0) {
1049                g_warning ("<cell> tag not supported");
1050        }
1051        else if (strncmp (p->buffer + 2, "table", 5) == 0) {
1052                html_tokenizer_blocking_push (t, Table);
1053        }
1054        else {
1055                if (p->blocking) {
1056                        const gchar *bn = html_tokenizer_blocking_get_name (t);
1057
1058                        if (strncmp (p->buffer + 1, bn, strlen (bn)) == 0) {
1059                                html_tokenizer_blocking_pop (t);
1060                        }
1061                }
1062        }
1063}
1064
1065static void
1066in_crlf (HTMLTokenizer *t, const gchar **src)
1067{
1068        struct _HTMLTokenizerPrivate *p = t->priv;
1069
1070        if (p->tquote) {
1071                if (p->discard == NoneDiscard)
1072                        p->pending = SpacePending;
1073        }
1074        else if (p->tag) {
1075                p->searchCount = 0; /* Stop looking for <!-- sequence */
1076                if (p->discard == NoneDiscard)
1077                        p->pending = SpacePending; /* Treat LFs inside tags as spaces */
1078        }
1079        else if (p->pre || p->textarea) {
1080                if (p->discard == LFDiscard) {
1081                        /* Ignore this LF */
1082                        p->discard = NoneDiscard; /*  We have discarded 1 LF */
1083                } else {
1084                        /* Process this LF */
1085                        if (p->pending)
1086                                html_tokenizer_add_pending (t);
1087                        p->pending = LFPending;
1088                }
1089        }
1090        else {
1091                if (p->discard == LFDiscard) {
1092                        /* Ignore this LF */
1093                        p->discard = NoneDiscard; /* We have discarded 1 LF */
1094                } else {
1095                        /* Process this LF */
1096                        if (p->pending == NonePending)
1097                                p->pending = LFPending;
1098                }
1099        }
1100        /* Check for MS-DOS CRLF sequence */
1101        if (**src == '\r') {
1102                p->skipLF = TRUE;
1103        }
1104        (*src)++;
1105}
1106
1107static void
1108in_space_or_tab (HTMLTokenizer *t, const gchar **src)
1109{
1110        if (t->priv->tquote) {
1111                if (t->priv->discard == NoneDiscard)
1112                        t->priv->pending = SpacePending;
1113        }
1114        else if (t->priv->tag) {
1115                t->priv->searchCount = 0; /* Stop looking for <!-- sequence */
1116                if (t->priv->discard == NoneDiscard)
1117                        t->priv->pending = SpacePending;
1118        }
1119        else if (t->priv->pre || t->priv->textarea) {
1120                if (t->priv->pending)
1121                        html_tokenizer_add_pending (t);
1122                if (**src == ' ')
1123                        t->priv->pending = SpacePending;
1124                else
1125                        t->priv->pending = TabPending;
1126        }
1127        else {
1128                t->priv->pending = SpacePending;
1129        }
1130        (*src)++;
1131}
1132
1133static void
1134in_quoted (HTMLTokenizer *t, const gchar **src)
1135{
1136        /* We treat ' and " the same in tags " */
1137        t->priv->discard = NoneDiscard;
1138        if (t->priv->tag) {
1139                t->priv->searchCount = 0; /* Stop looking for <!-- sequence */
1140                if ((t->priv->tquote == SINGLE_QUOTE && **src == '\"') /* match " */
1141                    || (t->priv->tquote == DOUBLE_QUOTE && **src == '\'')) {
1142                        add_unichar (t, **src);
1143                        (*src)++;
1144                } else if (*(t->priv->dest-1) == '=' && !t->priv->tquote) {
1145                        t->priv->discard = SpaceDiscard;
1146                        t->priv->pending = NonePending;
1147                                       
1148                        if (**src == '\"') /* match " */
1149                                t->priv->tquote = DOUBLE_QUOTE;
1150                        else
1151                                t->priv->tquote = SINGLE_QUOTE;
1152                        add_unichar (t, **src);
1153                        (*src)++;
1154                }
1155                else if (t->priv->tquote) {
1156                        t->priv->tquote = NO_QUOTE;
1157                        add_byte (t, src);
1158                        t->priv->pending = SpacePending;
1159                }
1160                else {
1161                        /* Ignore stray "\'" */
1162                        (*src)++;
1163                }
1164        }
1165        else {
1166                if (t->priv->pending)
1167                        html_tokenizer_add_pending (t);
1168
1169                add_byte (t, src);
1170        }
1171}
1172
1173static void
1174in_assignment (HTMLTokenizer *t, const gchar **src)
1175{
1176        t->priv->discard = NoneDiscard;
1177        if (t->priv->tag) {
1178                t->priv->searchCount = 0; /* Stop looking for <!-- sequence */
1179                add_unichar (t, '=');
1180                if (!t->priv->tquote) {
1181                        t->priv->pending = NonePending;
1182                        t->priv->discard = SpaceDiscard;
1183                }
1184        }
1185        else {
1186                if (t->priv->pending)
1187                        html_tokenizer_add_pending (t);
1188
1189                add_unichar (t, '=');
1190        }
1191        (*src)++;
1192}
1193
1194inline static void
1195in_plain (HTMLTokenizer *t, const gchar **src)
1196{
1197        struct _HTMLTokenizerPrivate *p = t->priv;
1198       
1199        p->discard = NoneDiscard;
1200        if (p->pending)
1201                html_tokenizer_add_pending (t);
1202                       
1203        if (p->tag) {
1204                if (p->searchCount > 0) {
1205                        if (**src == commentStart[p->searchCount]) {
1206                                p->searchCount++;
1207                                if (p->searchCount == 4) {
1208                                        /* Found <!-- sequence */
1209                                        p->comment = TRUE;
1210                                        p->dest = p->buffer;
1211                                        p->tag = FALSE;
1212                                        p->searchCount = 0;
1213                                        return;
1214                                }
1215                        }
1216                        else {
1217                                p->searchCount = 0; /* Stop lookinf for <!-- sequence */
1218                        }
1219                }
1220        }
1221
1222        add_byte (t, src);
1223}
1224
1225static void
1226html_tokenizer_tokenize_one_char (HTMLTokenizer *t, const gchar **src)
1227{
1228        struct _HTMLTokenizerPrivate *p = t->priv;
1229       
1230        prepare_enough_space (t);
1231
1232        if (p->skipLF && **src != '\n')
1233                p->skipLF = FALSE;
1234
1235        if (p->skipLF)
1236                (*src) ++;
1237        else if (p->comment)
1238                in_comment (t, src);
1239        else if (p->extension)
1240                in_extension (t, src);
1241        else if (p->script || p->style)
1242                in_script_or_style (t, src);
1243        else if (p->charEntity)
1244                in_entity (t, src);
1245        else if (p->startTag)
1246                in_tag (t, src);
1247        else if (**src == '&')
1248                start_entity (t, src);
1249        else if (**src == '<' && !p->tag)
1250                start_tag (t, src);
1251        else if (**src == '>' && p->tag && !p->tquote)
1252                end_tag (t, src);
1253        else if ((**src == '\n') || (**src == '\r'))
1254                in_crlf (t, src);
1255        else if ((**src == ' ') || (**src == '\t'))
1256                in_space_or_tab (t, src);
1257        else if (**src == '\"' || **src == '\'') /* match " ' */
1258                in_quoted (t, src);
1259        else if (**src == '=')
1260                in_assignment (t, src);
1261        else
1262                in_plain (t, src);
1263}
1264
1265static void
1266html_tokenizer_real_write (HTMLTokenizer *t, const gchar *string, size_t size)
1267{
1268        const gchar *src = string;
1269
1270        while ((src - string) < size)
1271                html_tokenizer_tokenize_one_char (t, &src);
1272}
1273
1274static gchar *
1275html_tokenizer_blocking_get_name (HTMLTokenizer *t)
1276{
1277        switch (GPOINTER_TO_INT (t->priv->blocking->data)) {
1278        case Table:
1279                return "</table";
1280        }
1281       
1282        return "";
1283}
1284
1285static void
1286html_tokenizer_blocking_push (HTMLTokenizer *t, HTMLTokenType tt)
1287{
1288        struct _HTMLTokenizerPrivate *p = t->priv;
1289       
1290        /* block tokenizer - we must block last token in buffers as it was already added */
1291        if (!p->blocking) {
1292                p->tokens_num--;
1293                p->blocking_tokens_num++;
1294        }
1295        p->blocking = g_list_prepend (p->blocking, GINT_TO_POINTER (tt));
1296}
1297
1298static void
1299html_tokenizer_blocking_pop (HTMLTokenizer *t)
1300{
1301        struct _HTMLTokenizerPrivate *p = t->priv;
1302
1303        p->blocking = g_list_remove (p->blocking, p->blocking->data);
1304
1305        /* unblock tokenizer */
1306        if (!p->blocking) {
1307                p->tokens_num += p->blocking_tokens_num;
1308                p->blocking_tokens_num = 0;
1309        }
1310}
1311
1312/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
1313
1314void
1315html_tokenizer_begin (HTMLTokenizer *t, gchar *content_type)
1316{
1317        g_return_if_fail (t && HTML_IS_TOKENIZER (t));
1318
1319        g_signal_emit (t, html_tokenizer_signals [HTML_TOKENIZER_BEGIN_SIGNAL], 0, content_type);
1320}
1321
1322void
1323html_tokenizer_end (HTMLTokenizer *t)
1324{
1325        g_return_if_fail (t && HTML_IS_TOKENIZER (t));
1326
1327        g_signal_emit (t, html_tokenizer_signals[HTML_TOKENIZER_END_SIGNAL], 0);
1328}
1329
1330void
1331html_tokenizer_write (HTMLTokenizer *t, const gchar *str, size_t size)
1332{
1333        HTMLTokenizerClass *klass;
1334
1335        g_return_if_fail (t && HTML_IS_TOKENIZER (t));
1336        klass = HTML_TOKENIZER_CLASS (G_OBJECT_GET_CLASS (t));
1337
1338        if (klass->write)
1339                klass->write (t, str, size);
1340        else
1341                g_warning ("No write method defined.");
1342}
1343
1344gchar *
1345html_tokenizer_peek_token (HTMLTokenizer *t)
1346{
1347        HTMLTokenizerClass *klass;
1348
1349        g_return_val_if_fail (t && HTML_IS_TOKENIZER (t), NULL);
1350
1351        klass = HTML_TOKENIZER_CLASS (G_OBJECT_GET_CLASS (t));
1352
1353        if (klass->peek_token)
1354                return klass->peek_token (t);
1355       
1356        g_warning ("No peek_token method defined.");
1357        return NULL;
1358
1359}
1360
1361gchar *
1362html_tokenizer_next_token (HTMLTokenizer *t)
1363{
1364        HTMLTokenizerClass *klass;
1365
1366        g_return_val_if_fail (t && HTML_IS_TOKENIZER (t), NULL);
1367
1368        klass = HTML_TOKENIZER_CLASS (G_OBJECT_GET_CLASS (t));
1369
1370        if (klass->next_token)
1371                return klass->next_token (t);
1372
1373        g_warning ("No next_token method defined.");
1374        return NULL;
1375}
1376
1377gboolean
1378html_tokenizer_has_more_tokens (HTMLTokenizer *t)
1379{
1380        HTMLTokenizerClass *klass;
1381
1382        g_return_val_if_fail (t && HTML_IS_TOKENIZER (t), FALSE);
1383
1384        klass = HTML_TOKENIZER_CLASS (G_OBJECT_GET_CLASS (t));
1385
1386        if (klass->has_more) {
1387                return klass->has_more (t);
1388        }
1389
1390        g_warning ("No has_more method defined.");
1391        return FALSE;
1392       
1393}
1394
1395HTMLTokenizer *
1396html_tokenizer_clone (HTMLTokenizer *t)
1397{
1398        HTMLTokenizerClass *klass;
1399       
1400        if (t == NULL)
1401                return NULL;
1402        g_return_val_if_fail (HTML_IS_TOKENIZER (t), NULL);
1403
1404        klass = HTML_TOKENIZER_CLASS (G_OBJECT_GET_CLASS (t));
1405
1406        if (klass->clone)
1407                return klass->clone (t);
1408       
1409        g_warning ("No clone method defined.");
1410        return NULL;
1411}
Note: See TracBrowser for help on using the repository browser.