Merge branch 'master' of https://git.maemo.org/projects/erwise
[erwise] / WWWLibrary / HTML.c
1 /*              HTML Parser
2 **              ===========
3 */
4 #include <ctype.h>
5 #include <stdio.h>
6
7 #include "HTUtils.h"
8 #include "SGML.h"
9 #include "HTAtom.h"
10 #include "HTChunk.h"
11 #include "HText.h"
12 #include "HTStyle.h"
13 #include "HTML.h"
14
15
16 /*                              SPECIAL HTML CODE
17 **                              =================
18 */
19
20 extern HTStyleSheet * styleSheet;       /* Application-wide */
21
22 PRIVATE HTParentAnchor * node_anchor;
23 PRIVATE HText * text;
24
25 PRIVATE HTStyle * glossary_style;
26 PRIVATE HTStyle * list_compact_style;
27 PRIVATE HTStyle * glossary_compact_style;
28
29 PRIVATE HTChunk title = { 0, 128, 0, 0 };       /* Grow by 128 */
30
31
32 /*      Forward declarations of routines for DTD
33 */
34 PRIVATE void no_change PARAMS((HTTag * t, HTElement * e));
35 PRIVATE void begin_litteral PARAMS((HTTag * t, HTElement * e));
36 PRIVATE void begin_element PARAMS((HTTag * t, HTElement * e));
37 PRIVATE void end_element PARAMS((HTTag * t, HTElement * e));
38 PRIVATE void begin_document PARAMS((HTTag * t, HTElement * e));
39 PRIVATE void end_document PARAMS((HTTag * t, HTElement * e));
40 PRIVATE void begin_anchor PARAMS((HTTag * t, HTElement * e));
41 PRIVATE void end_anchor PARAMS((HTTag * t, HTElement * e));
42 PRIVATE void begin_list PARAMS((HTTag * t, HTElement * e));
43 PRIVATE void list_element PARAMS((HTTag * t, HTElement * e));
44 PRIVATE void end_list PARAMS((HTTag * t, HTElement * e));
45 PRIVATE void begin_glossary PARAMS((HTTag * t, HTElement * e));
46 PRIVATE void end_glossary PARAMS((HTTag * t, HTElement * e));
47
48 PRIVATE int got_styles = 0;
49 PRIVATE void get_styles NOPARAMS;
50
51 PRIVATE BOOL            style_change;
52 PRIVATE HTStyle *       new_style;
53 PRIVATE HTStyle *       old_style;
54 PRIVATE BOOL            in_word;  /* Have just had a non-white character */
55
56 /*      Style buffering avoids dummy paragraph begin/ends.
57 */
58 #define UPDATE_STYLE if (style_change) { \
59         HText_setStyle(text, new_style); \
60         old_style = new_style; \
61         style_change = NO; }
62
63 PRIVATE void change_style ARGS1(HTStyle *,style)
64 {
65     if (new_style!=style) {
66         style_change = YES /* was old_style == new_style */ ;
67         new_style = style;
68     }
69 }
70
71
72 /*              TITLE
73 */
74
75 /*      Accumulate a character of title
76 */
77 #ifdef __STDC__
78 static void accumulate_string(char c)
79 #else
80 static void accumulate_string(c)
81     char c;
82 #endif
83 {
84     HTChunkPutc(&title, c);
85 }
86
87
88 /*              Clear the title
89 */
90 PRIVATE  void clear_string ARGS2(HTTag *,t, HTElement *,e)
91 {
92     HTChunkClear(&title);
93 }
94
95 PRIVATE void set_title ARGS2(HTTag *,t, HTElement *,e)
96 {
97     HTChunkTerminate(&title);
98     HTAnchor_setTitle(node_anchor, title.data);
99 }
100
101 /*              Character handling
102 */
103 PRIVATE void set_index ARGS2(HTTag *,t, HTElement *,e)
104 {
105     HTAnchor_setIndex(node_anchor);
106 }
107
108 PRIVATE void pass_character ARGS1(char, c)
109 {
110     if (style_change) {
111         if ((c=='\n') || (c==' ')) return;      /* Ignore it */
112         UPDATE_STYLE;
113     }
114     if (c=='\n') {
115         if (in_word) {
116             HText_appendCharacter(text, ' ');
117             in_word = NO;
118         }
119     } else {
120         HText_appendCharacter(text, c);
121         in_word = YES;
122     }
123 }
124
125 PRIVATE void litteral_text ARGS1(char, c)
126 {
127 /*      We guarrantee that the style is up-to-date in begin_litteral
128 */
129     HText_appendCharacter(text, c);             /* @@@@@ */
130 }
131
132 PRIVATE void ignore_text ARGS1(char, c)
133 {
134     /* Do nothing */
135 }
136
137 PRIVATE void set_next_id  ARGS2(HTTag *,t, HTElement *,e)
138 {
139     /* @@@@@  Bad SGML anyway */
140 }
141
142 PRIVATE void new_paragraph  ARGS2(HTTag *,t, HTElement *,e)
143 {
144     UPDATE_STYLE;
145     HText_appendParagraph(text);
146     in_word = NO;
147 }
148
149 PRIVATE void term ARGS2(HTTag *,t, HTElement *,e)
150 {
151     if (!style_change) {
152         HText_appendParagraph(text);
153         in_word = NO;
154     }
155 }
156
157 PRIVATE void definition ARGS2(HTTag *,t, HTElement *,e)
158 {
159     UPDATE_STYLE;
160     pass_character('\t');       /* Just tab out one stop */
161     in_word = NO;
162 }
163
164 /*              Our Static DTD for HTML
165 **              -----------------------
166 */
167
168 static entity entities[] = {
169         { "lt", "<" },
170         { "gt", ">" },
171         { "amp", "&" },
172         { "bullet" , "\267" },                  /* @@@ NeXT only */
173         { 0,    0 }  /* Terminate list */
174 };
175
176 static attr no_attr[] = {{ 0, 0 , 0}};
177
178 static attr a_attr[] = {                                /* Anchor attributes */
179 #define A_ID 0
180         { "NAME", 0, 0 },                               /* Should be ID */
181 #define A_TYPE 1
182         { "TYPE", 0, 0 },
183 #define A_HREF 2
184         { "HREF", 0, 0 },
185         { 0, 0 , 0}     /* Terminate list */
186 };      
187 static attr list_attr[] = {
188 #define LIST_COMPACT 0
189         { "COMPACT", 0, 0 },
190         { 0, 0, 0 }     /* Terminate list */
191 };
192
193 static attr glossary_attr[] = {
194 #define GLOSSARY_COMPACT 0
195         { "COMPACT", 0, 0 },
196         { 0, 0, 0 }     /* Terminate list */
197 };
198
199 static HTTag default_tag =
200     { "DOCUMENT", no_attr , 0, 0, begin_document, pass_character, end_document };
201 /*      NAME ATTR  STYLE LITERAL?  ON_BEGIN   ON__CHARACTER     ON_END
202 */
203 static HTTag tags[] = {
204 #define TITLE_TAG 0
205     { "TITLE", no_attr, 0, 0, clear_string, accumulate_string, set_title },
206 #define ISINDEX_TAG 1
207     { "ISINDEX", no_attr, 0, 0, set_index, 0 , 0 },
208 #define NEXTID_TAG 2
209     { "NEXTID", no_attr, 0, 0, set_next_id, 0, 0 },
210 #define ADDRESS_TAG 3
211     { "ADDRESS" , no_attr, 0, 0, begin_element, pass_character, end_element },
212 #define H1_TAG 4
213     { "H1"      , no_attr, 0, 0, begin_element, pass_character, end_element },
214     { "H2"      , no_attr, 0, 0, begin_element, pass_character, end_element },
215     { "H3"      , no_attr, 0, 0, begin_element, pass_character, end_element },
216     { "H4"      , no_attr, 0, 0, begin_element, pass_character, end_element },
217     { "H5"      , no_attr, 0, 0, begin_element, pass_character, end_element },
218     { "H6"      , no_attr, 0, 0, begin_element, pass_character, end_element },
219     { "H7"      , no_attr, 0, 0, begin_element, pass_character, end_element },
220 #define UL_TAG 11
221     { "UL"      , list_attr, 0, 0, begin_list, pass_character, end_list },
222 #define OL_TAG 12
223     { "OL"      , list_attr, 0, 0, begin_list, pass_character, end_list },
224 #define MENU_TAG 13
225     { "MENU"    , list_attr, 0, 0, begin_list, pass_character, end_list },
226 #define DIR_TAG 14
227     { "DIR"     , list_attr, 0, 0, begin_list, pass_character, end_list },
228 #define LI_TAG 15
229     { "LI"      , no_attr, 0, 0, list_element, pass_character, 0 },
230 #define DL_TAG 16
231     { "DL"      , list_attr, 0, 0, begin_glossary, pass_character, end_glossary },
232     { "DT"      , no_attr, 0, 0, term, pass_character, 0 },
233     { "DD"      , no_attr, 0, 0, definition, pass_character, 0 },
234     { "A"       , a_attr,  0, 0, begin_anchor, pass_character, end_anchor },
235 #define P_TAG 20
236     { "P"       , no_attr, 0, 0, new_paragraph, pass_character, 0 },
237 #define XMP_TAG 21
238   { "XMP"       , no_attr, 0, YES, begin_litteral, litteral_text, end_element },
239 #define LISTING_TAG 22
240   { "LISTING"   , no_attr, 0, YES,begin_litteral, litteral_text, end_element },
241 #define PLAINTEXT_TAG 23
242   { "PLAINTEXT", no_attr, 0, YES, begin_litteral, litteral_text, end_element },
243 #define COMMENT_TAG 24
244     { "COMMENT", no_attr, 0, YES, no_change, ignore_text, no_change },
245     { 0, 0, 0, 0,  0, 0 , 0}    /* Terminate list */
246 };
247
248 PUBLIC SGML_dtd HTML_dtd = { tags, &default_tag, entities };
249
250
251 /*              Flattening the style structure
252 **              ------------------------------
253 **
254 On the NeXT, and on any read-only browser, it is simpler for the text to have
255 a sequence of styles, rather than a nested tree of styles. In this
256 case we have to flatten the structure as it arrives from SGML tags into
257 a sequence of styles.
258 */
259
260 /*      Anchor handling
261 **      ---------------
262 */
263 PRIVATE void begin_anchor ARGS2(HTTag *,t, HTElement *,e)
264 {
265     HTChildAnchor * source = HTAnchor_findChildAndLink(
266         node_anchor,                                            /* parent */
267         a_attr[A_ID].present    ? a_attr[A_ID].value : 0,       /* Tag */
268         a_attr[A_HREF].present  ? a_attr[A_HREF].value : 0,     /* Addresss */
269         a_attr[A_TYPE].present  ? 
270                 (HTLinkType*)HTAtom_for(a_attr[A_TYPE].value)
271                  : 0);
272     
273     UPDATE_STYLE;
274     HText_beginAnchor(text, source);
275 }
276
277 PRIVATE void end_anchor ARGS2(HTTag *,   t,
278                         HTElement *,    e)
279 {
280     UPDATE_STYLE;
281     HText_endAnchor(text);
282 }
283
284
285 /*      General SGML Element Handling
286 **      -----------------------------
287 */
288 PRIVATE void begin_element ARGS2(HTTag *,t, HTElement *,e)
289 {
290     change_style((HTStyle*)(t->style));
291 }
292 PRIVATE void no_change ARGS2(HTTag *,t, HTElement *,e)
293 {
294     /* Do nothing */;
295 }
296 PRIVATE void begin_litteral ARGS2(HTTag *,t, HTElement *,e)
297 {
298     change_style(t->style);
299     UPDATE_STYLE;
300 }
301 PRIVATE void end_element ARGS2(HTTag *,t, HTElement *,e)
302 {
303     if (e) change_style(e->tag->style);
304 }
305
306 /*                      Lists
307 */
308 PRIVATE void begin_list ARGS2(HTTag *,t, HTElement *,e)
309 {
310     change_style(list_attr[LIST_COMPACT].present
311                 ? list_compact_style
312                 : (HTStyle*)(t->style));
313     in_word = NO;
314 }
315
316 PRIVATE void end_list ARGS2(HTTag *,t, HTElement *,e)
317 {
318     change_style(e->tag->style);
319     in_word = NO;
320 }
321
322 PRIVATE void list_element  ARGS2(HTTag *,t, HTElement *,e)
323 {
324     if (e->tag != &tags[DIR_TAG])
325         HText_appendParagraph(text);
326     else
327         HText_appendCharacter(text, '\t');      /* Tab @@ nl for UL? */
328     in_word = NO;
329 }
330
331
332 PRIVATE void begin_glossary ARGS2(HTTag *,t, HTElement *,e)
333 {
334     change_style(glossary_attr[GLOSSARY_COMPACT].present
335                 ? glossary_compact_style
336                 : glossary_style);
337     in_word = NO;
338 }
339
340 PRIVATE void end_glossary ARGS2(HTTag *,t, HTElement *,e)
341 {
342     change_style(e->tag->style);
343     in_word = NO;
344 }
345
346
347 /*      Begin and End document
348 **      ----------------------
349 */
350 PUBLIC void HTML_begin ARGS1(HTParentAnchor *,anchor)
351 {
352     node_anchor = anchor;
353 }
354
355 PRIVATE void begin_document ARGS2(HTTag *, t, HTElement *, e)
356 {
357     if (!got_styles) get_styles();
358     text = HText_new(node_anchor);
359     HText_beginAppend(text);
360     HText_setStyle(text, default_tag.style);
361     old_style = 0;
362     style_change = NO;
363     in_word = NO;
364 }
365
366 PRIVATE void end_document ARGS2(HTTag *, t, HTElement *, e)
367 {
368     HText_endAppend(text);
369
370 }
371
372 /*      Get Styles from style sheet
373 **      ---------------------------
374 */
375 PRIVATE void get_styles NOARGS
376 {
377     got_styles = YES;
378     
379     tags[P_TAG].style =
380     default_tag.style =         HTStyleNamed(styleSheet, "Normal");
381     tags[H1_TAG].style =        HTStyleNamed(styleSheet, "Heading1");
382     tags[H1_TAG+1].style =      HTStyleNamed(styleSheet, "Heading2");
383     tags[H1_TAG+2].style =      HTStyleNamed(styleSheet, "Heading3");
384     tags[H1_TAG+3].style =      HTStyleNamed(styleSheet, "Heading4");
385     tags[H1_TAG+4].style =      HTStyleNamed(styleSheet, "Heading5");
386     tags[H1_TAG+5].style =      HTStyleNamed(styleSheet, "Heading6");
387     tags[H1_TAG+6].style =      HTStyleNamed(styleSheet, "Heading7");
388     tags[DL_TAG].style =        HTStyleNamed(styleSheet, "Glossary");
389     tags[UL_TAG].style =        HTStyleNamed(styleSheet, "List");
390     tags[OL_TAG].style =        HTStyleNamed(styleSheet, "List");
391     tags[MENU_TAG].style =      HTStyleNamed(styleSheet, "Menu");
392     list_compact_style =
393     tags[DIR_TAG].style =       HTStyleNamed(styleSheet, "Dir");    
394     glossary_style =            HTStyleNamed(styleSheet, "Glossary");
395     glossary_compact_style =    HTStyleNamed(styleSheet, "GlossaryCompact");
396     tags[ADDRESS_TAG].style=    HTStyleNamed(styleSheet, "Address");
397     tags[PLAINTEXT_TAG].style =
398     tags[XMP_TAG].style =       HTStyleNamed(styleSheet, "Example");
399     tags[LISTING_TAG].style =   HTStyleNamed(styleSheet, "Listing");
400 }
401
402
403 /*      Parse an HTML file
404 **      ------------------
405 **
406 **      This version takes a pointer to the routine to call
407 **      to get each character.
408 */
409 BOOL HTML_Parse
410 #ifdef __STDC__
411   (HTParentAnchor * anchor, char (*next_char)() )
412 #else
413   (anchor, next_char)
414     HTParentAnchor * anchor;
415     char (*next_char)();
416 #endif
417 {
418         HTML_begin(anchor);
419         SGML_begin(&HTML_dtd);
420         for(;;) {
421             char character;
422             character = (*next_char)();
423             if (character == (char)EOF) break;
424     
425             SGML_character(&HTML_dtd, character);           
426          }
427         SGML_end(&HTML_dtd);
428         return YES;
429 }