Avoid re allocations
[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 char* dupe (const char *buff, int len)
58 {
59     char *value = (char *) malloc (len + 1);
60     assert (value);
61     memcpy (value, buff, len);
62     value[len] = '\0';
63     return value;
64 }
65
66 static int skipSpace (const char *cp)
67 {
68     int i = 0;
69     while (cp[i] && strchr (SPACECHR, cp[i]))
70         i++;
71     return i;
72 }
73
74 static int skipName (const char *cp, char *dst)
75 {
76     int i;
77     int j = 0;
78     for (i=0; cp[i] && !strchr (SPACECHR "/>=", cp[i]); i++)
79         if (j < TAG_MAX_LEN-1)
80         {
81             dst[j] = tolower(cp[j]);
82             j++;
83         }
84     dst[j] = '\0';
85     return i;
86 }
87
88 static int skipAttribute (const char *cp, char *name, const char **value, int *val_len)
89 {
90     int i = skipName (cp, name);   
91     *value = NULL;
92     if (!i)
93         return skipSpace (cp);
94     i += skipSpace (cp + i);
95     if (cp[i] == '=')
96     {
97         int v0, v1;
98         i++;
99         i += skipSpace (cp + i);
100         if (cp[i] == '\"' || cp[i] == '\'')
101         {
102             char tr = cp[i];
103             v0 = ++i;
104             while (cp[i] != tr && cp[i])
105                 i++; 
106             v1 = i;
107             if (cp[i])
108                 i++;
109         }
110         else
111         {
112             v0 = i;
113             while (cp[i] && !strchr (SPACECHR ">", cp[i]))
114                 i++;
115             v1 = i;
116         }
117         *value = cp + v0;
118         *val_len = v1 - v0;
119     }
120     i += skipSpace (cp + i);
121     return i;
122 }
123
124 static int tagAttrs (mp::HTMLParserEvent & event, 
125                      const char *tagName,
126                      const char *cp)
127 {
128     int i;
129     char attr_name[TAG_MAX_LEN];
130     const char *attr_value;
131     int val_len;
132     i = skipSpace (cp);
133     while (cp[i] && cp[i] != '>')
134     {
135         int nor = skipAttribute (cp+i, attr_name, &attr_value, &val_len);
136         i += nor;
137         if (nor)
138         {
139             DEBUG(printf ("------ attr %s=%s\n", attr_name, dupe(attr_value, val_len)));
140             event.attribute(tagName, attr_name, attr_value, val_len);
141         }
142         else
143         {
144             if (!nor)
145                 i++;
146         }
147     }
148     return i;
149 }
150
151 static int tagStart (mp::HTMLParserEvent & event,
152         char *tagName, const char *cp, const char which)
153 {
154     int i = 0;
155     i = skipName (cp, tagName);
156     switch (which) 
157     {
158         case '/' : 
159             DEBUG(printf ("------ tag close %s\n", tagName));
160             event.closeTag(tagName);
161             break;
162         case '!' : 
163             DEBUG(printf ("------ dtd %s\n", tagName)); 
164             break;
165         case '?' : 
166             DEBUG(printf ("------ pi %s\n", tagName)); 
167             break;
168         default :  
169             DEBUG(printf ("------ tag open %s\n", tagName));
170             event.openTagStart(tagName);
171             break;
172     }
173     return i;
174 }
175
176 static int tagEnd (mp::HTMLParserEvent & event, const char *tagName, const char *cp)
177 {
178     int i = 0;
179     while (cp[i] && cp[i] != '>')
180         i++;
181     if (cp[i] == '>')
182     {
183         event.anyTagEnd(tagName);
184         i++;
185     }
186     return i;
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         DEBUG(printf ("------ text %s\n", dupe(text_start, text_end-text_start)));
194         event.text(text_start, text_end-text_start);
195     }
196 }
197
198 static void parse_str (mp::HTMLParserEvent & event, const char *cp)
199 {
200     const char *text_start = cp;
201     const char *text_end = cp;
202     while (*cp)
203     {
204         if (cp[0] == '<' && cp[1])  //tag?
205         {
206             char which = cp[1];
207             if (which == '/') cp++;
208             if (!strchr (SPACECHR, cp[1])) //valid tag starts
209             {
210                 tagText (event, text_start, text_end); //flush any text
211                 char tagName[TAG_MAX_LEN];
212                 cp++;
213                 if (which == '/')
214                 {
215                     cp += tagStart (event, tagName, cp, which);
216                 }
217                 else if (which == '!' || which == '?') //pi or dtd
218                 {
219                     cp++;
220                     cp += tagStart (event, tagName, cp, which);
221                 }
222                 else
223                 {
224                     cp += tagStart (event, tagName, cp, which);
225                     cp += tagAttrs (event, tagName, cp);
226                 }
227                 cp += tagEnd (event, tagName, cp);
228                 text_start = cp;
229                 text_end = cp;
230                 continue;
231             }
232         }
233         //text
234         cp++;
235         text_end = cp;
236     }
237     tagText (event, text_start, text_end); //flush any text
238 }
239
240 /*
241  * Local variables:
242  * c-basic-offset: 4
243  * c-file-style: "Stroustrup"
244  * indent-tabs-mode: nil
245  * End:
246  * vim: shiftwidth=4 tabstop=8 expandtab
247  */
248