]> git.tdb.fi Git - ext/subsurface.git/blob - parse.c
Move the "text" nodename hackery out of 'nodename()'
[ext/subsurface.git] / parse.c
1 #include <stdio.h>
2 #include <ctype.h>
3 #include <string.h>
4 #include <libxml/parser.h>
5 #include <libxml/tree.h>
6
7 /*
8  * Some silly typedefs to make our units very explicit.
9  *
10  * Also, the units are chosen so that values can be expressible as
11  * integers, so that we never have FP rounding issues. And they
12  * are small enough that converting to/from imperial units doesn't
13  * really matter.
14  *
15  * We also strive to make '0' a meaningless number saying "not
16  * initialized", since many values are things that may not have
17  * been reported (eg tank pressure or temperature from dive
18  * computers that don't support them). But sometimes -1 is an even
19  * more explicit way of saying "not there".
20  *
21  * Thus "millibar" for pressure, for example, or "millikelvin" for
22  * temperatures. Doing temperatures in celsius or fahrenheit would
23  * make for loss of precision when converting from one to the other,
24  * and using millikelvin is SI-like but also means that a temperature
25  * of '0' is clearly just a missing temperature or tank pressure.
26  *
27  * Also strive to use units that can not possibly be mistaken for a
28  * valid value in a "normal" system without conversion. If the max
29  * depth of a dive is '20000', you probably didn't convert from mm on
30  * output, or if the max depth gets reported as "0.2ft" it was either
31  * a really boring dive, or there was some missing input conversion,
32  * and a 60-ft dive got recorded as 60mm.
33  *
34  * Doing these as "structs containing value" means that we always
35  * have to explicitly write out those units in order to get at the
36  * actual value. So there is hopefully little fear of using a value
37  * in millikelvin as Fahrenheit by mistake.
38  *
39  * We don't actually use these all yet, so maybe they'll change, but
40  * I made a number of types as guidelines.
41  */
42 typedef struct {
43         int seconds;
44 } duration_t;
45
46 typedef struct {
47         int mm;
48 } depth_t;
49
50 typedef struct {
51         int mbar;
52 } pressure_t;
53
54 typedef struct {
55         int mkelvin;
56 } temperature_t;
57
58 typedef struct {
59         int mliter;
60 } volume_t;
61
62 typedef struct {
63         int permille;
64 } fraction_t;
65
66 typedef struct {
67         int grams;
68 } weight_t;
69
70 typedef struct {
71         fraction_t o2;
72         fraction_t n2;
73         fraction_t he2;
74 } gasmix_t;
75
76 typedef struct {
77         volume_t size;
78         pressure_t pressure;
79 } tank_type_t;
80
81 struct sample {
82         duration_t time;
83         depth_t depth;
84         temperature_t temperature;
85         pressure_t tankpressure;
86         int tankindex;
87 };
88
89 struct dive {
90         time_t when;
91         depth_t maxdepth, meandepth;
92         duration_t duration, surfacetime;
93         depth_t visibility;
94         temperature_t airtemp, watertemp;
95         pressure_t beginning_pressure, end_pressure;
96         int samples;
97         struct sample sample[];
98 };
99
100 static void record_dive(struct dive *dive)
101 {
102         static int nr;
103
104         printf("Recording dive %d with %d samples\n", ++nr, dive->samples);
105 }
106
107 static void nonmatch(const char *type, const char *fullname, const char *name, int size, const char *buffer)
108 {
109         printf("Unable to match %s '(%.*s)%s' (%.*s)\n", type,
110                 (int) (name - fullname), fullname, name,
111                 size, buffer);
112 }
113
114 static const char *last_part(const char *name)
115 {
116         const char *p = strrchr(name, '.');
117         return p ? p+1 : name;
118 }
119
120 /* We're in samples - try to convert the random xml value to something useful */
121 static void try_to_fill_sample(struct sample *sample, const char *name, int size, const char *buffer)
122 {
123         const char *last = last_part(name);
124         nonmatch("sample", name, last, size, buffer);
125 }
126
127 /* We're in the top-level dive xml. Try to convert whatever value to a dive value */
128 static void try_to_fill_dive(struct dive *dive, const char *name, int size, const char *buffer)
129 {
130         const char *last = last_part(name);
131         nonmatch("dive", name, last, size, buffer);
132 }
133
134 /*
135  * Dive info as it is being built up..
136  */
137 static int alloc_samples;
138 static struct dive *dive;
139 static struct sample *sample;
140
141 static unsigned int dive_size(int samples)
142 {
143         return sizeof(struct dive) + samples*sizeof(struct sample);
144 }
145
146 /*
147  * File boundaries are dive boundaries. But sometimes there are
148  * multiple dives per file, so there can be other events too that
149  * trigger a "new dive" marker and you may get some nesting due
150  * to that. Just ignore nesting levels.
151  */
152 static void dive_start(void)
153 {
154         unsigned int size;
155
156         alloc_samples = 5;
157         size = dive_size(alloc_samples);
158         dive = malloc(size);
159         if (!dive)
160                 exit(1);
161         memset(dive, 0, size);
162 }
163
164 static void dive_end(void)
165 {
166         if (!dive)
167                 return;
168         record_dive(dive);
169         dive = NULL;
170 }
171
172 static void sample_start(void)
173 {
174         int nr;
175
176         if (!dive)
177                 return;
178         nr = dive->samples;
179         if (nr >= alloc_samples) {
180                 unsigned int size;
181
182                 alloc_samples = (alloc_samples * 3)/2 + 10;
183                 size = dive_size(alloc_samples);
184                 dive = realloc(dive, size);
185                 if (!dive)
186                         return;
187         }
188         sample = dive->sample + nr;
189 }
190
191 static void sample_end(void)
192 {
193         sample = NULL;
194         if (!dive)
195                 return;
196         dive->samples++;
197 }
198
199 static void entry(const char *name, int size, const char *buffer)
200 {
201         if (sample) {
202                 try_to_fill_sample(sample, name, size, buffer);
203                 return;
204         }
205         if (dive) {
206                 try_to_fill_dive(dive, name, size, buffer);
207                 return;
208         }
209 }
210
211 static const char *nodename(xmlNode *node, char *buf, int len)
212 {
213
214         if (!node || !node->name)
215                 return "root";
216
217         buf += len;
218         *--buf = 0;
219         len--;
220
221         for(;;) {
222                 const char *name = node->name;
223                 int i = strlen(name);
224                 while (--i >= 0) {
225                         unsigned char c = name[i];
226                         *--buf = tolower(c);
227                         if (!--len)
228                                 return buf;
229                 }
230                 node = node->parent;
231                 if (!node || !node->name)
232                         return buf;
233                 *--buf = '.';
234                 if (!--len)
235                         return buf;
236         }
237 }
238
239 #define MAXNAME 64
240
241 static void visit_one_node(xmlNode *node)
242 {
243         int len;
244         const unsigned char *content;
245         char buffer[MAXNAME];
246         const char *name;
247
248         content = node->content;
249         if (!content)
250                 return;
251
252         /* Trim whitespace at beginning */
253         while (isspace(*content))
254                 content++;
255
256         /* Trim whitespace at end */
257         len = strlen(content);
258         while (len && isspace(content[len-1]))
259                 len--;
260
261         if (!len)
262                 return;
263
264         /* Don't print out the node name if it is "text" */
265         if (!strcmp(node->name, "text"))
266                 node = node->parent;
267
268         name = nodename(node, buffer, sizeof(buffer));
269
270         entry(name, len, content);
271 }
272
273 static void traverse(xmlNode *node)
274 {
275         xmlNode *n;
276
277         for (n = node; n; n = n->next) {
278                 /* XML from libdivecomputer: 'dive' per new dive */
279                 if (!strcmp(n->name, "dive")) {
280                         dive_start();
281                         traverse(n->children);
282                         dive_end();
283                         continue;
284                 }
285
286                 /*
287                  * At least both libdivecomputer and Suunto
288                  * agree on "sample".
289                  *
290                  * Well - almost. Ignore case.
291                  */
292                 if (!strcasecmp(n->name, "sample")) {
293                         sample_start();
294                         traverse(n->children);
295                         sample_end();
296                         continue;
297                 }
298
299                 /* Anything else - just visit it and recurse */
300                 visit_one_node(n);
301                 traverse(n->children);
302         }
303 }
304
305 static void parse(const char *filename)
306 {
307         xmlDoc *doc;
308
309         doc = xmlReadFile(filename, NULL, 0);
310         if (!doc) {
311                 fprintf(stderr, "Failed to parse '%s'.\n", filename);
312                 return;
313         }
314
315         dive_start();
316         traverse(xmlDocGetRootElement(doc));
317         dive_end();
318         xmlFreeDoc(doc);
319         xmlCleanupParser();
320 }
321
322 int main(int argc, char **argv)
323 {
324         int i;
325
326         LIBXML_TEST_VERSION
327
328         for (i = 1; i < argc; i++)
329                 parse(argv[i]);
330         return 0;
331 }