source: subversion/applications/utils/planetdiff/planetdiff.c @ 6236

Revision 6236, 21.6 KB checked in by jonb, 6 years ago (diff)

planetdiff: Remove blank line from output

  • Property svn:keywords set to Revision
Line 
1/*
2#-----------------------------------------------------------------------------
3# planetdiff - Calculates the differences between 2 planet.osm
4# Roughly equivalent to 'diff' but optimised for planet.osm files
5#
6# Use:
7#      planetdiff planetA.osm planetB.osm > delta-AB.xml
8#
9#      planetpatch planetA.osm delta-AB.xml > planetB.osm
10#
11# One of the input files may come from STDIN by specifying - as the filename
12# Files can be gzip or bzip2 compressed.
13#
14#-----------------------------------------------------------------------------
15# Written by Jon Burgess, Copyright 2007
16#
17# This program is free software; you can redistribute it and/or
18# modify it under the terms of the GNU General Public License
19# as published by the Free Software Foundation; either version 2
20# of the License, or (at your option) any later version.
21#
22# This program is distributed in the hope that it will be useful,
23# but WITHOUT ANY WARRANTY; without even the implied warranty of
24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25# GNU General Public License for more details.
26#
27# You should have received a copy of the GNU General Public License
28# along with this program; if not, write to the Free Software
29# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
30#-----------------------------------------------------------------------------
31*/
32
33/* TODO:
34 * - Empty!
35 *
36 * DONE:
37 * Escape key/values on output (&"'<>)
38 * write patch tool
39 * Error if file not in required sequence (node/way/relation with increasing ID)
40 * Warn if generator is not planet.rb since others tend to violate ordering above
41 * Generate run-time stats on stderr
42 * Allow direct reading of .gz and .bz2 files
43 * Perform UTF8sanitize on input
44 */
45
46// define VERBOSE if you want run time stats
47#undef VERBOSE
48
49#include <stdio.h>
50#include <unistd.h>
51#include <stdlib.h>
52#include <string.h>
53#include <assert.h>
54
55#include <libxml/xmlstring.h>
56#include <libxml/xmlreader.h>
57
58#include "keyvals.h"
59#include "input.h"
60#include "sanitizer.h"
61
62/* Since {node,way,relation} elements are not nested we can
63 * accumulates the attributes, tags and nodes in global data.
64 * This is maintained independently for planetA and planetB
65 */
66enum type { invalid, node, way, relation, eof } current[2];
67static int osm_id[2];
68static struct keyval tags[2], nds[2], attrs[2];
69static struct keyval member_nodes[2], member_ways[2], member_rels[2];
70static xmlTextReaderPtr reader[2];
71static const char *files[2];
72// Strict mode is considers timestamp and key ordering changes as significant, otherwise they are ignored
73static const int strict = 0;
74// Do we run UTF8sanitizer on input, this should only be necessary if you are using very old planet dumps.
75static const int sanitize = 0;
76
77static int compareTypeID() {
78    // Compare object types
79    if (current[0] < current[1])
80        return -1;
81    if (current[0] > current[1])
82        return +1;
83
84    if (osm_id[0] < osm_id[1])
85        return -1;
86    if (osm_id[0] > osm_id[1])
87        return +1;
88
89    return 0;
90}
91
92static int compareKeyval(struct keyval kv[2])
93{
94    struct keyval *p[2];
95    int r;
96
97    p[0] = kv[0].next;
98    p[1] = kv[1].next;
99#define END(x) (p[x] == &kv[x])
100
101    while(1) {
102        if (END(0) && END(1))
103            return 0;
104        if (END(0))
105            return -1;
106        if (END(1))
107            return +1;
108
109        r = strcmp(p[0]->key, p[1]->key);
110        if (r) return r;
111
112        // Ignore the value of any timstemap= for comparison
113        // This skips formatting and other uninteresting timestamp-only updates
114        // Note: this triggers on both k="timestamp" as well as timestamp=...
115        if (strict || strcmp(p[0]->key, "timestamp")) {
116            r = strcmp(p[0]->value, p[1]->value);
117            if (r) return r;
118        }
119
120        p[0] = p[0]->next;
121        p[1] = p[1]->next;
122    }
123    return 0;
124}
125
126int compareKV(const void *hA, const void *hB)
127{
128    struct keyval * const *a = hA;
129    struct keyval * const *b = hB;
130    int c;
131
132    c = strcmp((*a)->key, (*b)->key);
133    if (c) return c;
134
135    return strcmp((*a)->value, (*b)->value);
136}
137
138void sortList(struct keyval *in, struct keyval *out)
139{
140    struct keyval **t, *p;
141    int len = countList(in);
142    int i;
143
144    initList(out);
145    if (!len)
146        return;
147
148    t = calloc(len, sizeof(*in));
149    assert(t);
150
151    for (i=0, p = in->next; i<len; i++, p = p->next)
152        t[i] = p;
153
154    qsort(t, len, sizeof(*t), compareKV);
155
156    for (i=0; i<len; i++)
157        addItem(out, t[i]->key, t[i]->value, 0);
158
159    free(t);
160}
161
162static int compareLists(struct keyval kv[2])
163{
164    // Strict mode requires all lists to be in a consistent order
165    // non-strict treats the 2 sets of tags below as equivalent
166    //
167    // <k='A' v='1' />
168    // <k='B' v='2' />
169    //
170    // <k='B' v='2' />
171    // <k='A' v='1' />
172
173    struct keyval clone[2];
174    int r;
175
176    if (strict)
177        return compareKeyval(kv);
178
179    if (!listHasData(&kv[0]) && !listHasData(&kv[1]))
180        return 0;
181
182    sortList(&kv[0], &clone[0]);
183    sortList(&kv[1], &clone[1]);
184    r = compareKeyval(clone);
185    resetList(&clone[0]);
186    resetList(&clone[1]);
187
188    return r;
189}
190
191
192static int compareOther(void)
193{
194    int c;
195
196    c = compareKeyval(attrs);
197    if (c) return c;
198
199    c = compareLists(tags);
200    if (c) return c;
201
202    c = compareKeyval(nds);
203    if (c) return c;
204
205    c = compareLists(member_nodes);
206    if (c) return c;
207
208    c = compareLists(member_ways);
209    if (c) return c;
210
211    c = compareLists(member_rels);
212    if (c) return c;
213
214    return 0;
215}
216
217static void collectAttributes(int i)
218{
219    int ret;
220    const xmlChar *name, *value;
221    while ((ret = xmlTextReaderMoveToNextAttribute(reader[i])) == 1) {
222        name  = xmlTextReaderConstName(reader[i]);
223        value = xmlTextReaderConstValue(reader[i]);
224        addItem(&attrs[i], (char *)name, (char *)value, 0);
225        //fprintf(stderr, "%s = %s\n", (char *)name, (char *)value);
226    }
227    if (ret != 0) {
228        fprintf(stderr, "%s : failed to parse attributes\n", files[i]);
229        exit(1);
230    }
231}
232
233static void StartElement(int i, const xmlChar *name)
234{
235    xmlChar *xk, *xv, *xid, *xgen, *xrole, *xtype;
236    static int warn = 1;
237
238    if (xmlStrEqual(name, BAD_CAST "node")) {
239        current[i] = node;
240        collectAttributes(i);
241    } else if (xmlStrEqual(name, BAD_CAST "tag")) {
242        xk = xmlTextReaderGetAttribute(reader[i], BAD_CAST "k");
243        xv = xmlTextReaderGetAttribute(reader[i], BAD_CAST "v");
244        addItem(&tags[i], (char *)xk, (char *)xv, 0);
245        xmlFree(xk);
246        xmlFree(xv);
247    } else if (xmlStrEqual(name, BAD_CAST "member")) {
248        xtype = xmlTextReaderGetAttribute(reader[i], BAD_CAST "type");
249        xid   = xmlTextReaderGetAttribute(reader[i], BAD_CAST "ref");
250        xrole = xmlTextReaderGetAttribute(reader[i], BAD_CAST "role");
251        if (xmlStrEqual(xtype, BAD_CAST "node"))
252            addItem(&member_nodes[i], (char *)xid, (char *)xrole, 0);
253        else if (xmlStrEqual(xtype, BAD_CAST "way"))
254            addItem(&member_ways[i], (char *)xid, (char *)xrole, 0);
255        else if (xmlStrEqual(xtype, BAD_CAST "relation"))
256            addItem(&member_rels[i], (char *)xid, (char *)xrole, 0);
257        else
258            fprintf(stderr, "Unknown type: <member type='%s' ...\n", (char *)xtype);
259        xmlFree(xtype);
260        xmlFree(xid);
261        xmlFree(xrole);
262    } else if (xmlStrEqual(name, BAD_CAST "way")) {
263        current[i] = way;
264        collectAttributes(i);
265    } else if (xmlStrEqual(name, BAD_CAST "nd")) {
266        xid  = xmlTextReaderGetAttribute(reader[i], BAD_CAST "ref");
267        addItem(&nds[i], "ref", (char *)xid, 0);
268        xmlFree(xid);
269    } else if (xmlStrEqual(name, BAD_CAST "relation")) {
270        current[i] = relation;
271        collectAttributes(i);
272    } else if (xmlStrEqual(name, BAD_CAST "osm")) {
273        if (warn) {
274            xgen = xmlTextReaderGetAttribute(reader[i], BAD_CAST "generator");
275            if (!xmlStrEqual(xgen, BAD_CAST "OpenStreetMap planet.rb") && !xmlStrEqual(xgen, BAD_CAST "OpenStreetMap planet.c")) {
276                fprintf(stderr, "Warning: The input file %s was generated by: %s\n", files[i], xgen ? (char *)xgen : "unknown");
277                fprintf(stderr, "this tool relies on the planet.osm format of the format of the planet.osm file\n");
278                fprintf(stderr, "generated by planet.rb of planet.openstreetmap.org and may not work with other osm files.\n\n");
279                warn = 0;
280            }
281            xmlFree(xgen);
282        }
283    } else if (xmlStrEqual(name, BAD_CAST "bound")) {
284        // Ignored
285    } else {
286        fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
287    }
288}
289
290static int EndElement(int i, const xmlChar *name)
291{
292    // Return 0 for closing tag of node/way/relation
293    int ret;
294    if (xmlStrEqual(name, BAD_CAST "node")) {
295        ret = 0;
296    } else if (xmlStrEqual(name, BAD_CAST "way")) {
297        ret = 0;
298    } else if (xmlStrEqual(name, BAD_CAST "tag")) {
299        ret = 1;
300    } else if (xmlStrEqual(name, BAD_CAST "member")) {
301        ret = 1;
302    } else if (xmlStrEqual(name, BAD_CAST "nd")) {
303        ret = 1;
304    } else if (xmlStrEqual(name, BAD_CAST "relation")) {
305        ret = 0;
306    } else if (xmlStrEqual(name, BAD_CAST "osm")) {
307        ret = 1; // maybe should be 0
308    } else if (xmlStrEqual(name, BAD_CAST "bound")) {
309        ret = 1;
310    } else {
311        fprintf(stderr, "%s: Unknown element name: %s\n", __FUNCTION__, name);
312        ret = 1;
313    }
314    return ret;
315}
316
317// Process an XML node, returns 0 for the closing tag for a node/segment/way
318static int processNode(int i)
319{
320    int ret = 1;
321    int empty;
322    xmlChar *name = xmlTextReaderName(reader[i]);
323    if (name == NULL)
324        name = xmlStrdup(BAD_CAST "--");
325       
326    switch(xmlTextReaderNodeType(reader[i])) {
327        case XML_READER_TYPE_ELEMENT:
328            empty = xmlTextReaderIsEmptyElement(reader[i]);
329            StartElement(i, name);     
330            if (!empty)
331                break;
332            /* Drop through for self closing tags since these generate no end_element */
333        case XML_READER_TYPE_END_ELEMENT:
334            ret = EndElement(i, name);
335            break;
336        case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
337            /* Ignore */
338            break;
339        default:
340            fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader[i]));
341            break;
342    }
343    xmlFree(name);
344    return ret;
345}
346
347static const char *getName(int i)
348{
349    switch (current[i]) {
350        case node: return "node";
351        case way: return "way";
352        case relation: return "relation";
353        case invalid: return "invalid";
354        default:
355            break;
356    }
357    fprintf(stderr, "Unhandled type %d\n", current[i]);
358    exit(5);
359}
360
361// Reads in a complete OSM object, e.g. <node> to <node/>
362static void getobject(int i)
363{
364    int ret = 1;
365    const char *id;
366    enum type last_type = current[i];
367    const char *last_name = getName(i);
368    int last_id = osm_id[i];
369    //static int count;
370
371    // Delete data for previous object
372    resetList(&attrs[i]);
373    resetList(&nds[i]);
374    resetList(&tags[i]);
375    resetList(&member_nodes[i]);
376    resetList(&member_ways[i]);
377    resetList(&member_rels[i]);
378
379    current[i] = eof;
380    if (!reader[i])
381        return; // EOF
382
383    while(ret == 1) {
384        ret = xmlTextReaderRead(reader[i]);
385        if (ret == 0) {
386            //fprintf(stderr, "EOF %s\n", files[i]);
387            xmlFreeTextReader(reader[i]);
388            reader[i] = NULL;
389            return;
390        }
391        if (ret != 1) {
392            fprintf(stderr, "Error parsing file %s\n", files[i]);
393            exit(3);
394        }
395
396        ret = processNode(i);
397    }
398    // Retrieve osm_id for node/way/relation
399    id = getItem(&attrs[i], "id");
400    osm_id[i] = id ? atoi(id) : 0;
401    //fprintf(stderr, "%d: object %d, id=%d\n", i, current[i], osm_id[i]);
402
403    // Perform sanity checking on element sequence. The output of planet.rb conforms to this and
404    // the current diff/patch algorithm relies on these properties.
405    if (current[i] < last_type) {
406        fprintf(stderr, "Error: <%s> seen after <%s>. The file must be strictly ordered node/way/relation.\n", getName(i), last_name);
407        fprintf(stderr, "The planet.osm generated by the planet export (planet.rb) is consistent with this.\n");
408        exit(8);
409    }
410    if ((current[i] == last_type) && (osm_id[i] < last_id)) {
411        fprintf(stderr, "Error: <%s id=%d> seen after <%s id=%d>. The IDs must be in increasing order.\n", getName(i), osm_id[i], getName(i), last_id);
412        fprintf(stderr, "The planet.osm generated by the planet export (planet.rb) is consistent with this.\n");
413        exit(9);
414    }
415#ifdef VERBOSE
416    // Some run-time stats, only count first stream
417    if (i == 0) {
418        count++;
419        if (current[i] != last_type) {
420            count = 0;
421            fprintf(stderr, "\n");
422        }
423        if ((count % 10000) == 0)
424            fprintf(stderr, "\rProcessing: %s(%dk)", getName(i), count/1000);
425    }
426#endif
427}
428
429
430static void displayNode(int i, const char *indent)
431{
432    struct keyval *p;
433
434    printf("%s<%s", indent,getName(i));
435
436    while ((p = popItem(&attrs[i])) != NULL) {
437        printf(" %s=\"%s\"", p->key, p->value);
438        freeItem(p);
439    }
440
441    if (listHasData(&tags[i]) || listHasData(&nds[i]) || listHasData(&member_nodes[i]) ||
442        listHasData(&member_ways[i]) || listHasData(&member_rels[i])) {
443        printf(">\n");
444
445        while ((p = popItem(&nds[i])) != NULL) {
446            printf("%s  <nd ref=\"%s\" />\n", indent, p->value);
447            freeItem(p);
448        }
449        while ((p = popItem(&member_nodes[i])) != NULL) {
450            printf("%s  <member type=\"node\" ref=\"%s\" role=\"%s\" />\n", indent, p->key, p->value);
451            freeItem(p);
452        }
453        while ((p = popItem(&member_ways[i])) != NULL) {
454            printf("%s  <member type=\"way\" ref=\"%s\" role=\"%s\" />\n", indent, p->key, p->value);
455            freeItem(p);
456        }
457        while ((p = popItem(&member_rels[i])) != NULL) {
458            printf("%s  <member type=\"relation\" ref=\"%s\" role=\"%s\" />\n", indent, p->key, p->value);
459            freeItem(p);
460        }
461        while ((p = popItem(&tags[i])) != NULL) {
462            printf("%s  <tag k=\"%s\" v=\"%s\" />\n", indent, p->key, p->value);
463            freeItem(p);
464        }
465        printf("%s</%s>\n", indent,getName(i));
466    } else {
467        printf("/>\n");
468    }
469}
470
471static void displayDiff(int i)
472{
473    const char *operation = i ? "add" : "delete";
474    printf("  <%s>\n", operation);
475    displayNode(i, "    ");
476    printf("  </%s>\n", operation);
477}
478
479static void displayPatch(int i)
480{
481    displayNode(i, "  ");
482}
483
484
485static void process(void)
486{
487    int c;
488
489    getobject(0);
490    getobject(1);
491
492    while (reader[0] || reader[1]) {
493        c = compareTypeID();
494        if (c == 0) {
495            // Matching object type and ID, generate diff if content differs
496            if (compareOther()) {
497                displayDiff(0);
498                displayDiff(1);
499            }
500            getobject(0);
501            getobject(1);
502        } else if (c < 0) {
503            // Object in stream 0 is missing in 1, generate diff to remove old content 0
504            displayDiff(0);
505            getobject(0);
506        } else {
507            // Object in stream 0 is ahead of 1, generate diff to add content in 1
508            displayDiff(1);
509            getobject(1);
510        }
511    }
512}
513
514
515
516static int diffFiles(void)
517{
518    int i;
519
520    for(i=0; i<2; i++) {
521        if (sanitize)
522            reader[i] = sanitizerOpen(files[i]);
523        else
524            reader[i] = inputUTF8(files[i]);
525        if (!reader[i]) {
526            fprintf(stderr, "Unable to open %s\n", files[i]);
527            return 1;
528        }
529    }
530
531    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
532    printf("<planetdiff version=\"0.5\" generator=\"OpenStreetMap planetdiff\" from=\"%s\" to=\"%s\">\n", files[0], files[1]);
533    process();
534    printf("</planetdiff>\n");
535
536    return 0;
537}
538
539
540static void applyPatch(int mode)
541{
542    int c;
543    //fprintf(stdout,"%s: %d id=%d\n", (mode > 0)? "add" : "delete", current[1], osm_id[1]);
544
545    // Stream input until we get to or pass the matching type+id
546    while ((c = compareTypeID()) < 0) {
547        if (current[0] != invalid)
548            displayPatch(0);
549        getobject(0);
550    }
551    if (mode == -1) {
552        if ((c == 0) && (compareOther() == 0)) {
553            // Perfect match, remove this from input by fetching next object
554            getobject(0);
555            return;
556        }
557        fprintf(stderr, "Error remove for <%s id=%d> does not match input file. %s\n",
558                getName(1), osm_id[1], (c==0)?"Object content differs.":"Object ID missing.");
559        exit(6);
560    } else if (mode == +1) {
561        if (c > 0) {
562            // Found next input following the insert, emit the new content but not input (yet)
563            displayPatch(1);
564            return;
565        }
566        fprintf(stderr, "Error adding <%s id=%d> input file already contains this object\n", getName(1), osm_id[1]);
567        exit(6);
568    }
569}
570
571static void processPatchNode(void)
572{
573    int i = 1; // Patch file is always stream 1
574    int empty;
575    int mode = 0; // -1 = remove, +1 = add;
576    xmlChar *name = xmlTextReaderName(reader[i]);
577    if (name == NULL)
578        name = xmlStrdup(BAD_CAST "--");
579       
580    switch(xmlTextReaderNodeType(reader[i])) {
581        case XML_READER_TYPE_ELEMENT:
582            empty = xmlTextReaderIsEmptyElement(reader[i]);
583            if (xmlStrEqual(name, BAD_CAST "add"))
584                mode = +1;
585            else if (xmlStrEqual(name, BAD_CAST "delete"))
586                mode = -1;
587            else if (xmlStrEqual(name, BAD_CAST "planetdiff")) {
588                mode = 0;
589                break;
590            } else {
591                fprintf(stderr, "Unexpected node: <%s>\n", (char *)name);
592                if (xmlStrEqual(name, BAD_CAST "osm"))
593                    fprintf(stderr, "It looks like %s is an osm file not a planetdiff\n", files[i]);
594                exit(1);
595            }
596            // Retrieve the object to add/remove from the patch file and apply
597            if (!empty) {
598                getobject(1);
599                applyPatch(mode);
600                break;
601            }
602            /* Drop through for self closing tags since these generate no end_element */
603        case XML_READER_TYPE_END_ELEMENT:
604            mode = 0;
605            break;
606        case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
607            /* Ignore */
608            break;
609        default:
610            fprintf(stderr, "Unknown node type %d\n", xmlTextReaderNodeType(reader[i]));
611            break;
612    }
613    xmlFree(name);
614}
615
616
617static int patchFiles(void)
618{
619    int ret = 0;
620    int i;
621    // files[0] = planet.osm (input)
622    // files[1] = delta.xml (patch)
623    for(i=0; i<2; i++) {
624        if (sanitize)
625            reader[i] = sanitizerOpen(files[i]);
626        else
627            reader[i] = inputUTF8(files[i]);
628        if (!reader[i]) {
629            fprintf(stderr, "Unable to open %s\n", files[i]);
630            return 1;
631        }
632    }
633
634    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
635    printf("<osm version=\"0.5\" generator=\"OpenStreetMap planet.rb\">\n");
636    // Ensure generator string length matches original planet.rb
637    // to allow 'cmp -l' to be run on output to verify correctness
638    //printf("<osm version=\"0.5\" generator=\"OSM planetpatch tool---\">\n");
639
640    ret = xmlTextReaderRead(reader[1]);
641
642    while (ret == 1) {
643        processPatchNode();
644        ret = xmlTextReaderRead(reader[1]);
645    }
646
647    if (ret != 0) {
648        fprintf(stderr, "%s : failed to parse patch\n", files[1]);
649        return ret;
650    }
651
652    // Displaying any trailer from input
653    while ((current[0] != eof) && reader[0]) {
654        displayPatch(0);
655        getobject(0);
656    }
657
658    printf("</osm>\n");
659
660    xmlFreeTextReader(reader[0]);
661    xmlFreeTextReader(reader[1]);
662    return 0;
663}
664
665
666static void usageDiff(const char *arg0)
667{
668    fprintf(stderr, "Usage:\n\t%s planet1.osm planet2.osm > planet.diff\n\nGenerates a difference file between the two input planet.osm files.\nThis binary is based on SVN revision: $Rev$.\n", arg0);
669    exit(1);
670}
671
672static int mainDiff(int argc, char *argv[], const char *name)
673{
674    int i;
675
676    if (argc != 3) {
677        usageDiff(name);
678        exit(1);
679    }
680
681    for (i=0; i<2; i++) {
682        files[i] = argv[i+1];
683        initList(&tags[i]);
684        initList(&nds[i]);
685        initList(&attrs[i]);
686        initList(&member_nodes[i]);
687        initList(&member_ways[i]);
688        initList(&member_rels[i]);
689    }
690
691    if (diffFiles() != 0)
692        exit(2);
693
694    return 0;
695}
696
697static void usagePatch(const char *arg0)
698{
699    fprintf(stderr, "Usage:\n\t%s planet.osm delta.xml > out.osm\n\n", arg0);
700    fprintf(stderr, "Applies the differences listed in delta.xml to the input planet.osm\n");
701    exit(1);
702}
703
704static int mainPatch(int argc, char *argv[], const char *name)
705{
706    int i;
707
708    if (argc != 3) {
709        usagePatch(name);
710        exit(1);
711    }
712
713    for (i=0; i<2; i++) {
714        files[i] = argv[i+1];
715        initList(&tags[i]);
716        initList(&nds[i]);
717        initList(&attrs[i]);
718        initList(&member_nodes[i]);
719        initList(&member_ways[i]);
720        initList(&member_rels[i]);
721    }
722
723    if (patchFiles() != 0)
724        exit(2);
725
726    return 0;
727}
728
729int main(int argc, char *argv[])
730{
731    int r;
732    const char *name;
733    LIBXML_TEST_VERSION
734
735    name = strrchr(argv[0], '/');
736
737    if (!name)
738        name = strrchr(argv[0], '\\');
739
740    if (name)
741        name++;
742    else
743        name = argv[0];
744
745    if (!strcmp(name, "planetdiff"))
746            r = mainDiff(argc, argv, name);
747    else if (!strcmp(name, "planetpatch"))
748            r = mainPatch(argc, argv, name);
749    else {
750        fprintf(stderr, "Usage error - should be called as planetdiff or planetpatch (not '%s')\n", name);
751        exit(1);
752    }
753
754    xmlCleanupParser();
755    xmlMemoryDump();
756#ifdef VERBOSE
757    fprintf(stderr, "\n");
758#endif
759    return r;
760}
Note: See TracBrowser for help on using the repository browser.