]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/import.c
2a2583950b6431eb89fcb7f16ca44dde195738fd
[nominatim.git] / nominatim / import.c
1 /*
2 */
3 #include <stdlib.h>
4 #include <string.h>
5
6 #include <libpq-fe.h>
7
8 #include <libxml/xmlstring.h>
9 #include <libxml/xmlreader.h>
10 #include <libxml/hash.h>
11
12 #include "nominatim.h"
13 #include "import.h"
14 #include "input.h"
15
16 typedef enum { FILETYPE_NONE, FILETYPE_STRUCTUREDV0P1 } filetypes_t;
17 typedef enum { FILEMODE_NONE, FILEMODE_ADD, FILEMODE_UPDATE, FILEMODE_DELETE } filemodes_t;
18
19 #define MAX_FEATUREADDRESS 500
20 #define MAX_FEATURENAMES 1000
21
22 struct feature_address {
23         int                     place_id;
24         int                     rankAddress;
25         xmlChar *       type;
26         xmlChar *       id;
27         xmlChar *       key;
28         xmlChar *       value;
29         xmlChar *       distance;
30 };
31
32 struct feature_name {
33         xmlChar *       type;
34         xmlChar *       value;
35 };
36
37 struct feature {
38         int                     placeID;
39         xmlChar *       type;
40         xmlChar *       id;
41         xmlChar *       key;
42         xmlChar *       value;
43         xmlChar *       rankAddress;
44         xmlChar *       rankSearch;
45         xmlChar *       countryCode;
46         xmlChar *       adminLevel;
47         xmlChar *       houseNumber;
48         xmlChar *       geometry;
49 } feature;
50
51 int                                     fileType = FILETYPE_NONE;
52 int                                     fileMode = FILEMODE_ADD;
53 PGconn *                                conn;
54 struct feature_address  featureAddress[MAX_FEATUREADDRESS];
55 struct feature_name     featureName[MAX_FEATURENAMES];
56 struct feature                  feature;
57 int                                     featureAddressLines = 0;
58 int                                     featureNameLines = 0;
59 int                                     featureCount = 0;
60 xmlHashTablePtr                 partionTableTagsHash;
61
62
63
64 void StartElement(xmlTextReaderPtr reader, const xmlChar *name)
65 {
66     char * value;
67     float version;
68     int isAddressLine;
69
70     if (fileType == FILETYPE_NONE)
71     {
72         // Potential to handle other file types in the future / versions
73         if (xmlStrEqual(name, BAD_CAST "osmStructured"))
74         {
75                 value = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "version");
76                 version = strtof(value, NULL);
77                 xmlFree(value);
78
79                 if (version == (float)0.1)
80                 {
81                         fileType = FILETYPE_STRUCTUREDV0P1;
82                         fileMode = FILEMODE_ADD;
83                 }
84                 else
85                 {
86                 fprintf( stderr, "Unknown osmStructured version %f\n", version );
87                 exit_nicely();
88                 }
89         }
90         else
91         {
92             fprintf( stderr, "Unknown XML document type: %s\n", name );
93             exit_nicely();
94         }
95         return;
96     }
97
98     if (xmlStrEqual(name, BAD_CAST "add"))
99     {
100         fileMode = FILEMODE_ADD;
101         return;
102     }
103     if (xmlStrEqual(name, BAD_CAST "update"))
104     {
105         fileMode = FILEMODE_UPDATE;
106         return;
107     }
108     if (xmlStrEqual(name, BAD_CAST "delete"))
109     {
110         fileMode = FILEMODE_DELETE;
111         return;
112     }
113     if (fileMode == FILEMODE_NONE)
114     {
115         fprintf( stderr, "Unknown import mode in: %s\n", name );
116         exit_nicely();
117     }
118
119     if (xmlStrEqual(name, BAD_CAST "feature"))
120     {
121         feature.type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
122         feature.id = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
123         feature.key = xmlTextReaderGetAttribute(reader, BAD_CAST "key");
124         feature.value = xmlTextReaderGetAttribute(reader, BAD_CAST "value");
125         feature.rankAddress = xmlTextReaderGetAttribute(reader, BAD_CAST "rank");
126         feature.rankSearch = xmlTextReaderGetAttribute(reader, BAD_CAST "importance");
127
128                 feature.countryCode = NULL;
129                 feature.adminLevel = NULL;
130                 feature.houseNumber = NULL;
131                 feature.geometry = NULL;
132                 featureAddressLines = 0;
133                 featureNameLines = 0;
134
135         return;
136     }
137     if (xmlStrEqual(name, BAD_CAST "names")) return;
138     if (xmlStrEqual(name, BAD_CAST "name"))
139     {
140         featureName[featureNameLines].type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
141         featureName[featureNameLines].value = xmlTextReaderReadString(reader);
142         featureNameLines++;
143         if (featureNameLines >= MAX_FEATURENAMES)
144         {
145             fprintf( stderr, "Too many name elements\n");
146             exit_nicely();
147         }
148         return;
149     }
150     if (xmlStrEqual(name, BAD_CAST "osmGeometry"))
151     {
152         feature.geometry = xmlTextReaderReadString(reader);
153         return;
154     }
155     if (xmlStrEqual(name, BAD_CAST "adminLevel"))
156     {
157         feature.adminLevel = xmlTextReaderReadString(reader);
158         return;
159     }
160     if (xmlStrEqual(name, BAD_CAST "countryCode"))
161     {
162         feature.countryCode = xmlTextReaderReadString(reader);
163         return;
164     }
165     if (xmlStrEqual(name, BAD_CAST "houseNumber"))
166     {
167         feature.houseNumber = xmlTextReaderReadString(reader);
168         return;
169     }
170     if (xmlStrEqual(name, BAD_CAST "address"))
171     {
172         featureAddressLines = 0;
173         return;
174     }
175     isAddressLine = 0;
176     if (xmlStrEqual(name, BAD_CAST "continent"))
177     {
178         isAddressLine = 1;
179     }
180     else if (xmlStrEqual(name, BAD_CAST "sea"))
181     {
182         isAddressLine = 1;
183     }
184     else if (xmlStrEqual(name, BAD_CAST "country"))
185     {
186         isAddressLine = 1;
187     }
188     else if (xmlStrEqual(name, BAD_CAST "state"))
189     {
190         isAddressLine = 1;
191     }
192     else if (xmlStrEqual(name, BAD_CAST "county"))
193     {
194         isAddressLine = 1;
195     }
196     else if (xmlStrEqual(name, BAD_CAST "city"))
197     {
198         isAddressLine = 1;
199     }
200     else if (xmlStrEqual(name, BAD_CAST "town"))
201     {
202         isAddressLine = 1;
203     }
204     else if (xmlStrEqual(name, BAD_CAST "village"))
205     {
206         isAddressLine = 1;
207     }
208     else if (xmlStrEqual(name, BAD_CAST "unknown"))
209     {
210         isAddressLine = 1;
211     }
212     else if (xmlStrEqual(name, BAD_CAST "suburb"))
213     {
214         isAddressLine = 1;
215     }
216     else if (xmlStrEqual(name, BAD_CAST "postcode"))
217     {
218         isAddressLine = 1;
219     }
220     else if (xmlStrEqual(name, BAD_CAST "neighborhood"))
221     {
222         isAddressLine = 1;
223     }
224     else if (xmlStrEqual(name, BAD_CAST "street"))
225     {
226         isAddressLine = 1;
227     }
228     else if (xmlStrEqual(name, BAD_CAST "access"))
229     {
230         isAddressLine = 1;
231     }
232     else if (xmlStrEqual(name, BAD_CAST "building"))
233     {
234         isAddressLine = 1;
235     }
236     else if (xmlStrEqual(name, BAD_CAST "other"))
237     {
238         isAddressLine = 1;
239     }
240     if (isAddressLine)
241     {
242         value = (char*)xmlTextReaderGetAttribute(reader, BAD_CAST "rank");
243         if (!value)
244         {
245             fprintf( stderr, "Address element missing rank\n");
246             exit_nicely();
247         }
248         featureAddress[featureAddressLines].rankAddress =  atoi(value);
249         xmlFree(value);
250
251         featureAddress[featureAddressLines].type = xmlTextReaderGetAttribute(reader, BAD_CAST "type");
252         featureAddress[featureAddressLines].id = xmlTextReaderGetAttribute(reader, BAD_CAST "id");
253         featureAddress[featureAddressLines].key = xmlTextReaderGetAttribute(reader, BAD_CAST "key");
254         featureAddress[featureAddressLines].value = xmlTextReaderGetAttribute(reader, BAD_CAST "value");
255         featureAddress[featureAddressLines].distance = xmlTextReaderGetAttribute(reader, BAD_CAST "distance");
256
257         featureAddressLines++;
258         if (featureAddressLines >= MAX_FEATUREADDRESS)
259         {
260             fprintf( stderr, "Too many address elements\n");
261             exit_nicely();
262         }
263
264         return;
265     }
266     fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
267 }
268
269 void EndElement(xmlTextReaderPtr reader, const xmlChar *name)
270 {
271         PGresult *              res;
272         PGresult *              resPlaceID;
273     const char *        paramValues[11];
274     char *                      place_id;
275     char *                      partionQueryName;
276         int i;
277
278     if (xmlStrEqual(name, BAD_CAST "feature"))
279         {
280         featureCount++;
281         if (featureCount % 1000 == 0) printf("feature %i(k)\n", featureCount/1000);
282
283         if (fileMode == FILEMODE_ADD)
284         {
285                 resPlaceID = PQexecPrepared(conn, "get_new_place_id", 0, NULL, NULL, NULL, 0);
286                 if (PQresultStatus(resPlaceID) != PGRES_TUPLES_OK)
287                         {
288                                 fprintf(stderr, "get_place_id: INSERT failed: %s", PQerrorMessage(conn));
289                                 PQclear(resPlaceID);
290                                 exit(EXIT_FAILURE);
291                         }
292         }
293         else
294         {
295             paramValues[0] = (const char *)feature.type;
296             paramValues[1] = (const char *)feature.id;
297             paramValues[2] = (const char *)feature.key;
298             paramValues[3] = (const char *)feature.value;
299             resPlaceID = PQexecPrepared(conn, "get_new_place_id", 4, paramValues, NULL, NULL, 0);
300             if (PQresultStatus(resPlaceID) != PGRES_TUPLES_OK)
301             {
302                 fprintf(stderr, "index_placex: INSERT failed: %s", PQerrorMessage(conn));
303                 PQclear(resPlaceID);
304                 exit(EXIT_FAILURE);
305             }
306         }
307                 place_id = PQgetvalue(resPlaceID, 0, 0);
308
309         if (fileMode == FILEMODE_UPDATE || fileMode == FILEMODE_DELETE)
310         {
311             paramValues[0] = (const char *)place_id;
312             res = PQexecPrepared(conn, "placex_delete", 1, paramValues, NULL, NULL, 0);
313             if (PQresultStatus(res) != PGRES_COMMAND_OK)
314             {
315                 fprintf(stderr, "placex_delete: DELETE failed: %s", PQerrorMessage(conn));
316                 PQclear(res);
317                 exit(EXIT_FAILURE);
318             }
319             PQclear(res);
320
321             res = PQexecPrepared(conn, "search_name_delete", 1, paramValues, NULL, NULL, 0);
322             if (PQresultStatus(res) != PGRES_COMMAND_OK)
323             {
324                 fprintf(stderr, "search_name_delete: DELETE failed: %s", PQerrorMessage(conn));
325                 PQclear(res);
326                 exit(EXIT_FAILURE);
327             }
328             PQclear(res);
329
330             res = PQexecPrepared(conn, "place_addressline_delete", 1, paramValues, NULL, NULL, 0);
331             if (PQresultStatus(res) != PGRES_COMMAND_OK)
332             {
333                 fprintf(stderr, "place_addressline_delete: DELETE failed: %s", PQerrorMessage(conn));
334                 PQclear(res);
335                 exit(EXIT_FAILURE);
336             }
337             PQclear(res);
338         }
339
340         if (fileMode == FILEMODE_UPDATE || fileMode == FILEMODE_ADD)
341         {
342                         // Insert into placex
343                         paramValues[0] = (const char *)place_id;
344                         paramValues[1] = (const char *)feature.type;
345                         paramValues[2] = (const char *)feature.id;
346                         paramValues[3] = (const char *)feature.key;
347                         paramValues[4] = (const char *)feature.value;
348 //                      paramValues[5] = (const char *)feature.name;
349                         paramValues[6] = (const char *)feature.adminLevel;
350                         paramValues[7] = (const char *)feature.houseNumber;
351                         paramValues[8] = (const char *)feature.rankAddress;
352                         paramValues[9] = (const char *)feature.rankSearch;
353                         paramValues[10] = (const char *)feature.geometry;
354                         res = PQexecPrepared(conn, "placex_insert", 11, paramValues, NULL, NULL, 0);
355                         if (PQresultStatus(res) != PGRES_COMMAND_OK)
356                         {
357                                 fprintf(stderr, "index_placex: INSERT failed: %s", PQerrorMessage(conn));
358                                 PQclear(res);
359                                 exit(EXIT_FAILURE);
360                         }
361                         PQclear(res);
362
363                         for(i = 0; i < featureAddressLines; i++)
364                         {
365                                 // insert into place_address
366                                 paramValues[0] = (const char *)place_id;
367                                 paramValues[1] = (const char *)featureAddress[i].distance;
368                                 paramValues[2] = (const char *)featureAddress[i].type;
369                                 paramValues[3] = (const char *)featureAddress[i].id;
370                                 paramValues[4] = (const char *)featureAddress[i].key;
371                                 paramValues[5] = (const char *)featureAddress[i].value;
372                                 res = PQexecPrepared(conn, "place_addressline_insert", 6, paramValues, NULL, NULL, 0);
373                                 if (PQresultStatus(res) != PGRES_COMMAND_OK)
374                                 {
375                                         fprintf(stderr, "place_addressline_insert: INSERT failed: %s", PQerrorMessage(conn));
376                                         PQclear(res);
377                                         exit(EXIT_FAILURE);
378                                 }
379                                 PQclear(res);
380
381                                 xmlFree(featureAddress[i].type);
382                                 xmlFree(featureAddress[i].id);
383                                 xmlFree(featureAddress[i].key);
384                                 xmlFree(featureAddress[i].value);
385                                 xmlFree(featureAddress[i].distance);
386                         }
387
388                         if (featureNameLines)
389                         {
390                                 paramValues[0] = (const char *)place_id;
391                                 res = PQexecPrepared(conn, "search_name_insert", 1, paramValues, NULL, NULL, 0);
392                                 if (PQresultStatus(res) != PGRES_COMMAND_OK)
393                                 {
394                                         fprintf(stderr, "search_name_insert: INSERT failed: %s", PQerrorMessage(conn));
395                                         PQclear(res);
396                                         exit(EXIT_FAILURE);
397                                 }
398                                 PQclear(res);
399                         }
400
401                         partionQueryName = xmlHashLookup2(partionTableTagsHash, feature.key, feature.value);
402                         if (partionQueryName)
403                         {
404                                 // insert into partition table
405                                 paramValues[0] = (const char *)place_id;
406                                 paramValues[1] = (const char *)feature.geometry;
407                                 res = PQexecPrepared(conn, partionQueryName, 2, paramValues, NULL, NULL, 0);
408                                 if (PQresultStatus(res) != PGRES_COMMAND_OK)
409                                 {
410                                         fprintf(stderr, "%s: INSERT failed: %s", partionQueryName, PQerrorMessage(conn));
411                                         PQclear(res);
412                                         exit(EXIT_FAILURE);
413                                 }
414                                 PQclear(res);
415
416                         }
417
418         }
419         else
420         {
421                         for(i = 0; i < featureAddressLines; i++)
422                         {
423                                 xmlFree(featureAddress[i].type);
424                                 xmlFree(featureAddress[i].id);
425                                 xmlFree(featureAddress[i].key);
426                                 xmlFree(featureAddress[i].value);
427                                 xmlFree(featureAddress[i].distance);
428                         }
429         }
430
431                 xmlFree(feature.type);
432                 xmlFree(feature.id);
433                 xmlFree(feature.key);
434                 xmlFree(feature.value);
435                 xmlFree(feature.rankAddress);
436                 xmlFree(feature.rankSearch);
437 //              if (feature.name) xmlFree(feature.name);
438                 if (feature.countryCode) xmlFree(feature.countryCode);
439                 if (feature.adminLevel) xmlFree(feature.adminLevel);
440                 if (feature.houseNumber) xmlFree(feature.houseNumber);
441                 if (feature.geometry) xmlFree(feature.geometry);
442
443                 PQclear(resPlaceID);
444         }
445 }
446
447 static void processNode(xmlTextReaderPtr reader)
448 {
449         xmlChar *name;
450     name = xmlTextReaderName(reader);
451     if (name == NULL)
452     {
453         name = xmlStrdup(BAD_CAST "--");
454     }
455
456     switch(xmlTextReaderNodeType(reader))
457     {
458         case XML_READER_TYPE_ELEMENT:
459             StartElement(reader, name);
460             if (xmlTextReaderIsEmptyElement(reader))
461                 EndElement(reader, name); /* No end_element for self closing tags! */
462             break;
463         case XML_READER_TYPE_END_ELEMENT:
464             EndElement(reader, name);
465             break;
466         case XML_READER_TYPE_TEXT:
467         case XML_READER_TYPE_CDATA:
468         case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
469             /* Ignore */
470             break;
471         default:
472             fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader));
473             break;
474     }
475
476     xmlFree(name);
477 }
478
479 int nominatim_import(const char *conninfo, const char *partionTagsFilename, const char *filename)
480 {
481     xmlTextReaderPtr    reader;
482     int                                 ret = 0;
483         PGresult *                      res;
484         FILE *                          partionTagsFile;
485         char *                          partionQueryName;
486     char                                partionQuerySQL[1024];
487
488         conn = PQconnectdb(conninfo);
489     if (PQstatus(conn) != CONNECTION_OK)
490     {
491         fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
492         exit(EXIT_FAILURE);
493     }
494
495         partionTableTagsHash = xmlHashCreate(200);
496
497         partionTagsFile = fopen(partionTagsFilename, "rt");
498         if (!partionTagsFile)
499     {
500         fprintf(stderr, "Unable to read partition tags file: %s\n", partionTagsFilename);
501         exit(EXIT_FAILURE);
502     }
503
504         char buffer[1024], osmkey[256], osmvalue[256];
505         int fields;
506         while(fgets(buffer, sizeof(buffer), partionTagsFile) != NULL)
507         {
508                 fields = sscanf( buffer, "%23s %63s", osmkey, osmvalue );
509
510                 if( fields <= 0 ) continue;
511
512             if( fields != 2  )
513             {
514               fprintf( stderr, "Error partition file\n");
515               exit_nicely();
516             }
517             partionQueryName = malloc(strlen("partition_insert_")+strlen(osmkey)+strlen(osmvalue)+2);
518                 strcpy(partionQueryName, "partition_insert_");
519                 strcat(partionQueryName, osmkey);
520                 strcat(partionQueryName, "_");
521                 strcat(partionQueryName, osmvalue);
522
523                 strcpy(partionQuerySQL, "insert into place_classtype_");
524                 strcat(partionQuerySQL, osmkey);
525                 strcat(partionQuerySQL, "_");
526                 strcat(partionQuerySQL, osmvalue);
527                 strcat(partionQuerySQL, " (place_id, centroid) values ($1, ST_Centroid(st_setsrid($2, 4326)))");
528
529             res = PQprepare(conn, partionQueryName, partionQuerySQL, 2, NULL);
530             if (PQresultStatus(res) != PGRES_COMMAND_OK)
531                 {
532                 fprintf(stderr, "Failed to prepare %s: %s\n", partionQueryName, PQerrorMessage(conn));
533                 exit(EXIT_FAILURE);
534                 }
535
536                 xmlHashAddEntry2(partionTableTagsHash, BAD_CAST osmkey, BAD_CAST osmvalue, BAD_CAST partionQueryName);
537         }
538
539     res = PQprepare(conn, "get_new_place_id",
540                 "select nextval('seq_place')",
541         0, NULL);
542     if (PQresultStatus(res) != PGRES_COMMAND_OK)
543         {
544         fprintf(stderr, "Failed to prepare get_new_place_id: %s\n", PQerrorMessage(conn));
545         exit(EXIT_FAILURE);
546         }
547
548     res = PQprepare(conn, "get_place_id",
549                 "select place_id from placex where osm_type = $1 and osm_id = $2 and class = $3 and type = $4",
550         4, NULL);
551     if (PQresultStatus(res) != PGRES_COMMAND_OK)
552         {
553         fprintf(stderr, "Failed to prepare get_place_id: %s\n", PQerrorMessage(conn));
554         exit(EXIT_FAILURE);
555         }
556
557     res = PQprepare(conn, "placex_insert",
558                 "insert into placex (place_id,osm_type,osm_id,class,type,name,admin_level,housenumber,rank_address,rank_search,geometry) "
559                 "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, st_setsrid($11, 4326))",
560         11, NULL);
561     if (PQresultStatus(res) != PGRES_COMMAND_OK)
562         {
563         fprintf(stderr, "Failed to prepare placex_insert: %s\n", PQerrorMessage(conn));
564         exit(EXIT_FAILURE);
565         }
566
567     res = PQprepare(conn, "search_name_insert",
568                 "insert into search_name (place_id, search_rank, address_rank, country_code, name_vector, nameaddress_vector, centroid) "
569                 "select place_id, rank_address, rank_search, country_code, make_keywords(name), "
570                 "(select uniq(sort(array_agg(name_vector))) from place_addressline join search_name on "
571                 "(address_place_id = search_name.place_id) where place_addressline.place_id = $1 ), st_centroid(geometry) from placex "
572                 "where place_id = $1",
573         1, NULL);
574     if (PQresultStatus(res) != PGRES_COMMAND_OK)
575         {
576         fprintf(stderr, "Failed to prepare placex_insert: %s\n", PQerrorMessage(conn));
577         exit(EXIT_FAILURE);
578         }
579
580     res = PQprepare(conn, "place_addressline_insert",
581                 "insert into place_addressline (place_id, address_place_id, fromarea, isaddress, distance, cached_rank_address) "
582                 "select $1, place_id, false, true, $2, rank_address from placex where osm_type = $3 and osm_id = $4 and class = $5 and type = $6",
583         6, NULL);
584     if (PQresultStatus(res) != PGRES_COMMAND_OK)
585         {
586         fprintf(stderr, "Failed to prepare place_addressline_insert: %s\n", PQerrorMessage(conn));
587         exit(EXIT_FAILURE);
588         }
589
590     res = PQprepare(conn, "placex_delete",
591                 "delete from placex where place_id = $1",
592         1, NULL);
593     if (PQresultStatus(res) != PGRES_COMMAND_OK)
594         {
595         fprintf(stderr, "Failed to prepare placex_delete: %s\n", PQerrorMessage(conn));
596         exit(EXIT_FAILURE);
597         }
598
599     res = PQprepare(conn, "search_name_delete",
600                 "delete from search_name where place_id = $1",
601         1, NULL);
602     if (PQresultStatus(res) != PGRES_COMMAND_OK)
603         {
604         fprintf(stderr, "Failed to prepare search_name_delete: %s\n", PQerrorMessage(conn));
605         exit(EXIT_FAILURE);
606         }
607
608     res = PQprepare(conn, "place_addressline_delete",
609                 "delete from place_addressline where place_id = $1",
610         1, NULL);
611     if (PQresultStatus(res) != PGRES_COMMAND_OK)
612         {
613         fprintf(stderr, "Failed to prepare place_addressline_delete: %s\n", PQerrorMessage(conn));
614         exit(EXIT_FAILURE);
615         }
616
617     featureCount = 0;
618
619     reader = inputUTF8(filename);
620
621     if (reader == NULL)
622     {
623         fprintf(stderr, "Unable to open %s\n", filename);
624         return 1;
625     }
626
627     ret = xmlTextReaderRead(reader);
628     while (ret == 1)
629     {
630         processNode(reader);
631         ret = xmlTextReaderRead(reader);
632     }
633         if (ret != 0) {
634                 fprintf(stderr, "%s : failed to parse\n", filename);
635                 return ret;
636         }
637
638         xmlFreeTextReader(reader);
639         xmlHashFree(partionTableTagsHash, NULL);
640
641     return 0;
642 }