file associations should more or less work; only filetype is honored in the <associat...
[pandora-libraries.git] / lib / pnd_tinyxml.cpp
1
2 #include <stdio.h>
3 #include "tinyxml/tinyxml.h"
4 #include "../include/pnd_pxml.h"
5 #include "pnd_tinyxml.h"
6 #include "pnd_logger.h"
7
8 //Easily change the tag names if required (globally in this file):
9 #include "pnd_pxml_names.h"
10
11 extern "C" {
12
13 char *pnd_pxml_get_attribute(TiXmlElement *elem, const char *name)
14 {
15         const char *value = elem->Attribute(name);
16         if (value)
17                 return strdup(value);
18         else
19                 return NULL;
20 }
21
22 unsigned char pnd_pxml_parse_titles(const TiXmlHandle hRoot, pnd_pxml_t *app) {
23   TiXmlElement *pElem;
24   app->titles_alloc_c = 4;  //TODO: adjust this based on how many titles a PXML usually has. Power of 2.
25
26   app->titles = (pnd_localized_string_t *)malloc(sizeof(pnd_localized_string_t) * app->titles_alloc_c);
27   if (!app->titles) return (0); //errno = NOMEM
28
29   // Go through all title tags and load them.
30   // - Check if newer style titles sub-block exists; if so, use that.
31   //   - if not, fall back to old style
32   //     - failing that, crash earth into sun
33   if ( (pElem = hRoot.FirstChild(PND_PXML_NODENAME_TITLES).Element()) ) {
34     // newer <titles> block
35
36     pElem = pElem -> FirstChildElement ( PND_PXML_ENAME_TITLE );
37
38     while ( pElem ) {
39
40       // handle <title lang="en_US">Program Title</title>
41       //
42
43       // parse out the text and lang
44       char *text, *lang;
45
46       if ( ! ( text = strdup ( pElem -> GetText() ) ) ) {
47         continue;
48       }
49
50       if ( ! ( lang = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_TITLELANG ) ) ) {
51         continue;
52       }
53
54       // increment counter; if we're running out of buffers, grow to handle the new strings
55       app -> titles_c++;
56
57       if ( app -> titles_c > app -> titles_alloc_c ) {
58         // we don't have enough strings allocated
59         app -> titles_alloc_c <<= 1;
60         app -> titles = (pnd_localized_string_t *)realloc((void*)app->titles, app->titles_alloc_c);
61         if (!app->titles) return (0); //errno = ENOMEM
62       }
63
64       // populate the stringbuf
65       pnd_localized_string_t *title = &app->titles[app->titles_c - 1];
66       title->language = lang;
67       title->string = text;
68
69       // next
70       pElem = pElem -> NextSiblingElement ( PND_PXML_ENAME_TITLE );
71
72     } // foreach
73
74   } else {
75     // older style <title> entry series
76
77     for ( pElem = hRoot.FirstChild(PND_PXML_ENAME_TITLE).Element(); pElem;
78           pElem = pElem->NextSiblingElement(PND_PXML_ENAME_TITLE))
79     {
80
81       if ( ! pElem->GetText() ) {
82         continue;
83       }
84
85       char *text = strdup(pElem->GetText());
86       if (!text) continue;
87
88       char *lang = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_TITLELANG);
89       if (!lang) continue;
90
91       app->titles_c++;
92       if (app->titles_c > app->titles_alloc_c) //we don't have enough strings allocated
93       {
94         app->titles_alloc_c <<= 1;
95         app->titles = (pnd_localized_string_t *)realloc((void*)app->titles, app->titles_alloc_c);
96         if (!app->titles) return (0); //errno = ENOMEM
97       }
98
99       pnd_localized_string_t *title = &app->titles[app->titles_c - 1];
100       title->language = lang;
101       title->string = text;
102
103       //pnd_log ( PND_LOG_DEFAULT, (char*)"    Title/Lang: %s/%s\n", text, lang );
104
105     } // for
106
107   } // new or old style <title(s)>
108
109   return ( 1 );
110 }
111
112 unsigned char pnd_pxml_parse_descriptions(const TiXmlHandle hRoot, pnd_pxml_t *app) {
113   TiXmlElement *pElem;
114   app->descriptions_alloc_c = 4; //TODO: adjust this based on how many descriptions a PXML usually has. Power of 2.
115
116   app->descriptions = (pnd_localized_string_t *)malloc(sizeof(pnd_localized_string_t) * app->descriptions_alloc_c);
117   if (!app->descriptions)
118   {
119     app->descriptions_alloc_c = 0;
120     return (0); //errno = NOMEM
121   }
122
123   // similar logic to how <titles> or <title> is parsed
124   // - if <titles> block is found, use that; otherwise fall back to <title> deprecated form
125   if ( (pElem = hRoot.FirstChild ( PND_PXML_NODENAME_DESCRIPTIONS).Element() ) ) {
126     // newer style <descriptions> block
127
128     pElem = pElem -> FirstChildElement ( PND_PXML_ENAME_DESCRIPTION );
129
130     while ( pElem ) {
131
132       char *text, *lang;
133
134       if ( ! ( text = strdup ( pElem -> GetText() ) ) ) {
135         continue;
136       }
137
138       if ( ! ( lang = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_DESCRLANG ) ) ) {
139        if(text) free(text); text = NULL;
140         continue;
141       }
142
143       app->descriptions_c++;
144       if (app->descriptions_c > app->descriptions_alloc_c) //we don't have enough strings allocated
145       {
146         app->descriptions_alloc_c <<= 1;
147         app->descriptions = (pnd_localized_string_t*)realloc((void*)app->descriptions, app->descriptions_alloc_c * sizeof(pnd_localized_string_t) );
148         if (!app->descriptions) { if(text) free(text); if(lang) free(lang); return (0); } //errno = ENOMEM
149       }
150
151       pnd_localized_string_t *description = &app->descriptions[app->descriptions_c - 1];
152       description->language = lang;
153       description->string = text;
154
155       // next
156       pElem = pElem -> NextSiblingElement ( PND_PXML_ENAME_DESCRIPTION );
157
158     } // foreach
159
160   } else {
161     // fallback to older approach
162
163     for (pElem = hRoot.FirstChild(PND_PXML_ENAME_DESCRIPTION).Element(); pElem;
164          pElem = pElem->NextSiblingElement(PND_PXML_ENAME_DESCRIPTION))
165     {
166
167       if ( ! pElem->GetText() ) {
168         continue;
169       }
170
171       char *text = strdup(pElem->GetText());
172       if (!text) continue;
173
174       char *lang = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_DESCRLANG);
175       if (!lang) { if(text) free(text); text = NULL;  continue; }
176
177       app->descriptions_c++;
178       if (app->descriptions_c > app->descriptions_alloc_c) //we don't have enough strings allocated
179       {
180         app->descriptions_alloc_c <<= 1;
181         app->descriptions = (pnd_localized_string_t*)realloc((void*)app->descriptions, app->descriptions_alloc_c * sizeof(pnd_localized_string_t) );
182         if (!app->descriptions) { if(text) free(text); if(lang) free(lang); return (0); } //errno = ENOMEM
183       }
184
185       pnd_localized_string_t *description = &app->descriptions[app->descriptions_c - 1];
186       description->language = lang;
187       description->string = text;
188     } // for
189
190   } // new form or old form?
191
192   return ( 1 );
193 }
194
195 unsigned char pnd_pxml_parse ( const char *pFilename, char *buffer, unsigned int length, pnd_pxml_t **apps ) {
196
197   //Load the XML document
198   TiXmlDocument doc;
199   doc.Parse(buffer);
200
201   unsigned char appwrappermode = 0; // >=1 -> using <application>...</application> wrapper
202   unsigned char appcount = 0;
203   pnd_pxml_t *app = NULL;
204
205   TiXmlElement *pElem = NULL;
206   TiXmlElement *appElem = NULL;
207
208   //Find the root element
209   TiXmlHandle hDoc(&doc);
210   TiXmlHandle hRoot(0);
211
212   pElem = hDoc.FirstChild("PXML").Element();
213   if (!pElem) return (0);
214
215   // new Strategy; really, we want multiple app support within a PXML, without the lameness of
216   // multiple <PXML>..</PXML> within a single PXML.xml file. As such, we should have an app within
217   // an <application>..</application> wrapper level, with the ID on that. Further, the icon and
218   // .desktop filenames can have a # appended which is the application-count-# within the PXML,
219   // so that they can have their own .desktop and icon without collisions, but still use the
220   // same unique-id if they want to.
221   //   To avoid breaking existing PXML's (even though we're pre-launch), can detect if ID
222   // is present in PXML line or not; if not, assume application mode?
223   hRoot = TiXmlHandle(pElem);
224
225   // workaround for package ID's used by some package managers
226   // get the package ID and store it for each application
227   char* package_id = NULL;
228   char* package_version_major = NULL;
229   char* package_version_minor = NULL;
230   char* package_version_release = NULL;
231   char* package_version_build = NULL;
232   pElem = hRoot.FirstChild ( PND_PXML_ENAME_PACKAGE ).Element();
233   if ( pElem ) {
234         package_id = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_PACKAGE_ID );
235    TiXmlHandle pRoot = TiXmlHandle( pElem );
236    if ( (pElem = pRoot.FirstChild(PND_PXML_ENAME_VERSION).Element()) )
237    {
238       package_version_major   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERMAJOR);
239       package_version_minor   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERMINOR);
240       package_version_release = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERREL);
241       package_version_build   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERBUILD);
242    }
243   }
244
245   // move to applications element then
246   if ( hRoot.FirstChild(PND_PXML_APP).Element() != NULL ) {
247     appwrappermode = 1;
248     appElem = hRoot.FirstChild(PND_PXML_APP).Element();
249     hRoot = TiXmlHandle ( appElem );
250   }
251
252   // until we run out of applications in the PXML..
253   while ( 1 ) {
254
255     //pnd_log ( PND_LOG_DEFAULT, (char*)"  App #%u inside of PXML %s\n", appcount, pFilename );
256
257     // create the buffer to hold the pxml
258     apps [ appcount ] = (pnd_pxml_t*) malloc ( sizeof(pnd_pxml_t) );
259     memset ( apps [ appcount ], '\0', sizeof(pnd_pxml_t) );
260
261     // due to old code, just make life easier a minute..
262     app = apps [ appcount ];
263     if ( appwrappermode ) {
264       app -> subapp_number = appcount;
265     } else {
266       app -> subapp_number = 0;
267     }
268
269     // give application the package id, if there is one
270     if( package_id )
271       app -> package_id               = strdup(package_id);
272     if( package_version_major )
273       app -> package_version_major    = strdup(package_version_major);
274     if( package_version_minor )
275       app -> package_version_minor    = strdup(package_version_minor);
276     if( package_version_release )
277       app -> package_version_release  = strdup(package_version_release);
278     if( package_version_build )
279       app -> package_version_build    = strdup(package_version_build);
280
281     //Get unique ID first.
282     if ( appwrappermode ) {
283       app->unique_id = pnd_pxml_get_attribute(appElem, PND_PXML_ATTRNAME_UID);
284       //pnd_log ( PND_LOG_DEFAULT, (char*)"  Subapp #%u has unique_id %s\n", appcount, app -> unique_id );
285       app->appdata_dirname = pnd_pxml_get_attribute(appElem, PND_PXML_ATTRNAME_APPDATANAME);
286     } else {
287       app->unique_id = pnd_pxml_get_attribute(hRoot.Element(), PND_PXML_ATTRNAME_UID);
288       //pnd_log ( PND_LOG_DEFAULT, (char*)"  Only-app #%u has unique_id %s\n", appcount, app -> unique_id );
289     }
290
291     //Everything related to the title:
292     pnd_pxml_parse_titles(hRoot, app);
293
294     //Everything description-related:
295     pnd_pxml_parse_descriptions(hRoot, app);
296
297     //Everything launcher-related in one tag:
298     if ( (pElem = hRoot.FirstChild(PND_PXML_ENAME_EXEC).Element()) )
299      {
300        app->background = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_EXECBG); //if this returns NULL, the struct is filled with NULL. No need to check.
301        app->standalone = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_EXECSTAL);
302        app->exec       = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_EXECCMD);
303        app->startdir   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_EXECWD);
304        app->exec_no_x11     = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_EXECNOX11);
305        app->execargs   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_EXECARGS);
306      }
307
308     //The app icon:
309     if ( (pElem = hRoot.FirstChild(PND_PXML_ENAME_ICON).Element()) ) {
310       app->icon       = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_ICONSRC);
311     }
312
313     // <info>
314     if ( (pElem = hRoot.FirstChild(PND_PXML_ENAME_INFO).Element()) )
315      {
316        app-> info_name = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_INFONAME );
317        app-> info_filename = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_INFOSRC );
318        app-> info_type = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_INFOTYPE );
319      }
320
321     //The preview pics:
322     if ( (pElem = hRoot.FirstChild(PND_PXML_NODENAME_PREVPICS).Element()) )
323     {
324       //TODO: Change this if pnd_pxml_t gains the feature of more pics than 2.
325       if ( (pElem = pElem->FirstChildElement(PND_PXML_ENAME_PREVPIC)) )
326       {
327         app->previewpic1 = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_PREVPICSRC);
328
329         if ( (pElem = pElem->NextSiblingElement(PND_PXML_ENAME_PREVPIC)) )
330         {
331           app->previewpic2 = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_PREVPICSRC);
332         }
333       }
334     } //previewpic
335
336     //The author info:
337     if ( (pElem = hRoot.FirstChild(PND_PXML_ENAME_AUTHOR).Element()) )
338     {
339       app->author_name    = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_AUTHORNAME);
340       app->author_website = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_AUTHORWWW);
341       //TODO: Uncomment this if the author gets email support.
342       //app->author_email = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_AUTHOREMAIL));
343     }
344
345     //The version info:
346     if ( (pElem = hRoot.FirstChild(PND_PXML_ENAME_VERSION).Element()) )
347     {
348       app->version_major   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERMAJOR);
349       app->version_minor   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERMINOR);
350       app->version_release = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERREL);
351       app->version_build   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_VERBUILD);
352     }
353
354     //The OS version info:
355     if ( (pElem = hRoot.FirstChild(PND_PXML_ENAME_OSVERSION).Element()) )
356     {
357       app->osversion_major   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_OSVERMAJOR);
358       app->osversion_minor   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_OSVERMINOR);
359       app->osversion_release = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_OSVERREL);
360       app->osversion_build   = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_OSVERBUILD);
361     }
362
363     int i; //For now, we need to keep track of the index of categories.
364     //Categories:
365     if ( (pElem = hRoot.FirstChildElement(PND_PXML_NODENAME_CATS).Element()) ) //First, enter the "categories" node.
366     {
367       i = 0;
368
369       //Goes through all the top-level categories and their sub-categories. i helps limit these to 2.
370       for (pElem = pElem->FirstChildElement(PND_PXML_ENAME_CAT); pElem && i < 2;
371            pElem = pElem->NextSiblingElement(PND_PXML_ENAME_CAT), i++)
372       {
373         //TODO: Fix pnd_pxml_t so that there can be more than 2 category 'trees' and more than 2 subcategories. Then this can be removed.
374         switch (i)
375         {
376         case 0: //first category counts as the main cat for now
377           app->main_category = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_CATNAME);
378           break;
379
380         case 1: //...second as the alternative
381           app->altcategory = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_CATNAME);
382         }
383
384         TiXmlElement *pSubCatElem; //the sub-elements for a main category.
385         int j = 0; //the subcategory index within this category
386
387         //Goes through all the subcategories within this category. j helps limit these to 2.
388         for (pSubCatElem = pElem->FirstChildElement(PND_PXML_ENAME_SUBCAT); pSubCatElem && j < 2;
389              pSubCatElem = pSubCatElem->NextSiblingElement(PND_PXML_ENAME_SUBCAT), j++)
390         {
391           char *subcat = pnd_pxml_get_attribute(pSubCatElem, PND_PXML_ATTRNAME_SUBCATNAME);
392           if (!(subcat)) continue;
393
394           //TODO: This is ugly. Fix pnd_pxml_t so that there can be more than 2 category 'trees' and more than 2 subcategories. Then this can be removed.
395           switch (j | (i << 1))
396           {
397           case 0:
398             app->subcategory1 = subcat;
399             break;
400           case 1:
401             app->subcategory2 = subcat;
402             break;
403           case 2:
404             app->altsubcategory1 = subcat;
405             break;
406           case 3:
407             app->altsubcategory2 = subcat;
408           }
409         }
410       }
411     }
412
413     //All file associations:
414 #if 1
415     //Step into the associations node
416     if ( (pElem = hRoot.FirstChild(PND_PXML_NODENAME_ASSOCS).Element()) )
417     {
418       i = 0;
419       //Go through all associations. i serves as index; since the format only supports 3 associations we need to keep track of the number.
420       for (pElem = pElem->FirstChildElement(PND_PXML_ENAME_ASSOC); pElem && i < 3;
421            pElem = pElem->NextSiblingElement(PND_PXML_ENAME_ASSOC), i++)
422       {
423         char *name = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_ASSOCNAME);
424         char *filetype = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_ASSOCFTYPE);
425         //char *paramter = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_ASSOCARGS);
426
427         //pnd_log ( PND_LOG_DEFAULT, "  Found file association request in PXML - parse (%p %p)\n", name, filetype/*, paramter */ );
428
429         if (!(name && filetype /* && paramter*/ ))
430        {
431          if(name)     free(name);
432          if(filetype) free(filetype);
433          //if(paramter) free(paramter);
434          continue;
435        }
436
437         switch(i) //TODO: same problem here: only 3 associations supported
438         {
439         case 0:
440         {
441           app->associationitem1_name      = strdup ( name );
442           app->associationitem1_filetype  = strdup ( filetype );
443           //app->associationitem1_parameter = paramter;
444           pnd_log ( PND_LOG_DEFAULT, "  Found file association request in PXML (%d-0)\n", i );
445           break;
446         }
447         case 1:
448         {
449           app->associationitem2_name      = strdup ( name );
450           app->associationitem2_filetype  = strdup ( filetype );
451           //app->associationitem2_parameter = paramter;
452           pnd_log ( PND_LOG_DEFAULT, "  Found file association request in PXML (%d-1)\n", i );
453           break;
454         }
455         case 2:
456         {
457           app->associationitem3_name      = strdup ( name );
458           app->associationitem3_filetype  = strdup ( filetype );
459           //app->associationitem3_parameter = paramter;
460           pnd_log ( PND_LOG_DEFAULT, "  Found file association request in PXML (%d-2)\n", i );
461         }
462         }
463        if(name)     free(name);
464        if(filetype) free(filetype);
465        //if(paramter) free(paramter);
466       }
467     }
468 #endif
469
470     //Performance related things (aka: Clockspeed XD):
471     pElem = hRoot.FirstChild(PND_PXML_ENAME_CLOCK).Element();
472     if (pElem)
473     {
474       app->clockspeed = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_CLOCKFREQ);
475     }
476
477     // Package
478     pElem = hRoot.FirstChild ( PND_PXML_ENAME_PACKAGE ).Element();
479     if ( pElem ) {
480       app -> package_name = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_PACKAGE_NAME );
481       app -> package_release_date = pnd_pxml_get_attribute ( pElem, PND_PXML_ATTRNAME_PACKAGE_DATE );
482     }
483
484     // mkdir request
485     if ( (pElem = hRoot.FirstChild(PND_PXML_NODENAME_MKDIR).Element()) ) {
486
487       // seek <dir>
488       if ( (pElem = pElem->FirstChildElement(PND_PXML_ENAME_MKDIR)) ) {
489         char *t;
490
491         if ( ( t = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_MKDIRPATH) ) ) {
492           // first <dir>, so just replace it wholesale; we use strdup so we can free() easily later, consistently. Mmm, leak seems imminent.
493           app -> mkdir_sp = strdup ( t );
494          free(t); // free this attribute
495         }
496
497         while ( ( pElem = pElem -> NextSiblingElement ( PND_PXML_ENAME_MKDIR ) ) ) {
498
499           if ( ( t = pnd_pxml_get_attribute(pElem, PND_PXML_ATTRNAME_MKDIRPATH) ) ) {
500             char *foo = (char*) malloc ( strlen ( app -> mkdir_sp ) + strlen ( t ) + 1 /*:*/ + 1 /*\0*/ );
501
502             if ( foo ) {
503               sprintf ( foo, "%s:%s", app -> mkdir_sp, t );
504               free ( app -> mkdir_sp );
505               app -> mkdir_sp = foo;
506             } // assuming we got ram, lets cat it all together
507
508            free(t); // free this attribute
509           } // got another elem?
510
511         } // while
512
513       } // found a <dir>
514
515 #if 0
516       if ( app -> mkdir_sp ) {
517         printf ( "mkdir: %s\n", app -> mkdir_sp );
518       }
519 #endif
520
521     } // mkdir
522
523     // if in <application> mode, do we find another app?
524     if ( appwrappermode ) {
525       appElem = appElem -> NextSiblingElement ( PND_PXML_APP );
526       if ( ! appElem ) {
527         //pnd_log ( PND_LOG_DEFAULT, (char*)"  No more applications within PXML\n" );
528         break; // no more applications
529       }
530       // got another application..
531       //pnd_log ( PND_LOG_DEFAULT, "  Found another applications within PXML\n" );
532       appwrappermode++;
533       hRoot = TiXmlHandle ( appElem );
534
535       appcount++;
536
537       if ( appcount == PXML_MAXAPPS ) {
538         return ( 1 ); // thats all we can handle; we're not going to auto-extend this
539       }
540
541     } else {
542       break; // single-app old PXML
543     }
544
545   } // while finding apps
546
547   if( package_id )
548      free(package_id);
549   if( package_version_major )
550      free(package_version_major);
551   if( package_version_minor )
552      free(package_version_minor);
553   if( package_version_release )
554      free(package_version_release);
555   if( package_version_build )
556      free(package_version_build);
557
558   return (1);
559 }
560
561 } // extern C