HTML push parser
[metaproxy-moved-to-github.git] / src / html_parser.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2013 Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20 #include "html_parser.hpp"
21
22 #include <assert.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <stdio.h>
27
28 #define TAG_MAX_LEN 64
29
30 #define SPACECHR " \t\r\n\f"
31
32 #define DEBUG(x) x
33
34 #if HAVE_SYS_TYPES_H
35 #include <sys/types.h>
36 #endif
37
38 namespace mp = metaproxy_1;
39
40 mp::HTMLParser::HTMLParser()
41 {
42 }
43
44 mp::HTMLParser::~HTMLParser()
45 {
46 }
47
48 static void parse_str(mp::HTMLParserEvent & event, const char * str);
49
50 void mp::HTMLParser::parse(mp::HTMLParserEvent & event, const char *str) const
51 {
52     parse_str(event, str);
53 }
54
55 //static C functions follow would probably make sense to wrap this in PIMPL?
56
57 static int skipSpace (const char *cp)
58 {
59     int i = 0;
60     while (cp[i] && strchr (SPACECHR, cp[i]))
61         i++;
62     return i;
63 }
64
65 static int skipName (const char *cp, char *dst)
66 {
67     int i;
68     int j = 0;
69     for (i=0; cp[i] && !strchr (SPACECHR "/>=", cp[i]); i++)
70         if (j < TAG_MAX_LEN-1)
71         {
72             dst[j] = tolower(cp[j]);
73             j++;
74         }
75     dst[j] = '\0';
76     return i;
77 }
78
79 static int skipAttribute (const char *cp, char *name, char **value)
80 {
81     int i = skipName (cp, name);   
82     *value = NULL;
83     if (!i)
84         return skipSpace (cp);
85     i += skipSpace (cp + i);
86     if (cp[i] == '=')
87     {
88         int v0, v1;
89         i++;
90         i += skipSpace (cp + i);
91         if (cp[i] == '\"' || cp[i] == '\'')
92         {
93             char tr = cp[i];
94             v0 = ++i;
95             while (cp[i] != tr && cp[i])
96                 i++; 
97             v1 = i;
98             if (cp[i])
99                 i++;
100         }
101         else
102         {
103             v0 = i;
104             while (cp[i] && !strchr (SPACECHR ">", cp[i]))
105                 i++;
106             v1 = i;
107         }
108         *value = (char *) malloc (v1 - v0 + 1);
109         memcpy (*value, cp + v0, v1-v0);
110         (*value)[v1-v0] = '\0';
111     }
112     i += skipSpace (cp + i);
113     return i;
114 }
115
116 static int tagAttrs (mp::HTMLParserEvent & event, 
117                      const char *tagName,
118                      const char *cp)
119 {
120     int i;
121     char attr_name[TAG_MAX_LEN];
122     char *attr_value;
123     i = skipSpace (cp);
124     while (cp[i] && cp[i] != '>')
125     {
126         int nor = skipAttribute (cp+i, attr_name, &attr_value);
127         i += nor;
128         if (nor)
129         {
130             DEBUG(printf ("------ attr %s=%s\n", attr_name, attr_value));
131             event.attribute(tagName, attr_name, attr_value);
132         }
133         else
134         {
135             if (!nor)
136                 i++;
137         }
138     }
139     return i;
140 }
141
142 static int tagStart (mp::HTMLParserEvent & event,
143         char *tagName, const char *cp, const char which)
144 {
145     int i = 0;
146     i = skipName (cp, tagName);
147     switch (which) 
148     {
149         case '/' : 
150             DEBUG(printf ("------ tag close %s\n", tagName));
151             event.closeTag(tagName);
152             break;
153         case '!' : 
154             DEBUG(printf ("------ dtd %s\n", tagName)); 
155             break;
156         case '?' : 
157             DEBUG(printf ("------ pi %s\n", tagName)); 
158             break;
159         default :  
160             DEBUG(printf ("------ tag open %s\n", tagName));
161             event.openTagStart(tagName);
162             break;
163     }
164     return i;
165 }
166
167 static int tagEnd (mp::HTMLParserEvent & event, const char *tagName, const char *cp)
168 {
169     int i = 0;
170     while (cp[i] && cp[i] != '>')
171         i++;
172     if (cp[i] == '>')
173     {
174         event.anyTagEnd(tagName);
175         i++;
176     }
177     return i;
178 }
179
180 static char* allocFromRange (const char *start, const char *end)
181 {
182     char *value = (char *) malloc (end - start + 1);
183     assert (value);
184     memcpy (value, start, end - start);
185     value[end - start] = '\0';
186     return value;
187 }
188
189 static void tagText (mp::HTMLParserEvent & event, const char *text_start, const char *text_end)
190 {
191     if (text_end - text_start) //got text to flush
192     {
193         char *temp = allocFromRange(text_start, text_end);
194         DEBUG(printf ("------ text %s\n", temp));
195         event.text(text_start, text_end-text_start);
196         free(temp);
197     }
198 }
199
200 static void parse_str (mp::HTMLParserEvent & event, const char *cp)
201 {
202     const char *text_start = cp;
203     const char *text_end = cp;
204     while (*cp)
205     {
206         if (cp[0] == '<' && cp[1])  //tag?
207         {
208             char which = cp[1];
209             if (which == '/') cp++;
210             if (!strchr (SPACECHR, cp[1])) //valid tag starts
211             {
212                 tagText (event, text_start, text_end); //flush any text
213                 char tagName[TAG_MAX_LEN];
214                 cp++;
215                 if (which == '/')
216                 {
217                     cp += tagStart (event, tagName, cp, which);
218                 }
219                 else if (which == '!' || which == '?') //pi or dtd
220                 {
221                     cp++;
222                     cp += tagStart (event, tagName, cp, which);
223                 }
224                 else
225                 {
226                     cp += tagStart (event, tagName, cp, which);
227                     cp += tagAttrs (event, tagName, cp);
228                 }
229                 cp += tagEnd (event, tagName, cp);
230                 text_start = cp;
231                 text_end = cp;
232                 continue;
233             }
234         }
235         //text
236         cp++;
237         text_end = cp;
238     }
239     tagText (event, text_start, text_end); //flush any text
240 }
241
242 /*
243  * Local variables:
244  * c-basic-offset: 4
245  * c-file-style: "Stroustrup"
246  * indent-tabs-mode: nil
247  * End:
248  * vim: shiftwidth=4 tabstop=8 expandtab
249  */
250