]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/idlval.c
1e25334e0146abbc2971bcdc04b23b4521c7a5d7
[working/Evergreen.git] / Open-ILS / src / c-apps / idlval.c
1 /**
2         @file idlval.c
3         @brief Validator for IDL files.
4 */
5
6 /*
7 Copyright (C) 2009  Georgia Public Library Service
8 Scott McKellar <scott@esilibrary.com>
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 */
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <libxml/globals.h>
26 #include <libxml/xmlerror.h>
27 #include <libxml/parser.h>
28 #include <libxml/tree.h>
29 #include <libxml/debugXML.h>
30 #include <libxml/xmlmemory.h>
31
32 #include "utils.h"
33 #include "osrf_hash.h"
34
35 /* Represents the command line */
36 struct Opts {
37         int new_argc;
38         char ** new_argv;
39
40         char * idl_file_name;
41         int idl_file_name_found;
42         int warning;
43 };
44 typedef struct Opts Opts;
45
46 /* datatype attribute of <field> element */
47 typedef enum {
48         DT_NONE,
49         DT_BOOL,
50         DT_FLOAT,
51         DT_ID,
52         DT_INT,
53         DT_INTERVAL,
54         DT_LINK,
55         DT_MONEY,
56         DT_NUMBER,
57         DT_ORG_UNIT,
58         DT_TEXT,
59         DT_TIMESTAMP,
60         DT_INVALID
61 } Datatype;
62
63 /* Represents a <Field> aggregate */
64 struct Field_struct {
65         struct Field_struct* next;
66         xmlChar* name;
67         int is_virtual;     // boolean
68         xmlChar* label;
69         Datatype datatype;
70 };
71 typedef struct Field_struct Field;
72
73 /* reltype attribute of <link> element */
74 typedef enum {
75         RT_NONE,
76         RT_HAS_A,
77         RT_MIGHT_HAVE,
78         RT_HAS_MANY,
79         RT_INVALID
80 } Reltype;
81
82 /* Represents a <link> element */
83 struct Link_struct {
84         struct Link_struct* next;
85         xmlChar* field;
86         Reltype reltype;
87         xmlChar* key;
88         xmlChar* classref;
89 };
90 typedef struct Link_struct Link;
91
92 /* Represents a <class> aggregate */
93 typedef struct {
94         xmlNodePtr node;
95         int loaded;        // boolean
96         int is_virtual;    // boolean
97         xmlChar* primary;  // name of primary key column
98         Field* fields;     // linked list
99         Link* links;       // linked list
100 } Class;
101
102 static int get_Opts( int argc, char * argv[], Opts * pOpts );;
103 static int val_idl( void );
104 static int cross_validate_classes( Class* class, const char* id );
105 static int cross_validate_linkage( Class* class, const char*id, Link* link );
106 static int val_class( Class* class, const char* id );
107 static int val_class_attributes( Class* class, const char* id );
108 static int check_labels( const Class* class, const char* id );
109 static int val_fields_attributes( Class* class, const char* id, xmlNodePtr fields );
110 static int val_links_to_fields( const Class* class, const char* id );
111 static int compareFieldAndLink( const Class* class, const char* id,
112                 const Field* field, const Link* link );
113 static int val_fields_to_links( const Class* class, const char* id );
114 static const Field* searchFieldByName( const Class* class, const xmlChar* field_name );
115 static int val_fields( Class* class, const char* id, xmlNodePtr fields );
116 static int val_one_field( Class* class, const char* id, xmlNodePtr field );
117 static Datatype translate_datatype( const xmlChar* value );
118 static int val_links( Class* class, const char* id, xmlNodePtr links );
119 static int val_one_link( Class* class, const char* id, xmlNodePtr link );
120 static Reltype translate_reltype( const xmlChar* value );
121 static int scan_idl( xmlDocPtr doc );
122 static int register_class( xmlNodePtr child );
123 static int addField( Class* class, const char* id, Field* new_field );
124 static int addLink( Class* class, const char* id, Link* new_link );
125 static Class* newClass( xmlNodePtr node );
126 static void freeClass( char* key, void* p );
127 static Field* newField( xmlChar* name );
128 static void freeField( Field* field );
129 static Link* newLink( xmlChar* field );
130 static void freeLink( Link* link );
131
132 /* Stores an in-memory representation of the IDL */
133 static osrfHash* classes = NULL;
134
135 static int warn = 0;       // boolean; true if -w present on command line
136
137 int main( int argc, char* argv[] ) {
138
139         // Examine command line
140         Opts opts;
141         if( get_Opts( argc, argv, &opts ) )
142                 return 1;
143
144         const char* IDL_filename = NULL;
145         if( opts.idl_file_name_found )
146                 IDL_filename = opts.idl_file_name;
147         else {
148                 IDL_filename = getenv( "OILS_IDL_FILENAME" );
149                 if( ! IDL_filename )
150                         IDL_filename = "/openils/conf/fm_IDL.xml";
151         }
152
153         if( opts.warning )
154                 warn = 1;
155
156         int rc = 0;
157
158         xmlLineNumbersDefault(1);
159         xmlDocPtr doc = xmlReadFile( IDL_filename, NULL, XML_PARSE_XINCLUDE );
160         if ( ! doc ) {
161                 fprintf( stderr, "Could not load or parse the IDL XML file %s\n", IDL_filename );
162                 rc = 1;
163         } else {
164                 printf( "Validating: %s\n", IDL_filename );
165                 classes = osrfNewHash();
166                 osrfHashSetCallback( classes, freeClass );
167
168                 // Load the IDL
169                 if( scan_idl( doc ) )
170                         rc = 1;
171
172                 if( opts.new_argc < 2 ) {
173
174                         // No classes specified: validate all classes
175                         if( val_idl() )
176                                 rc = 1;
177                 } else {
178
179                         // Validate one or more specified classes
180                         int i = 1;
181                         while( i < opts.new_argc ) {
182                                 const char* classname = opts.new_argv[ i ];
183                                 Class* class = osrfHashGet( classes, classname );
184                                 if( ! class ) {
185                                         printf( "Class \"%s\" does not exist\n", classname );
186                                         rc = 1;
187                                 } else {
188                                         // Validate the class in isolation
189                                         if( val_class( class, classname ) )
190                                                 rc = 1;
191                                         // Cross-validate with linked classes
192                                         if( cross_validate_classes( class, classname ) )
193                                                 rc = 1;
194                                 }
195                                 ++i;
196                         }
197                 }
198                 osrfHashFree( classes );
199                 xmlFreeDoc( doc );
200         }
201
202         return rc;
203 }
204
205 /**
206         @brief Examine the command line
207         @param argc Number of entries in argv[]
208         @param argv Array of pointers to command line strings
209         @param pOpts Pointer to structure to be populated
210         @return 0 upon success, or 1 if the command line is invalid
211 */
212 static int get_Opts( int argc, char * argv[], Opts * pOpts ) {
213         int rc = 0; /* return code */
214         int opt;
215
216         /* Define valid option characters */
217
218         const char optstring[] = ":f:w";
219
220         /* Initialize members of struct */
221
222         pOpts->new_argc = 0;
223         pOpts->new_argv = NULL;
224
225         pOpts->idl_file_name_found = 0;
226         pOpts->idl_file_name = NULL;
227         pOpts->warning = 0;
228
229         /* Suppress error messages from getopt() */
230
231         opterr = 0;
232
233         /* Examine command line options */
234
235         while( ( opt = getopt( argc, argv, optstring ) ) != -1 ) {
236                 switch( opt ) {
237                         case 'f' :   /* Get idl_file_name */
238                                 if( pOpts->idl_file_name_found ) {
239                                         fprintf( stderr, "Only one occurrence of -f option allowed\n" );
240                                         rc = 1;
241                                         break;
242                                 }
243                                 pOpts->idl_file_name_found = 1;
244
245                                 pOpts->idl_file_name = optarg;
246                                 break;
247                                 case 'w' :   /* Get warning */
248                                         pOpts->warning = 1;
249                                         break;
250                                         case ':' : /* Missing argument */
251                                                 fprintf( stderr, "Required argument missing on -%c option\n",
252                                                                  (char) optopt );
253                                                 rc = 1;
254                                                 break;
255                                                 case '?' : /* Invalid option */
256                                                         fprintf( stderr, "Invalid option '-%c' on command line\n",
257                                                                          (char) optopt );
258                                                         rc = 1;
259                                                         break;
260                                                         default :  /* Programmer error */
261                                                                 fprintf( stderr, "Internal error: unexpected value '-%c'"
262                                                                                 "for optopt", (char) optopt );
263                                                                 rc = 1;
264                                                                 break;
265                 } /* end switch */
266         } /* end while */
267
268         if( optind > argc ) {
269                 /* This should never happen! */
270
271                 fprintf( stderr, "Program error: found more arguments than expected\n" );
272                 rc = 1;
273         } else {
274                 /* Calculate new_argcv and new_argc to reflect */
275                 /* the number of arguments consumed */
276
277                 pOpts->new_argc = argc - optind + 1;
278                 pOpts->new_argv = argv + optind - 1;
279         }
280
281         return rc;
282 }
283
284 /**
285         @brief Validate all classes.
286         @return 1 if errors found, or 0 if not.
287
288         Traverse the class list and validate each class in turn.
289 */
290 static int val_idl( void ) {
291         int rc = 0;
292         osrfHashIterator* itr = osrfNewHashIterator( classes );
293         Class* class = NULL;
294
295         // For each class
296         while( (class = osrfHashIteratorNext( itr )) ) {
297                 const char* id = osrfHashIteratorKey( itr );
298                 if( val_class( class, id ) )               // validate class separately
299                         rc = 1;
300                 if( cross_validate_classes( class, id ) )  // cross-validate with linked classes
301                         rc = 1;
302         }
303
304         osrfHashIteratorFree( itr );
305         return rc;
306 }
307
308 /**
309         @brief Make sure that every linkage appropriately matches the linked class.
310         @param class Pointer to the current Class.
311         @param id Class id.
312         @return 1 if errors found, or 0 if not.
313 */
314 static int cross_validate_classes( Class* class, const char* id ) {
315         int rc = 0;
316         Link* link = class->links;
317         while( link ) {
318                 if( cross_validate_linkage( class, id, link ) )
319                         rc = 1;
320                 link = link->next;
321         }
322
323         return rc;
324 }
325
326 /**
327         @brief Make sure that a linkage appropriately matches the linked class.
328         @param class Pointer to the current class.
329         @param id Class id.
330         @param link Pointer to the link being validated.
331         @return 1 if errors found, or 0 if not.
332
333         Rules:
334         - The linked class must exist.
335         - The field to which the linkage points must exist.
336         - If the linked class has a corresponding link back to the current class, then exactly
337         one end of the linkage must have a reltype of "has_many".
338
339         It is not an error if the linkage is not reciprocated.
340 */
341 static int cross_validate_linkage( Class* class, const char*id, Link* link ) {
342         int rc = 0;
343         Class* other_class = osrfHashGet( classes, (char*) link->classref );
344         if( ! other_class ) {
345                 printf( "In class \"%s\": class \"%s\", referenced by \"%s\" field, does not exist\n",
346                                 id, (char*) link->classref, (char*) link->field );
347                 rc = 1;
348         } else {
349                 // Make sure the other class is loaded before we look at it further
350                 if( val_class( other_class, (char*) link->classref ) )
351                         rc = 1;
352
353                 // Now see if the other class links back to this one
354                 Link* other_link = other_class->links;
355                 while( other_link ) {
356                         if( !strcmp( id, (char*) other_link->classref )                    // class to class
357                                 && !strcmp( (char*) link->key,   (char*) other_link->field )   // key to field
358                                 && !strcmp( (char*) link->field, (char*) other_link->key ) ) { // field to key
359                                 break;
360                         }
361                         other_link = other_link->next;
362                 }
363
364                 if( ! other_link ) {
365                         // Link is not reciprocated?  That's okay, as long as
366                         // the referenced field exists in the referenced class.
367                         if( !searchFieldByName( other_class, link->key ) ) {
368                                 printf( "In class \"%s\": field \"%s\" links to field \"%s\" of class \"%s\", "
369                                         "but that field doesn't exist\n", id, (char*) link->field,
370                                         (char*) link->key, (char*) link->classref );
371                                 rc = 1;
372                         }
373                 } else {
374                         // The link is reciprocated.  Make sure that exactly one of the links
375                         // has a reltype of "has_many"
376                         int many_count = 0;
377                         if( RT_HAS_MANY == link->reltype )
378                                 ++many_count;
379                         if( RT_HAS_MANY == other_link->reltype )
380                                 ++many_count;
381
382                         if( 0 == many_count ) {
383                                 printf( "Classes \"%s\" and \"%s\" link to each other, but neither has a reltype "
384                                                 "of \"has_many\"\n", id, (char*) link->classref );
385                                 rc = 1;
386                         } else if( 2 == many_count ) {
387                                 printf( "Classes \"%s\" and \"%s\" link to each other, but both have a reltype "
388                                                 "of \"has_many\"\n", id, (char*) link->classref );
389                                 rc = 1;
390                         }
391                 }
392         }
393
394         return rc;
395 }
396
397 /**
398         @brief Validate a single class.
399         @param id Class id.
400         @param class Pointer to the XML node for the class element.
401         @return 1 if errors found, or 0 if not.
402
403         We have already validated the id.
404
405         Rules:
406         - Allowed elements are "fields", "links", "permacrud", and "source_definition".
407         - None of these elements may occur more than once in the same class.
408         - The "fields" element is required.
409         - No text allowed, other than white space.
410         - Comments are allowed (and ignored).
411 */
412 static int val_class( Class* class, const char* id ) {
413         if( !class )
414                 return 1;
415         else if( class->loaded )
416                 return 0;         // We've already validated this one locally
417
418         int rc = 0;
419
420         if( val_class_attributes( class, id ) )
421                 rc = 1;
422
423         xmlNodePtr fields = NULL;
424         xmlNodePtr links = NULL;
425         xmlNodePtr permacrud = NULL;
426         xmlNodePtr src_def = NULL;
427
428         // Examine every child element of the <class> element.
429         xmlNodePtr child = class->node->children;
430         while( child ) {
431                 const char* child_name = (char*) child->name;
432                 if( xmlNodeIsText( child ) ) {
433                         if( ! xmlIsBlankNode( child ) ) {
434                                 // Found unexpected text.  After removing leading and
435                                 // trailing white space, complain about it.
436                                 xmlChar* content = xmlNodeGetContent( child );
437
438                                 xmlChar* begin = content;
439                                 while( *begin && isspace( *begin ) )
440                                         ++begin;
441                                 if( *begin ) {
442                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
443                                         while( (isspace( *end ) ) )
444                                                 --end;
445                                         end[ 1 ] = '\0';
446                                 }
447
448                                 printf( "Unexpected text in class \"%s\": \"%s\"\n", id,
449                                         (char*) begin );
450                                 xmlFree( content );
451                         }
452                 } else if( !strcmp( child_name, "fields" ) ) {
453                         if( fields ) {
454                                 printf( "Multiple <fields> elements in class \"%s\"\n", id );
455                                 rc = 1;
456                         } else {
457                                 fields = child;
458                                 // Identify the primary key, if any
459                                 class->primary = xmlGetProp( fields, (xmlChar*) "primary" );
460                                 if( val_fields( class, id, fields ) )
461                                         rc = 1;
462                         }
463                 } else if( !strcmp( child_name, "links" ) ) {
464                         if( links ) {
465                                 printf( "Multiple <links> elements in class \"%s\"\n", id );
466                                 rc = 1;
467                         } else {
468                                 links = child;
469                                 if( val_links( class, id, links ) )
470                                         rc = 1;
471                         }
472                 } else if( !strcmp( child_name, "permacrud" ) ) {
473                         if( permacrud ) {
474                                 printf( "Multiple <permacrud> elements in class \"%s\"\n", id );
475                                 rc = 1;
476                         } else {
477                                 permacrud = child;
478                         }
479                 } else if( !strcmp( child_name, "source_definition" ) ) {
480                         if( src_def ) {
481                                 printf( "Multiple <source_definition> elements in class \"%s\"\n", id );
482                                 rc = 1;
483                         } else {
484                                 // To do: verify that there is nothing in <source_definition> except text and
485                                 // comments, and that the text is non-empty.
486                                 src_def = child;
487                         }
488                 } else if( !strcmp( child_name, "comment" ) )
489                         ;  // ignore comment
490                 else {
491                         printf( "Line %ld: Unexpected <%s> element in class \"%s\"\n",
492                                 xmlGetLineNo( child ), child_name, id );
493                         rc = 1;
494                 }
495                 child = child->next;
496         }
497
498         if( fields ) {
499                 if( check_labels( class, id ) )
500                         rc = 1;
501                 if( val_fields_attributes( class, id, fields ) )
502                         rc = 1;
503         } else {
504                 printf( "No <fields> element in class \"%s\"\n", id );
505                 rc = 1;
506         }
507
508         if( val_links_to_fields( class, id ) )
509                 rc = 1;
510
511         if( val_fields_to_links( class, id ) )
512                 rc = 1;
513
514         class->loaded = 1;
515         return rc;
516 }
517
518 /**
519         @brief Validate the class attributes.
520         @param class Pointer to the current Class.
521         @param id Class id.
522         @return if errors found, or 0 if not.
523
524         Rules:
525         - Only the following attributes are valid: controller, core, field_safe, field_mapper,
526         id, label, readonly, restrict_primary, tablename, and virtual.
527         - The controller and fieldmapper attributes are required (as is the id attribute, but
528         that's checked elsewhere).
529         - Every attribute value must be non-empty.
530         - The values of attributes core, field_safe, reaadonly, and virtual must be either
531         "true" or "false".
532         - A virtual class must not have a tablename attribute.
533 */
534 static int val_class_attributes( Class* class, const char* id ) {
535         int rc = 0;
536
537         int controller_found = 0;     // boolean
538         int fieldmapper_found = 0;    // boolean
539         int tablename_found = 0;      // boolean
540
541         xmlAttrPtr attr = class->node->properties;
542         while( attr ) {
543                 const char* attr_name = (char*) attr->name;
544                 if( !strcmp( (char*) attr_name, "id" ) ) {
545                         ;  // ignore; we already grabbed this one
546                 } else if( !strcmp( (char*) attr_name, "controller" ) ) {
547                         controller_found = 1;
548                         xmlChar* value = xmlGetProp( class->node, (xmlChar*) "controller" );
549                         if( '\0' == *value ) {
550                                 printf( "Line %ld: Value of controller attribute is empty in class \"%s\"\n",
551                                         xmlGetLineNo( class->node ), id );
552                                 rc = 1;
553                         }
554                         xmlFree( value );
555                 } else if( !strcmp( (char*) attr_name, "fieldmapper" ) ) {
556                         fieldmapper_found = 1;
557                         xmlChar* value = xmlGetProp( class->node, (xmlChar*) "fieldmapper" );
558                         if( '\0' == *value ) {
559                                 printf( "Line %ld: Value of fieldmapper attribute is empty in class \"%s\"\n",
560                                                 xmlGetLineNo( class->node ), id );
561                                 rc = 1;
562                         }
563                         xmlFree( value );
564                 } else if( !strcmp( (char*) attr_name, "label" ) ) {
565                         xmlChar* value = xmlGetProp( class->node, (xmlChar*) "label" );
566                         if( '\0' == *value ) {
567                                 printf( "Line %ld: Value of label attribute is empty in class \"%s\"\n",
568                                                 xmlGetLineNo( class->node ), id );
569                                 rc = 1;
570                         }
571                         xmlFree( value );
572                 } else if( !strcmp( (char*) attr_name, "tablename" ) ) {
573                         tablename_found = 1;
574                         xmlChar* value = xmlGetProp( class->node, (xmlChar*) "tablename" );
575                         if( '\0' == *value ) {
576                                 printf( "Line %ld: Value of tablename attribute is empty in class \"%s\"\n",
577                                                 xmlGetLineNo( class->node ), id );
578                                 rc = 1;
579                         }
580                         xmlFree( value );
581                 } else if( !strcmp( (char*) attr_name, "virtual" ) ) {
582                         xmlChar* virtual_str = xmlGetProp( class->node, (xmlChar*) "virtual" );
583                         if( virtual_str ) {
584                                 if( !strcmp( (char*) virtual_str, "true" ) ) {
585                                         class->is_virtual = 1;
586                                 } else if( strcmp( (char*) virtual_str, "false" ) ) {
587                                         printf(
588                                                 "Line %ld: Invalid value \"%s\" for virtual attribute of class\"%s\"\n",
589                                                 xmlGetLineNo( class->node ), (char*) virtual_str, id );
590                                         rc = 1;
591                                 }
592                                 xmlFree( virtual_str );
593                         }
594                 } else if( !strcmp( (char*) attr_name, "readonly" ) ) {
595                         xmlChar* readonly = xmlGetProp( class->node, (xmlChar*) "readonly" );
596                         if( readonly ) {
597                                 if(    strcmp( (char*) readonly, "true" )
598                                         && strcmp( (char*) readonly, "false" ) ) {
599                                         printf(
600                                                 "Line %ld: Invalid value \"%s\" for readonly attribute of class\"%s\"\n",
601                                                 xmlGetLineNo( class->node ), (char*) readonly, id );
602                                         rc = 1;
603                                 }
604                                 xmlFree( readonly );
605                         }
606                 } else if( !strcmp( (char*) attr_name, "restrict_primary" ) ) {
607                         xmlChar* value = xmlGetProp( class->node, (xmlChar*) "restrict_primary" );
608                         if( '\0' == *value ) {
609                                 printf( "Line %ld: Value of restrict_primary attribute is empty in class \"%s\"\n",
610                                                 xmlGetLineNo( class->node ), id );
611                                 rc = 1;
612                         }
613                         xmlFree( value );
614                 } else if( !strcmp( (char*) attr_name, "core" ) ) {
615                         xmlChar* core = xmlGetProp( class->node, (xmlChar*) "core" );
616                         if( core ) {
617                                 if(    strcmp( (char*) core, "true" )
618                                         && strcmp( (char*) core, "false" ) ) {
619                                         printf(
620                                            "Line %ld: Invalid value \"%s\" for core attribute of class\"%s\"\n",
621                                                 xmlGetLineNo( class->node ), (char*) core, id );
622                                         rc = 1;
623                                 }
624                                 xmlFree( core );
625                         }
626                 } else if( !strcmp( (char*) attr_name, "field_safe" ) ) {
627                         xmlChar* field_safe = xmlGetProp( class->node, (xmlChar*) "field_safe" );
628                         if( field_safe ) {
629                                 if(    strcmp( (char*) field_safe, "true" )
630                                         && strcmp( (char*) field_safe, "false" ) ) {
631                                         printf(
632                                                 "Line %ld: Invalid value \"%s\" for field_safe attribute of class\"%s\"\n",
633                                                 xmlGetLineNo( class->node ), (char*) field_safe, id );
634                                         rc = 1;
635                                 }
636                                 xmlFree( field_safe );
637                         }
638                 } else {
639                         printf( "Line %ld: Unrecognized class attribute \"%s\" in class \"%s\"\n",
640                                 xmlGetLineNo( class->node ), attr_name, id );
641                         rc = 1;
642                 }
643                 attr = attr->next;
644         } // end while
645
646         if( ! controller_found ) {
647                 printf( "Line %ld: No controller attribute for class \"%s\"\n",
648                         xmlGetLineNo( class->node ), id );
649                 rc = 1;
650         }
651
652         if( ! fieldmapper_found ) {
653                 printf( "Line %ld: No fieldmapper attribute for class \"\%s\"\n",
654                         xmlGetLineNo( class->node ), id );
655                 rc = 1;
656         }
657
658         if( class->is_virtual && tablename_found ) {
659                 printf( "Line %ld: Virtual class \"%s\" shouldn't have a tablename",
660                         xmlGetLineNo( class->node ), id );
661                 rc = 1;
662         }
663
664         return rc;
665 }
666
667 /**
668         @brief Determine whether fields are either all labeled or all unlabeled.
669         @param class Pointer to the current Class.
670         @param id Class id.
671         @return 1 if errors found, or 0 if not.
672
673         Rule:
674         - The fields for a given class must either all be labeled or all unlabeled.
675
676         For purposes of this validation, a field is considered labeled even if the label is an
677         empty string.  Empty labels are reported elsewhere.
678 */
679 static int check_labels( const Class* class, const char* id ) {
680         int rc = 0;
681
682         int label_found = 0;    // boolean
683         int unlabel_found = 0;  // boolean
684
685         Field* field = class->fields;
686         while( field ) {
687                 if( field->label )
688                         label_found = 1;
689                 else
690                         unlabel_found = 1;
691                 field = field->next;
692         }
693
694         if( label_found && unlabel_found ) {
695                 printf( "Class \"%s\" has a mixture of labeled and unlabeled fields\n", id );
696                 rc = 1;
697         }
698
699         return rc;
700 }
701
702 /**
703         @brief Validate the fields attributes.
704         @param class Pointer to the current Class.
705         @param id Class id.
706         @param fields Pointer to the XML node for the fields element.
707         @return if errors found, or 0 if not.
708
709         Rules:
710         - The only valid attributes for the fields element are "primary" and "sequence".
711         - Neither attribute may have an empty string for a value.
712         - If there is a sequence attribute, there must also be a primary attribute.
713         - If there is a primary attribute, the field identified must exist.
714         - If the primary key field has the datatype "id", there must be a sequence attribute.
715         - If the datatype of the primary key is not "id" or "int", there must @em not be a
716         sequence attribute.
717 */
718 static int val_fields_attributes( Class* class, const char* id, xmlNodePtr fields ) {
719         int rc = 0;
720
721         xmlChar* sequence = NULL;
722         xmlChar* primary  = NULL;
723
724         // Traverse the attributes
725         xmlAttrPtr attr = fields->properties;
726         while( attr ) {
727                 const char* attr_name = (char*) attr->name;
728                 if( !strcmp( attr_name, "primary" ) ) {
729                         primary = xmlGetProp( fields, (xmlChar*) "primary" );
730                         if( '\0' == primary[0] ) {
731                                 printf(
732                                         "Line %ld: value of primary attribute is an empty string for class \"%s\"\n",
733                                         xmlGetLineNo( fields ), id );
734                                 rc = 1;
735                         }
736                 } else if( !strcmp( attr_name, "sequence" ) ) {
737                         sequence = xmlGetProp( fields, (xmlChar*) "sequence" );
738                         if( '\0' == sequence[0] ) {
739                                 printf(
740                                         "Line %ld: value of sequence attribute is an empty string for class \"%s\"\n",
741                                         xmlGetLineNo( fields ), id );
742                                 rc = 1;
743                         }
744                 } else {
745                         printf( "Line %ld: Unexpected fields attribute \"%s\" in class \"%s\"\n",
746                                 xmlGetLineNo( fields ), attr_name, id );
747                         rc = 1;
748                 }
749
750                 attr = attr->next;
751         }
752
753         if( sequence && ! primary ) {
754                 printf( "Line %ld: class \"%s\" has a sequence identified but no primary key\n",
755                         xmlGetLineNo( fields ), id );
756                 rc = 1;
757         }
758
759         if( primary ) {
760                 // look for the primary key
761                 Field* field = class->fields;
762                 while( field ) {
763                         if( !strcmp( (char*) field->name, (char*) primary ) )
764                                 break;
765                         field = field->next;
766                 }
767                 if( !field ) {
768                         printf( "Primary key field \"%s\" does not exist for class \"%s\"\n",
769                                 (char*) primary, id );
770                         rc = 1;
771                 } else if( DT_ID == field->datatype && ! sequence && ! class->is_virtual ) {
772                         printf(
773                                 "Line %ld: Primary key is an id; class \"%s\" should have a sequence attribute\n",
774                                 xmlGetLineNo( fields ), id );
775                         rc = 1;
776                 } else if(    DT_ID != field->datatype
777                                    && DT_INT != field->datatype
778                                    && DT_ORG_UNIT != field->datatype
779                                    && sequence ) {
780                         printf(
781                                 "Line %ld: Datatype of key for class \"%s\" does not allow a sequence attribute\n",
782                                 xmlGetLineNo( fields ), id );
783                         rc = 1;
784                 }
785         }
786
787         xmlFree( primary );
788         xmlFree( sequence );
789         return rc;
790 }
791
792 /**
793         @brief Verify that every Link has a matching Field for a given Class.
794         @param class Pointer to the current class.
795         @param id Class id.
796         @return 1 if errors found, or 0 if not.
797
798         Rules:
799         - For every link element, there must be a matching field element in the same class.
800         - If the link's reltype is "has_many", the field must be a virtual field of type link
801         or org_unit.
802         - If the link's reltype is "has_a" or "might_have", the field must be a non-virtual link
803         of type link or org_unit.
804 */
805 static int val_links_to_fields( const Class* class, const char* id ) {
806         if( !class )
807                 return 1;
808
809         int rc = 0;
810
811         const Link* link = class->links;
812         while( link ) {
813                 if( link->field && *link->field ) {
814                         const Field* field = searchFieldByName( class, link->field );
815                         if( field ) {
816                                 if( compareFieldAndLink( class, id, field, link ) )
817                                         rc = 1;
818                         } else {
819                                 printf( "\"%s\" class has no <field> corresponding to <link> for \"%s\"\n",
820                                         id, (char*) link->field );
821                                 rc = 1;
822                         }
823                 }
824                 link = link->next;
825         }
826
827         return rc;
828 }
829
830 /**
831         @brief Compare matching field and link elements to see if they are compatible
832         @param class Pointer to the current Class.
833         @param id Class id.
834         @param field Pointer to the Field to be compared to the Link.
835         @param link Pointer to the Link to be compared to the Field.
836         @return 0 if they are compatible, or 1 if not.
837
838         Rules:
839         - If the reltype is "has_many", the field must be virtual.
840         - If a field corresponds to a link, and is not the primary key, then it must have a
841         datatype "link" or "org_unit".
842         - If the datatype is "org_unit", the linkage must be to the class "aou".
843
844         Warnings:
845         - If the reltype is "has_a" or "might_have", the the field should probably @em not
846         be virtual, but there are legitimate exceptions.
847         - If the linkage is to the class "aou", then the datatype should probably be "org_unit".
848 */
849 static int compareFieldAndLink( const Class* class, const char* id,
850                 const Field* field, const Link* link ) {
851         int rc = 0;
852
853         Datatype datatype = field->datatype;
854         const char* classref = (const char*) link->classref;
855
856         // Validate the virtuality of the field
857         if( RT_HAS_A == link->reltype || RT_MIGHT_HAVE == link->reltype ) {
858                 if( warn && field->is_virtual ) {
859                         // This is the child class; field should usually be non-virtual,
860                         // but there are legitimate exceptions.
861                         printf( "WARNING: In class \"%s\": field \"%s\" is tied to a \"has_a\" or "
862                                 "\"might_have\" link; perhaps should not be virtual\n",
863                                 id, (char*) field->name );
864                 }
865         } else if ( RT_HAS_MANY == link->reltype ) {
866                 if( ! field->is_virtual ) {
867                         printf( "In class \"%s\": field \"%s\" is tied to a \"has_many\" link "
868                                         "and therefore should be virtual\n", id, (char*) field->name );
869                         rc = 1;
870                 }
871         }
872
873         // Validate the datatype of the field
874         if( class->primary && !strcmp( (char*) class->primary, (char*) field->name ) ) {
875                 ; // For the primary key field, the datatype can be anything
876         } else if( DT_NONE == datatype || DT_INVALID == datatype ) {
877                 printf( "In class \"%s\": \"%s\" field should have a datatype for linkage\n",
878                                 id, (char*) field->name );
879                 rc = 1;
880         } else if( DT_ORG_UNIT == datatype ) {
881                 if( strcmp( classref, "aou" ) ) {
882                         printf( "In class \"%s\": \"%s\" field should have a datatype "
883                                         "\"link\", not \"org_unit\"\n", id, field->name );
884                         rc = 1;
885                 }
886         } else if( DT_LINK == datatype ) {
887                 if( warn && !strcmp( classref, "aou" ) ) {
888                         printf( "WARNING: In class \"%s\", field \"%s\": Consider changing datatype "
889                                         "to \"org_unit\"\n", id, (char*) field->name );
890                 }
891         } else {
892                 // Datatype should be "link", or maybe "org_unit"
893                 if( !strcmp( classref, "aou" ) ) {
894                         printf( "In class \"%s\": \"%s\" field should have a datatype "
895                                         "\"org_unit\" or \"link\"\n",
896                                         id, (char*) field->name );
897                         rc = 1;
898                 } else {
899                         printf( "In class \"%s\": \"%s\" field should have a datatype \"link\"\n",
900                                         id, (char*) field->name );
901                         rc = 1;
902                 }
903         }
904
905         return rc;
906 }
907
908 /**
909         @brief See if every linked field has a counterpart in the links aggregate.
910         @param class Pointer to the current class.
911         @param id Class id.
912         @return 1 if errors found, or 0 if not.
913
914         Rules:
915         - If a field has a datatype of "link" or "org_unit, there must be a corresponding
916         entry in the links aggregate.
917 */
918 static int val_fields_to_links( const Class* class, const char* id ) {
919         int rc = 0;
920         const Field* field = class->fields;
921         while( field ) {
922                 if( DT_LINK != field->datatype && DT_ORG_UNIT != field->datatype ) {
923                         field = field->next;
924                         continue;  // not a link?  skip it
925                 }
926                 // See if there's a matching entry in the <links> aggregate
927                 const Link* link = class->links;
928                 while( link ) {
929                         if( !strcmp( (char*) field->name, (char*) link->field ) )
930                                 break;
931                         link = link->next;
932                 }
933
934                 if( !link ) {
935                         if( !strcmp( (char*) field->name, "id" ) && !strcmp( id, "aou" ) ) {
936                                 // Special exception: primary key of "aou" is of
937                                 // datatype "org_unit", but it's not a foreign key.
938                                 ;
939                         } else {
940                                 printf( "In class \"%s\": Linked field \"%s\" has no matching <link>\n",
941                                                 id, (char*) field->name );
942                                 rc = 1;
943                         }
944                 }
945                 field = field->next;
946         }
947         return rc;
948 }
949
950 /**
951         @brief Search a given Class for a Field with a given name.
952         @param class Pointer to the class in which to search.
953         @param field_name The field name for which to search.
954         @return Pointer to the Field if found, or NULL if not.
955 */
956 static const Field* searchFieldByName( const Class* class, const xmlChar* field_name ) {
957         if( ! class || ! field_name || ! *field_name )
958                 return NULL;
959
960         const char* name = (const char*) field_name;
961         const Field* field = class->fields;
962         while( field ) {
963                 if( field->name && !strcmp( (char*) field->name, name ) )
964                         return field;
965                 field = field->next;
966         }
967
968         return NULL;
969 }
970
971 /**
972         @brief Validate a fields element.
973         @param class Pointer to the current Class.
974         @param id Id of the current Class.
975         @param fields Pointer to the XML node for the fields element.
976         @return 1 if errors found, or 0 if not.
977
978         Rules:
979         - There must be at least one field element.
980         - No other elements are allowed.
981         - Text is not allowed, other than white space.
982         - Comments are allowed (and ignored).
983 */
984 static int val_fields( Class* class, const char* id, xmlNodePtr fields ) {
985         int rc = 0;
986         int field_found = 0;    // boolean
987
988         xmlNodePtr child = fields->children;
989         while( child ) {
990                 const char* child_name = (char*) child->name;
991                 if( xmlNodeIsText( child ) ) {
992                         if( ! xmlIsBlankNode( child ) ) {
993                                 // Found unexpected text.  After removing leading and
994                                 // trailing white space, complain about it.
995                                 xmlChar* content = xmlNodeGetContent( child );
996
997                                 xmlChar* begin = content;
998                                 while( *begin && isspace( *begin ) )
999                                         ++begin;
1000                                 if( *begin ) {
1001                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1002                                         while( (isspace( *end ) ) )
1003                                                 --end;
1004                                         end[ 1 ] = '\0';
1005                                 }
1006
1007                                 printf( "Unexpected text in <fields> element of class \"%s\": \"%s\"\n", id,
1008                                         (char*) begin );
1009                                 xmlFree( content );
1010                         }
1011                 } else if( ! strcmp( child_name, "field" ) ) {
1012                         field_found = 1;
1013                         if( val_one_field( class, id, child ) )
1014                                 rc = 1;
1015                 } else if( !strcmp( child_name, "comment" ) )
1016                         ;  // ignore comment
1017                 else {
1018                         printf( "Line %ld: Unexpected <%s> element in <fields> of class \"%s\"\n",
1019                                 xmlGetLineNo( child ), child_name, id );
1020                         rc = 1;
1021                 }
1022                 child = child->next;
1023         }
1024
1025         if( !field_found ) {
1026                 printf( "No <field> element in class \"%s\"\n", id );
1027                 rc = 1;
1028         }
1029
1030         return rc;
1031 }
1032
1033 /**
1034         @brief Validate a field element within a fields element.
1035         @param class Pointer to the current Class.
1036         @param id Class id.
1037         @param field Pointer to the XML node for the field element.
1038         @return 1 if errors found, or 0 if not.
1039
1040         Rules:
1041         - attribute names are limited to: "name", "virtual", "label", "datatype", "array_position",
1042         "selector", "i18n", "primitive".
1043         - "name" attribute is required.
1044         - label attribute, if present, must have a non-empty value.
1045         - virtual and i18n attributes, if present, must have a value of "true" or "false".
1046         - if the datatype attribute is present, its value must be one of: "bool", "float", "id",
1047         "int", "interval", "link", "money", "number", "org_unit", "text", "timestamp".
1048
1049         Warnings:
1050         - A non-virtual field should have a datatype attribute.
1051         - Attribute "array_position" is deprecated.
1052 */
1053 static int val_one_field( Class* class, const char* id, xmlNodePtr field ) {
1054         int rc = 0;
1055         xmlChar* label = NULL;
1056         xmlChar* field_name = NULL;
1057         int is_virtual = 0;
1058         Datatype datatype = DT_NONE;
1059
1060         // Traverse the attributes
1061         xmlAttrPtr attr = field->properties;
1062         while( attr ) {
1063                 const char* attr_name = (char*) attr->name;
1064                 if( !strcmp( attr_name, "name" ) ) {
1065                         field_name = xmlGetProp( field, (xmlChar*) "name" );
1066                 } else if( !strcmp( attr_name, "virtual" ) ) {
1067                         xmlChar* virt = xmlGetProp( field, (xmlChar*) "virtual" );
1068                         if( !strcmp( (char*) virt, "true" ) )
1069                                 is_virtual = 1;
1070                         else if( strcmp( (char*) virt, "false" ) ) {
1071                                 printf( "Line %ld: Invalid value for virtual attribute: \"%s\"\n",
1072                                         xmlGetLineNo( field ), (char*) virt );
1073                                 rc = 1;
1074                         }
1075                         xmlFree( virt );
1076                         // To do: verify that the namespace is oils_persist
1077                 } else if( !strcmp( attr_name, "label" ) ) {
1078                         label = xmlGetProp( field, (xmlChar*) "label" );
1079                         if( '\0' == *label ) {
1080                                 printf( "Line %ld: Empty value for label attribute for class \"%s\"\n",
1081                                         xmlGetLineNo( field ), id );
1082                                 xmlFree( label );
1083                                 label = NULL;
1084                                 rc = 1;
1085                         }
1086                         // To do: verify that the namespace is reporter
1087                 } else if( !strcmp( attr_name, "datatype" ) ) {
1088                         xmlChar* dt_str = xmlGetProp( field, (xmlChar*) "datatype" );
1089                         datatype = translate_datatype( dt_str );
1090                         if( DT_INVALID == datatype ) {
1091                                 printf( "Line %ld: Invalid datatype \"%s\" in class \"%s\"\n",
1092                                         xmlGetLineNo( field ), (char*) dt_str, id );
1093                                 rc = 1;
1094                         }
1095                         xmlFree( dt_str );
1096                         // To do: make sure that the namespace is reporter
1097                 } else if( !strcmp( attr_name, "array_position" ) ) {
1098                         printf( "Line %ld: WARNING: Deprecated array_position attribute "
1099                                         "for field \"%s\" in class \"%s\"\n",
1100                                         xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1101                 } else if( !strcmp( attr_name, "selector" ) ) {
1102                         ;  // Ignore for now
1103                 } else if( !strcmp( attr_name, "i18n" ) ) {
1104                         xmlChar* i18n = xmlGetProp( field, (xmlChar*) "i18n" );
1105                         if( strcmp( (char*) i18n, "true" ) && strcmp( (char*) i18n, "false" ) ) {
1106                                 printf( "Line %ld: Invalid value for i18n attribute: \"%s\"\n",
1107                                         xmlGetLineNo( field ), (char*) i18n );
1108                                 rc = 1;
1109                         }
1110                         xmlFree( i18n );
1111                         // To do: verify that the namespace is oils_persist
1112                 } else if( !strcmp( attr_name, "primitive" ) ) {
1113                         xmlChar* primitive = xmlGetProp( field, (xmlChar*) "primitive" );
1114                         if( strcmp( (char*) primitive, "string" ) && strcmp( (char*) primitive, "number" ) ) {
1115                                 printf( "Line %ld: Invalid value for primitive attribute: \"%s\"\n",
1116                                         xmlGetLineNo( field ), (char*) primitive );
1117                                 rc = 1;
1118                         }
1119                         xmlFree( primitive );
1120                 } else {
1121                         printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n",
1122                                 xmlGetLineNo( field ), attr_name, id );
1123                         rc = 1;
1124                 }
1125
1126                 attr = attr->next;
1127         }
1128
1129         if( warn && (!is_virtual) && DT_NONE == datatype ) {
1130                 printf( "Line %ld: WARNING: No datatype attribute for field \"%s\" in class \"%s\"\n",
1131                         xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1132         }
1133
1134         if( ! field_name ) {
1135                 printf( "Line %ld: No name attribute for <field> element in class \"%s\"\n",
1136                         xmlGetLineNo( field ), id );
1137                 rc = 1;
1138         } else if( '\0' == *field_name ) {
1139                 printf( "Line %ld: Field name is empty for <field> element in class \"%s\"\n",
1140                         xmlGetLineNo( field ), id );
1141                 rc = 1;
1142         } else {
1143                 // Add to the class's field list
1144                 Field* new_field = newField( field_name );
1145                 new_field->is_virtual = is_virtual;
1146                 new_field->label = label;
1147                 new_field->datatype = datatype;
1148                 if( addField( class, id, new_field ) )
1149                         rc = 1;
1150         }
1151
1152         return rc;
1153 }
1154
1155 /**
1156         @brief Translate a datatype string into a Dataype (an enum).
1157         @param value The value of a datatype attribute.
1158         @return The datatype in the form of an enum.
1159 */
1160 static Datatype translate_datatype( const xmlChar* value ) {
1161         const char* val = (const char*) value;
1162         Datatype type;
1163
1164         if( !value || !*value )
1165                 type = DT_NONE;
1166         else if( !strcmp( val, "bool" ) )
1167                 type = DT_BOOL;
1168         else if( !strcmp( val, "float" ) )
1169                 type = DT_FLOAT;
1170         else if( !strcmp( val, "id" ) )
1171                 type = DT_ID;
1172         else if( !strcmp( val, "int" ) )
1173                 type = DT_INT;
1174         else if( !strcmp( val, "interval" ) )
1175                 type = DT_INTERVAL;
1176         else if( !strcmp( val, "link" ) )
1177                 type = DT_LINK;
1178         else if( !strcmp( val, "money" ) )
1179                 type = DT_MONEY;
1180         else if( !strcmp( val, "number" ) )
1181                 type = DT_NUMBER;
1182         else if( !strcmp( val, "org_unit" ) )
1183                 type = DT_ORG_UNIT;
1184         else if( !strcmp( val, "text" ) )
1185                 type = DT_TEXT;
1186         else if( !strcmp( val, "timestamp" ) )
1187                 type = DT_TIMESTAMP;
1188         else
1189                 type = DT_INVALID;
1190
1191         return type;
1192 }
1193
1194 /**
1195         @brief Validate a links element.
1196         @param class Pointer to the current Class.
1197         @param id Id of the current Class.
1198         @param links Pointer to the XML node for the links element.
1199         @return 1 if errors found, or 0 if not.
1200
1201         Rules:
1202         - No elements other than "link" are allowed.
1203         - Text is not allowed, other than white space.
1204         - Comments are allowed (and ignored).
1205
1206         Warnings:
1207         - There is usually at least one link element.
1208 */
1209 static int val_links( Class* class, const char* id, xmlNodePtr links ) {
1210         int rc = 0;
1211         int link_found = 0;    // boolean
1212
1213         xmlNodePtr child = links->children;
1214         while( child ) {
1215                 const char* child_name = (char*) child->name;
1216                 if( xmlNodeIsText( child ) ) {
1217                         if( ! xmlIsBlankNode( child ) ) {
1218                                 // Found unexpected text.  After removing leading and
1219                                 // trailing white space, complain about it.
1220                                 xmlChar* content = xmlNodeGetContent( child );
1221
1222                                 xmlChar* begin = content;
1223                                 while( *begin && isspace( *begin ) )
1224                                         ++begin;
1225                                 if( *begin ) {
1226                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1227                                         while( (isspace( *end ) ) )
1228                                                 --end;
1229                                         end[ 1 ] = '\0';
1230                                 }
1231
1232                                 printf( "Unexpected text in <links> element of class \"%s\": \"%s\"\n", id,
1233                                         (char*) begin );
1234                                 xmlFree( content );
1235                         }
1236                 } else if( ! strcmp( child_name, "link" ) ) {
1237                         link_found = 1;
1238                         if( val_one_link( class, id, child ) )
1239                                 rc = 1;
1240                 } else if( !strcmp( child_name, "comment" ) )
1241                         ;  // ignore comment
1242                 else {
1243                         printf( "Line %ld: Unexpected <%s> element in <link> of class \"%s\"\n",
1244                                 xmlGetLineNo( child ), child_name, id );
1245                                 rc = 1;
1246                 }
1247                 child = child->next;
1248         }
1249
1250         if( warn && !link_found ) {
1251                 printf( "WARNING: No <link> element in class \"%s\"\n", id );
1252         }
1253
1254         return rc;
1255 }
1256
1257 /**
1258                 @brief Validate one link element.
1259                 @param class Pointer to the current Class.
1260                 @param id Id of the current Class.
1261                 @param link Pointer to the XML node for the link element.
1262                 @return 1 if errors found, or 0 if not.
1263
1264         Rules:
1265         - The only allowed attributes are "field", "reltype", "key", "map", and "class".
1266         - Except for map, every attribute is required.
1267         - Except for map, every attribute must have a non-empty value.
1268         - The value of the reltype attribute must be one of "has_a", "might_have", or "has_many".
1269 */
1270 static int val_one_link( Class* class, const char* id, xmlNodePtr link ) {
1271         int rc = 0;
1272         xmlChar* field_name = NULL;
1273         Reltype reltype = RT_NONE;
1274         xmlChar* key = NULL;
1275         xmlChar* classref = NULL;
1276
1277         // Traverse the attributes
1278         xmlAttrPtr attr = link->properties;
1279         while( attr ) {
1280                 const char* attr_name = (const char*) attr->name;
1281                 if( !strcmp( attr_name, "field" ) ) {
1282                         field_name = xmlGetProp( link, (xmlChar*) "field" );
1283                 } else if (!strcmp( attr_name, "reltype" ) ) {
1284                         ;
1285                         xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" );
1286                         if( *rt ) {
1287                                 reltype = translate_reltype( rt );
1288                                 if( RT_INVALID == reltype ) {
1289                                         printf(
1290                                                 "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n",
1291                                                 xmlGetLineNo( link ), (char*) rt, id );
1292                                         rc = 1;
1293                                 }
1294                         } else {
1295                                 printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n",
1296                                         xmlGetLineNo( link ), id );
1297                                 rc = 1;
1298                         }
1299                         xmlFree( rt );
1300                 } else if (!strcmp( attr_name, "key" ) ) {
1301                         key = xmlGetProp( link, (xmlChar*) "key" );
1302                 } else if (!strcmp( attr_name, "map" ) ) {
1303                         ;   // ignore for now
1304                 } else if (!strcmp( attr_name, "class" ) ) {
1305                         classref = xmlGetProp( link, (xmlChar*) "class" );
1306                 } else {
1307                         printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n",
1308                                 xmlGetLineNo( link ), attr_name, id );
1309                         rc = 1;
1310                 }
1311                 attr = attr->next;
1312         }
1313
1314         if( !field_name ) {
1315                 printf( "Line %ld: No field attribute found in <link> in class \"%s\"\n",
1316                         xmlGetLineNo( link ), id );
1317                 rc = 1;
1318         } else if( '\0' == *field_name ) {
1319                 printf( "Line %ld: Field name is empty for <link> element in class \"%s\"\n",
1320                         xmlGetLineNo( link ), id );
1321                 rc = 1;
1322         } else if( !reltype ) {
1323                 printf( "Line %ld: No reltype attribute found in <link> in class \"%s\"\n",
1324                         xmlGetLineNo( link ), id );
1325                 rc = 1;
1326         } else if( !key ) {
1327                 printf( "Line %ld: No key attribute found in <link> in class \"%s\"\n",
1328                                 xmlGetLineNo( link ), id );
1329                 rc = 1;
1330         } else if( '\0' == *key ) {
1331                 printf( "Line %ld: key attribute is empty for <link> element in class \"%s\"\n",
1332                         xmlGetLineNo( link ), id );
1333                 rc = 1;
1334         } else if( !classref ) {
1335                 printf( "Line %ld: No class attribute found in <link> in class \"%s\"\n",
1336                          xmlGetLineNo( link ), id );
1337                 rc = 1;
1338         } else if( '\0' == *classref ) {
1339                 printf( "Line %ld: class attribute is empty for <link> element in class \"%s\"\n",
1340                         xmlGetLineNo( link ), id );
1341                 rc = 1;
1342         } else {
1343                 // Add to Link list
1344                 Link* new_link = newLink( field_name );
1345                 new_link->reltype = reltype;
1346                 new_link->key = key;
1347                 new_link->classref = classref;
1348                 if( addLink( class, id, new_link ) )
1349                         rc = 1;
1350         }
1351
1352         return rc;
1353 }
1354
1355 /**
1356         @brief Translate an attribute value into a Reltype (an enum).
1357         @param value The value of a reltype attribute.
1358         @return The value of the attribute translated into the enum Reltype.
1359 */
1360 static Reltype translate_reltype( const xmlChar* value ) {
1361         const char* val = (char*) value;
1362         Reltype reltype;
1363
1364         if( !val || !*val )
1365                 reltype = RT_NONE;
1366         else if( !strcmp( val, "has_a" ) )
1367                 reltype = RT_HAS_A;
1368         else if( !strcmp( val, "might_have" ) )
1369                 reltype = RT_MIGHT_HAVE;
1370         else if( !strcmp( val, "has_many" ) )
1371                 reltype = RT_HAS_MANY;
1372         else
1373                 reltype = RT_INVALID;
1374
1375         return reltype;
1376 }
1377
1378 /**
1379         @brief Build a list of classes, while checking for several errors.
1380         @param doc Pointer to the xmlDoc loaded from the IDL.
1381         @return 1 if errors found, or 0 if not.
1382
1383         Rules:
1384         - Every child element of the root must be of the element "class".
1385         - No text is allowed, other than white space, between classes.
1386         - Comments are allowed (and ignored) between classes.
1387 */
1388 static int scan_idl( xmlDocPtr doc ) {
1389         int rc = 0;
1390
1391         xmlNodePtr child = xmlDocGetRootElement( doc )->children;
1392         while( child ) {
1393                 char* child_name = (char*) child->name;
1394                 if( xmlNodeIsText( child ) ) {
1395                         if( ! xmlIsBlankNode( child ) ) {
1396                                 // Found unexpected text.  After removing leading and
1397                                 // trailing white space, complain about it.
1398                                 xmlChar* content = xmlNodeGetContent( child );
1399
1400                                 xmlChar* begin = content;
1401                                 while( *begin && isspace( *begin ) )
1402                                         ++begin;
1403                                 if( *begin ) {
1404                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1405                                         while( (isspace( *end ) ) )
1406                                                 --end;
1407                                         end[ 1 ] = '\0';
1408                                 }
1409
1410                                 printf( "Unexpected text between class elements: \"%s\"\n",
1411                                         (char*) begin );
1412                                 xmlFree( content );
1413                         }
1414                 } else if( !strcmp( child_name, "class" ) ) {
1415                         if( register_class( child ) )
1416                                 rc = 1;
1417                 } else if( !strcmp( child_name, "comment" ) )
1418                         ;  // ignore comment
1419                 else {
1420                         printf( "Line %ld: Unexpected <%s> element under root\n",
1421                                 xmlGetLineNo( child ), child_name );
1422                         rc = 1;
1423                 }
1424
1425                 child = child->next;
1426         }
1427         return rc;
1428 }
1429
1430 /**
1431         @brief Register a class.
1432         @param class Pointer to the class node.
1433         @return 1 if errors found, or 0 if not.
1434
1435         Rules:
1436         - Every class element must have an "id" attribute.
1437         - A class id must not be an empty string.
1438         - Every class id must be unique.
1439
1440         Warnings:
1441         - A class id normally consists entirely of lower case letters, digits and underscores.
1442         - A class id longer than 12 characters is suspiciously long.
1443 */
1444 static int register_class( xmlNodePtr class ) {
1445         int rc = 0;
1446         xmlChar* id = xmlGetProp( class, (xmlChar*) "id" );
1447
1448         if( ! id ) {
1449                 printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) );
1450                 rc = 1;
1451         } else if( ! *id ) {
1452                 printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) );
1453                 rc = 1;
1454         } else {
1455
1456                 // In principle a class id could contain any arbitrary characters, but in practice
1457                 // anything but lower case, digits, and underscores is probably a mistake.
1458                 const xmlChar* p = id;
1459                 while( *p ) {
1460                         if( islower( *p ) || isdigit( *p ) || '_' == *p )
1461                                 ++p;
1462                         else if( warn ) {
1463                                 printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, "
1464                                                 "digits, and underscores\n", xmlGetLineNo( class ), (char*) id );
1465                                 break;
1466                         }
1467                 }
1468
1469                 // Warn about a suspiciously long id
1470                 if( warn && strlen( (char*) id ) > 12 ) {
1471                         printf( "Line %ld: WARNING: Class id is unusually long: \"%s\"\n",
1472                                 xmlGetLineNo( class ), (char*) id );
1473                 }
1474
1475                 // Add the classname to the list of classes.  If the size of
1476                 // the list doesn't change, then we must have a duplicate.
1477                 Class* entry = newClass( class );
1478                 unsigned long class_count = osrfHashGetCount( classes );
1479                 osrfHashSet( classes, entry, (char*) id );
1480                 if( osrfHashGetCount( classes ) == class_count ) {
1481                         printf( "Line %ld: Duplicate class name \"%s\"\n",
1482                                 xmlGetLineNo( class ), (char*) id );
1483                         rc = 1;
1484                 }
1485                 xmlFree( id );
1486         }
1487         return rc;
1488 }
1489
1490 /**
1491         @brief Add a field to a class's field list (unless the id collides with an earlier entry).
1492         @param class Pointer to the current class.
1493         @param id The class id.
1494         @param new_field Pointer to the Field to be added.
1495         @return 0 if successful, or 1 if not (probably due to a duplicate key).
1496
1497         If the id collides with a previous entry, we free the new Field instead of adding it
1498         to the list.  If the label collides with a previous entry, we complain, but we go
1499         ahead and add the Field to the list.
1500
1501         RULES:
1502         - Each field name should be unique within the fields element.
1503         - Each label should be unique within the fields element.
1504 */
1505 static int addField( Class* class, const char* id, Field* new_field ) {
1506         if( ! class || ! new_field )
1507                 return 1;
1508
1509         int rc = 0;
1510         int dup_name = 0;
1511
1512         // See if the class has any other fields with the same name or label.
1513         const Field* old_field = class->fields;
1514         while( old_field ) {
1515
1516                 // Compare the ids
1517                 if( !strcmp( (char*) old_field->name, (char*) new_field->name ) ) {
1518                         printf( "Duplicate field name \"%s\" in class \"%s\"\n",
1519                                 (char*) new_field->name, id );
1520                         dup_name = 1;
1521                         rc = 1;
1522                         break;
1523                 }
1524
1525                 // Compare the labels. if they're both non-empty
1526                 if( old_field->label && *old_field->label
1527                  && new_field->label && *new_field->label
1528                  && !strcmp( (char*) old_field->label, (char*) new_field->label )) {
1529                         printf( "Duplicate labels \"%s\" in class \"%s\"\n",
1530                                 (char*) old_field->label, id );
1531                         rc = 1;
1532                 }
1533
1534                 old_field = old_field->next;
1535         }
1536
1537         if( dup_name ) {
1538                 free( new_field );
1539         } else {
1540                 new_field->next = class->fields;
1541                 class->fields = new_field;
1542         }
1543
1544         return rc;
1545 }
1546
1547 /**
1548         @brief Add a Link to the Link list of a specified Class (unless it's a duplicate).
1549         @param class Pointer to the Class to whose list to add the Link.
1550         @param id Class id.
1551         @param new_link Pointer to the Link to be added.
1552         @return 0 if successful, or 1 if not (probably due to a duplicate).
1553
1554         If there's already a Link in the list with the same field name, free the new Link
1555         instead of adding it.
1556 */
1557 static int addLink( Class* class, const char* id, Link* new_link ) {
1558         if( ! class || ! new_link )
1559                 return 1;
1560
1561         int rc = 0;
1562         int dup_name = 0;
1563
1564         // See if the class has any other links with the same field
1565         const Link* old_link = class->links;
1566         while( old_link ) {
1567
1568                 if( !strcmp( (char*) old_link->field, (char*) new_link->field ) ) {
1569                         printf( "Duplicate field name \"%s\" in links of class \"%s\"\n",
1570                                 (char*) old_link->field, id );
1571                         rc = 1;
1572                         dup_name = 1;
1573                         break;
1574                 }
1575
1576                 old_link = old_link->next;
1577         }
1578
1579         if( dup_name ) {
1580                 freeLink( new_link );
1581         } else {
1582                 // Add to the linked list
1583                 new_link->next = class->links;
1584                 class->links = new_link;
1585         }
1586
1587         return rc;
1588 }
1589
1590 /**
1591         @brief Create and initialize a new Class.
1592         @param node Pointer to the XML node for a class element.
1593         @return Pointer to the newly created Class.
1594
1595         The calling code is responsible for freeing the Class by calling freeClass().  In practice
1596         this happens automagically when we free the osrfHash classes.
1597 */
1598 static Class* newClass( xmlNodePtr node ) {
1599         Class* class = safe_malloc( sizeof( Class ) );
1600         class->node = node;
1601         class->loaded = 0;
1602         class->is_virtual = 0;
1603         xmlFree( class->primary );
1604         class->fields = NULL;
1605         class->links = NULL;
1606         return class;
1607 }
1608
1609 /**
1610         @brief Free a Class and everything it owns.
1611         @param key The class id (not used).
1612         @param p A pointer to the Class to be freed, cast to a void pointer.
1613
1614         This function is designed to be a freeItem callback for an osrfHash.
1615 */
1616 static void freeClass( char* key, void* p ) {
1617         Class* class = p;
1618
1619         // Free the linked list of Fields
1620         Field* next_field = NULL;
1621         Field* field = class->fields;
1622         while( field ) {
1623                 next_field = field->next;
1624                 freeField( field );
1625                 field = next_field;
1626         }
1627
1628         // Free the linked list of Links
1629         Link* next_link = NULL;
1630         Link* link = class->links;
1631         while( link ) {
1632                 next_link = link->next;
1633                 freeLink( link );
1634                 link = next_link;
1635         }
1636
1637         free( class );
1638 }
1639
1640 /**
1641         @brief Allocate and initialize a Field.
1642         @param name Field name.
1643         @return Pointer to a new Field.
1644
1645         It is the responsibility of the caller to free the Field by calling freeField().
1646 */
1647 static Field* newField( xmlChar* name ) {
1648         Field* field = safe_malloc( sizeof( Field ) );
1649         field->next         = NULL;
1650         field->name         = name;
1651         field->is_virtual   = 0;
1652         field->label        = NULL;
1653         field->datatype     = DT_NONE;
1654         return field;
1655 }
1656
1657 /**
1658         @brief Free a Field and everything in it.
1659         @param field Pointer to the Field to be freed.
1660 */
1661 static void freeField( Field* field ) {
1662         if( field ) {
1663                 xmlFree( field->name );
1664                 if( field->label )
1665                         xmlFree( field->label );
1666                 free( field );
1667         }
1668 }
1669
1670 /**
1671         @brief Allocate and initialize a Link.
1672         @param field Field name.
1673         @return Pointer to a new Link.
1674
1675         It is the responsibility of the caller to free the Link by calling freeLink().
1676 */
1677 static Link* newLink( xmlChar* field ) {
1678         Link* link = safe_malloc( sizeof( Link ) );
1679         link->next         = NULL;
1680         link->field        = field;
1681         link->reltype      = RT_NONE;
1682         link->key          = NULL;
1683         link->classref     = NULL;
1684         return link;
1685 }
1686
1687 /**
1688         @brief Free a Link and everything it owns.
1689         @param link Pointer to the Link to be freed.
1690 */
1691 static void freeLink( Link* link ) {
1692         if( link ) {
1693                 xmlFree( link->field );
1694                 xmlFree( link->key );
1695                 xmlFree( link->classref );
1696                 free( link );
1697         }
1698 }