root/branches/dmdfe/html.c

Revision 458, 12.4 kB (checked in by Gregor, 2 years ago)

MERGE: DMD 1.011

Line 
1 // Copyright (c) 1999-2006 by Digital Mars
2 // All Rights Reserved
3 // written by Walter Bright
4 // http://www.digitalmars.com
5 // License for redistribution is by either the Artistic License
6 // in artistic.txt, or the GNU General Public License in gnu.txt.
7 // See the included readme.txt for details.
8
9
10 /* HTML parser
11  */
12
13 #include <stdio.h>
14 #include <string.h>
15 #include <ctype.h>
16 #include <stdarg.h>
17 #include <errno.h>
18 #include <wchar.h>
19
20 #include "mars.h"
21 #include "html.h"
22
23 #include <assert.h>
24 #include "root.h"
25
26 extern int HtmlNamedEntity(unsigned char *p, int length);
27
28 static int isLineSeparator(const unsigned char* p);
29
30 /**********************************
31  * Determine if beginning of tag identifier
32  * or a continuation of a tag identifier.
33  */
34
35 inline int istagstart(int c)
36 {
37     return (isalpha(c) || c == '_');
38 }
39
40 inline int istag(int c)
41 {
42     return (isalnum(c) || c == '_');
43 }
44
45 /**********************************************
46  */
47
48 Html::Html(const char *sourcename, unsigned char *base, unsigned length)
49 {
50     //printf("Html::Html()\n");
51     this->sourcename = sourcename;
52     this->base = base;
53     p = base;
54     end = base + length;
55     linnum = 1;
56     dbuf = NULL;
57     inCode = 0;
58 }
59
60 /**********************************************
61  * Print error & quit.
62  */
63
64 void Html::error(const char *format, ...)
65 {
66     if (!global.gag)
67     {
68     printf("%s(%d) : HTML Error: ", sourcename, linnum);
69
70     va_list ap;
71     va_start(ap, format);
72     vprintf(format, ap);
73     va_end(ap);
74
75     printf("\n");
76     fflush(stdout);
77     }
78
79     global.errors++;
80 }
81
82 /**********************************************
83  * Extract all the code from an HTML file,
84  * concatenate it all together, and store in buf.
85  */
86
87 void Html::extractCode(OutBuffer *buf)
88 {
89     //printf("Html::extractCode()\n");
90     dbuf = buf;         // save for other routines
91     buf->reserve(end - p);
92     inCode = 0;
93     while (1)
94     {
95     //printf("p = %p, *p = x%x\n", p, *p);
96     switch (*p)
97     {
98 #if 0 // strings are not recognized outside of tags
99         case '"':
100         case '\'':
101         skipString();
102         continue;
103 #endif
104         case '<':
105         if (p[1] == '!' && isCommentStart())
106         {   // Comments start with <!--
107             scanComment();
108         }
109         else if(p[1] == '!' && isCDATAStart())
110         {
111             scanCDATA();
112         }
113         else if (p[1] == '/' && istagstart(*skipWhite(p + 2)))
114             skipTag();
115         else if (istagstart(*skipWhite(p + 1)))
116             skipTag();
117         else
118             goto Ldefault;
119         continue;
120
121         case 0:
122         case 0x1a:
123         break;      // end of file
124
125         case '&':
126         if (inCode)
127         {   // Translate character entity into ascii for D parser
128             int c;
129
130             c = charEntity();
131             buf->writeUTF8(c);
132         }
133         else
134             p++;
135         continue;
136
137         case '\r':
138         if (p[1] == '\n')
139             goto Ldefault;
140         case '\n':
141         linnum++;
142         // Always extract new lines, so that D lexer counts the
143         // lines right.
144         buf->writeByte(*p);
145         p++;
146         continue;
147
148         default:
149         Ldefault:
150         if (inCode)
151             buf->writeByte(*p);
152         p++;
153         continue;
154     }
155     break;
156     }
157     buf->writeByte(0);              // ending sentinel
158     //printf("D code is: '%s'\n", (char *)buf->data);
159 }
160
161 /***********************************************
162  * Scan to end of <> tag.
163  * Look for <code> and </code> tags to start/stop D processing.
164  * Input:
165  *  p is on opening '<' of tag; it's already verified that
166  *  it's a tag by lookahead
167  * Output:
168  *  p is past closing '>' of tag
169  */
170
171 void Html::skipTag()
172 {
173     enum TagState   // what parsing state we're in
174     {
175     TStagstart, // start of tag name
176     TStag,      // in a tag name
177     TSrest,     // following tag name
178     };
179     enum TagState state = TStagstart;
180     int inot;
181     unsigned char *tagstart = NULL;
182     int taglen = 0;
183
184     p++;
185     inot = 0;
186     if (*p == '/')
187     {   inot = 1;
188     p++;
189     }
190     while (1)
191     {
192     switch (*p)
193     {
194         case '>':       // found end of tag
195         p++;
196         break;
197
198         case '"':
199         case '\'':
200         state = TSrest;
201         skipString();
202         continue;
203
204         case '<':
205         if (p[1] == '!' && isCommentStart())
206         {   // Comments start with <!--
207             scanComment();
208         }
209         else if (p[1] == '/' && istagstart(*skipWhite(p + 2)))
210         {   error("nested tag");
211             skipTag();
212         }
213         else if (istagstart(*skipWhite(p + 1)))
214         {   error("nested tag");
215             skipTag();
216         }
217         // Treat comments as if they were whitespace
218         state = TSrest;
219         continue;
220
221         case 0:
222         case 0x1a:
223         error("end of file before end of tag");
224         break;      // end of file
225
226         case '\r':
227         if (p[1] == '\n')
228             goto Ldefault;
229         case '\n':
230         linnum++;
231         // Always extract new lines, so that code lexer counts the
232         // lines right.
233         dbuf->writeByte(*p);
234         state = TSrest;         // end of tag
235         p++;
236         continue;
237
238         case ' ':
239         case '\t':
240         case '\f':
241         case '\v':
242         if (state == TStagstart)
243         {   p++;
244             continue;
245         }
246         default:
247         Ldefault:
248         switch (state)
249         {
250             case TStagstart:        // start of tag name
251             assert(istagstart(*p));
252             state = TStag;
253             tagstart = p;
254             taglen = 0;
255             break;
256
257             case TStag:
258             if (istag(*p))
259             {   // Continuing tag name
260                 taglen++;
261             }
262             else
263             {   // End of tag name
264                 state = TSrest;
265             }
266             break;
267
268             case TSrest:
269             break;
270         }
271         p++;
272         continue;
273     }
274     break;
275     }
276
277     // See if we parsed a <code> or </code> tag
278     if (taglen && memicmp((char *) tagstart, (char *) "CODE", taglen) == 0
279     && *(p - 2) != '/') // ignore "<code />" (XHTML)
280     {
281     if (inot)
282     {   inCode--;
283         if (inCode < 0)
284         inCode = 0;     // ignore extra </code>'s
285     }
286     else
287         inCode++;
288     }
289 }
290
291 /***********************************************
292  * Scan to end of attribute string.
293  */
294
295 void Html::skipString()
296 {
297     int tc = *p;
298
299     while (1)
300     {
301     p++;
302     switch (*p)
303     {
304         case '"':
305         case '\'':
306         if (*p == tc)
307         {   p++;
308             break;
309         }
310         continue;
311
312         case '\r':
313         if (p[1] == '\n')
314             goto Ldefault;
315         case '\n':
316         linnum++;
317         // Always extract new lines, so that D lexer counts the
318         // lines right.
319         dbuf->writeByte(*p);
320         continue;
321
322         case 0:
323         case 0x1a:
324         Leof:
325         error("end of file before closing %c of string", tc);
326         break;
327
328         default:
329         Ldefault:
330         continue;
331     }
332     break;
333     }
334 }
335
336 /*********************************
337  * If p points to any white space, skip it
338  * and return pointer just past it.
339  */
340
341 unsigned char *Html::skipWhite(unsigned char *q)
342 {
343     for (; 1; q++)
344     {
345     switch (*q)
346     {
347         case ' ':
348         case '\t':
349         case '\f':
350         case '\v':
351         case '\r':
352         case '\n':
353         continue;
354
355         default:
356         break;
357     }
358     break;
359     }
360     return q;
361 }
362
363 /***************************************************
364  * Scan to end of comment.
365  * Comments are defined any of a number of ways.
366  * IE 5.0: <!-- followed by >
367  * "HTML The Definitive Guide": <!-- text with at least one space in it -->
368  * Netscape: <!-- --> comments nest
369  * w3c: whitespace can appear between -- and > of comment close
370  */
371
372 void Html::scanComment()
373 {
374     // Most of the complexity is dealing with the case that
375     // an arbitrary amount of whitespace can appear between
376     // the -- and the > of a comment close.
377     int scangt = 0;
378
379     //printf("scanComment()\n");
380     if (*p == '\n')
381     {   linnum++;
382     // Always extract new lines, so that D lexer counts the
383     // lines right.
384     dbuf->writeByte(*p);
385     }
386     while (1)
387     {
388     //scangt = 1;           // IE 5.0 compatibility
389     p++;
390     switch (*p)
391     {
392         case '-':
393         if (p[1] == '-')
394         {
395             if (p[2] == '>')    // optimize for most common case
396             {
397             p += 3;
398             break;
399             }
400             p++;
401             scangt = 1;
402         }
403         else
404             scangt = 0;
405         continue;
406
407         case '>':
408         if (scangt)
409         {   // found -->
410             p++;
411             break;
412         }
413         continue;
414
415         case ' ':
416         case '\t':
417         case '\f':
418         case '\v':
419         // skip white space
420         continue;
421
422         case '\r':
423         if (p[1] == '\n')
424             goto Ldefault;
425         case '\n':
426         linnum++;       // remember to count lines
427         // Always extract new lines, so that D lexer counts the
428         // lines right.
429         dbuf->writeByte(*p);
430         continue;
431
432         case 0:
433         case 0x1a:
434         error("end of file before closing --> of comment");
435         break;
436
437         default:
438         Ldefault:
439         scangt = 0;     // it's not -->
440         continue;
441     }
442     break;
443     }
444     //printf("*p = '%c'\n", *p);
445 }
446
447 /********************************************
448  * Determine if we are at the start of a comment.
449  * Input:
450  *  p is on the opening '<'
451  * Returns:
452  *  0 if not start of a comment
453  *  1 if start of a comment, p is adjusted to point past --
454  */
455
456 int Html::isCommentStart()
457 #ifdef __DMC__
458     __out(result)
459     {
460     if (result == 0)
461         ;
462     else if (result == 1)
463     {
464         assert(p[-2] == '-' && p[-1] == '-');
465     }
466     else
467         assert(0);
468     }
469     __body
470 #endif /* __DMC__ */
471     {   unsigned char *s;
472
473     if (p[0] == '<' && p[1] == '!')
474     {
475         for (s = p + 2; 1; s++)
476         {
477         switch (*s)
478         {
479             case ' ':
480             case '\t':
481             case '\r':
482             case '\f':
483             case '\v':
484             // skip white space, even though spec says no
485             // white space is allowed
486             continue;
487
488             case '-':
489             if (s[1] == '-')
490             {
491                 p = s + 2;
492                 return 1;
493             }
494             goto No;
495
496             default:
497             goto No;
498         }
499         }
500     }
501     No:
502     return 0;
503     }
504
505 int Html::isCDATAStart()
506 {
507     const char * CDATA_START_MARKER = "<![CDATA[";
508     size_t len = strlen(CDATA_START_MARKER);
509
510     if (strncmp((char*)p, CDATA_START_MARKER, len) == 0)
511     {
512     p += len;
513     return 1;
514     }
515     else
516     {
517     return 0;
518     }
519 }
520
521 void Html::scanCDATA()
522 {
523     while(*p && *p != 0x1A)
524     {
525     int lineSepLength = isLineSeparator(p);
526     if (lineSepLength>0)
527     {
528         /* Always extract new lines, so that D lexer counts the lines
529          * right.
530          */
531         linnum++;
532         dbuf->writeUTF8('\n');
533         p += lineSepLength;
534         continue;
535         }
536     else if (p[0] == ']' && p[1] == ']' && p[2] == '>')
537     {
538         /* end of CDATA section */
539         p += 3;
540         return;
541     }
542     else if (inCode)
543     {
544         /* this CDATA section contains D code */
545         dbuf->writeByte(*p);
546     }
547
548     p++;
549     }
550 }
551
552 /********************************************
553  * Convert an HTML character entity into a character.
554  * Forms are:
555  *  &name;      named entity
556  *  &#ddd;      decimal
557  *  &#xhhhh;    hex
558  * Input:
559  *  p is on the &
560  */
561
562 int Html::charEntity()
563 {   int c = 0;
564     int v;
565     int hex;
566     unsigned char *pstart = p;
567
568     //printf("Html::charEntity('%c')\n", *p);
569     if (p[1] == '#')
570     {
571     p++;
572     if (p[1] == 'x' || p[1] == 'X')
573     {   p++;
574         hex = 1;
575     }
576     else
577         hex = 0;
578     if (p[1] == ';')
579         goto Linvalid;
580     while (1)
581     {
582         p++;
583         switch (*p)
584         {
585         case 0:
586         case 0x1a:
587             error("end of file before end of character entity");
588             goto Lignore;
589
590         case '\n':
591         case '\r':
592         case '<':   // tag start
593             // Termination is assumed
594             break;
595
596         case ';':
597             // Termination is explicit
598             p++;
599             break;
600
601         case '0': case '1': case '2': case '3': case '4':
602         case '5': case '6': case '7': case '8': case '9':
603             v = *p - '0';
604             goto Lvalue;
605
606         case 'a': case 'b': case 'c':
607         case 'd': case 'e': case 'f':
608             if (!hex)
609             goto Linvalid;
610             v = (*p - 'a') + 10;
611             goto Lvalue;
612
613         case 'A': case 'B': case 'C':
614         case 'D': case 'E': case 'F':
615             if (!hex)
616             goto Linvalid;
617             v = (*p - 'A') + 10;
618             goto Lvalue;
619
620         Lvalue:
621             if (hex)
622             c = (c << 4) + v;
623             else
624             c = (c * 10) + v;
625             if (c > 0x10FFFF)
626             {
627             error("character entity out of range");
628             goto Lignore;
629             }
630             continue;
631
632         default:
633         Linvalid:
634             error("invalid numeric character reference");
635             goto Lignore;
636         }
637         break;
638     }
639     }
640     else
641     {
642     // It's a named entity; gather all characters until ;
643     unsigned char *idstart = p + 1;
644
645     while (1)
646     {
647         p++;
648         switch (*p)
649         {
650         case 0:
651         case 0x1a:
652             error("end of file before end of character entity");
653             break;
654
655         case '\n':
656         case '\r':
657         case '<':   // tag start
658             // Termination is assumed
659             c = HtmlNamedEntity(idstart, p - idstart);
660             if (c == -1)
661             goto Lignore;
662             break;
663
664         case ';':
665             // Termination is explicit
666             c = HtmlNamedEntity(idstart, p - idstart);
667             if (c == -1)
668             goto Lignore;
669             p++;
670             break;
671
672         default:
673             continue;
674         }
675         break;
676     }
677     }
678
679     // Kludge to convert non-breaking space to ascii space
680     if (c == 160)
681     c = ' ';
682
683     return c;
684
685 Lignore:
686     //printf("Lignore\n");
687     p = pstart + 1;
688     return '&';
689 }
690
691 /**
692  * identify DOS, Linux, Mac, Next and Unicode line endings
693  * 0 if this is no line separator
694  * >0 the length of the separator
695  * Note: input has to be UTF-8
696  */
697 static int isLineSeparator(const unsigned char* p)
698 {
699     // Linux
700     if( p[0]=='\n')
701     return 1;
702
703     // Mac & Dos
704     if( p[0]=='\r')
705     return (p[1]=='\n') ? 2 : 1;
706
707     // Unicode (line || paragraph sep.)
708     if( p[0]==0xE2 && p[1]==0x80 && (p[2]==0xA8 || p[2]==0xA9))
709     return 3;
710
711     // Next
712     if( p[0]==0xC2 && p[1]==0x85)
713     return 2;
714
715     return 0;
716 }
Note: See TracBrowser for help on using the browser.