]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/idlval.c
LP1818288 WS Option to pre-fetch record holds
[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 <opensrf/utils.h>
33 #include <opensrf/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                         } else if( !strchr( (const char*) sequence, '.' )) {
744                                 printf(
745                                         "Line %ld: name of sequence for class \"%s\" is not qualified by schema\n",
746                                         xmlGetLineNo( fields ), id );
747                                 rc = 1;
748                         }
749                 } else {
750                         printf( "Line %ld: Unexpected fields attribute \"%s\" in class \"%s\"\n",
751                                 xmlGetLineNo( fields ), attr_name, id );
752                         rc = 1;
753                 }
754
755                 attr = attr->next;
756         }
757
758         if( sequence && ! primary ) {
759                 printf( "Line %ld: class \"%s\" has a sequence identified but no primary key\n",
760                         xmlGetLineNo( fields ), id );
761                 rc = 1;
762         }
763
764         if( primary ) {
765                 // look for the primary key
766                 Field* field = class->fields;
767                 while( field ) {
768                         if( !strcmp( (char*) field->name, (char*) primary ) )
769                                 break;
770                         field = field->next;
771                 }
772                 if( !field ) {
773                         printf( "Primary key field \"%s\" does not exist for class \"%s\"\n",
774                                 (char*) primary, id );
775                         rc = 1;
776                 } else if( DT_ID == field->datatype && ! sequence && ! class->is_virtual ) {
777                         printf(
778                                 "Line %ld: Primary key is an id; class \"%s\" may need a sequence attribute\n",
779                                 xmlGetLineNo( fields ), id );
780                         rc = 1;
781                 } else if(    DT_ID != field->datatype
782                                    && DT_INT != field->datatype
783                                    && DT_ORG_UNIT != field->datatype
784                                    && sequence ) {
785                         printf(
786                                 "Line %ld: Datatype of key for class \"%s\" does not allow a sequence attribute\n",
787                                 xmlGetLineNo( fields ), id );
788                         rc = 1;
789                 }
790         }
791
792         xmlFree( primary );
793         xmlFree( sequence );
794         return rc;
795 }
796
797 /**
798         @brief Verify that every Link has a matching Field for a given Class.
799         @param class Pointer to the current class.
800         @param id Class id.
801         @return 1 if errors found, or 0 if not.
802
803         Rules:
804         - For every link element, there must be a matching field element in the same class.
805         - If the link's reltype is "has_many", the field must be a virtual field of type link
806         or org_unit.
807         - If the link's reltype is "has_a" or "might_have", the field must be a non-virtual link
808         of type link or org_unit.
809 */
810 static int val_links_to_fields( const Class* class, const char* id ) {
811         if( !class )
812                 return 1;
813
814         int rc = 0;
815
816         const Link* link = class->links;
817         while( link ) {
818                 if( link->field && *link->field ) {
819                         const Field* field = searchFieldByName( class, link->field );
820                         if( field ) {
821                                 if( compareFieldAndLink( class, id, field, link ) )
822                                         rc = 1;
823                         } else {
824                                 printf( "\"%s\" class has no <field> corresponding to <link> for \"%s\"\n",
825                                         id, (char*) link->field );
826                                 rc = 1;
827                         }
828                 }
829                 link = link->next;
830         }
831
832         return rc;
833 }
834
835 /**
836         @brief Compare matching field and link elements to see if they are compatible
837         @param class Pointer to the current Class.
838         @param id Class id.
839         @param field Pointer to the Field to be compared to the Link.
840         @param link Pointer to the Link to be compared to the Field.
841         @return 0 if they are compatible, or 1 if not.
842
843         Rules:
844         - If the reltype is "has_many", the field must be virtual.
845         - If a field corresponds to a link, and is not the primary key, then it must have a
846         datatype "link" or "org_unit".
847         - If the datatype is "org_unit", the linkage must be to the class "aou".
848
849         Warnings:
850         - If the reltype is "has_a" or "might_have", the the field should probably @em not
851         be virtual, but there are legitimate exceptions.
852         - If the linkage is to the class "aou", then the datatype should probably be "org_unit".
853 */
854 static int compareFieldAndLink( const Class* class, const char* id,
855                 const Field* field, const Link* link ) {
856         int rc = 0;
857
858         Datatype datatype = field->datatype;
859         const char* classref = (const char*) link->classref;
860
861         // Validate the virtuality of the field
862         if( RT_HAS_A == link->reltype || RT_MIGHT_HAVE == link->reltype ) {
863                 if( warn && field->is_virtual ) {
864                         // This is the child class; field should usually be non-virtual,
865                         // but there are legitimate exceptions.
866                         printf( "WARNING: In class \"%s\": field \"%s\" is tied to a \"has_a\" or "
867                                 "\"might_have\" link; perhaps should not be virtual\n",
868                                 id, (char*) field->name );
869                 }
870         } else if ( RT_HAS_MANY == link->reltype ) {
871                 if( ! field->is_virtual ) {
872                         printf( "In class \"%s\": field \"%s\" is tied to a \"has_many\" link "
873                                         "and therefore should be virtual\n", id, (char*) field->name );
874                         rc = 1;
875                 }
876         }
877
878         // Validate the datatype of the field
879         if( class->primary && !strcmp( (char*) class->primary, (char*) field->name ) ) {
880                 ; // For the primary key field, the datatype can be anything
881         } else if( DT_NONE == datatype || DT_INVALID == datatype ) {
882                 printf( "In class \"%s\": \"%s\" field should have a datatype for linkage\n",
883                                 id, (char*) field->name );
884                 rc = 1;
885         } else if( DT_ORG_UNIT == datatype ) {
886                 if( strcmp( classref, "aou" ) ) {
887                         printf( "In class \"%s\": \"%s\" field should have a datatype "
888                                         "\"link\", not \"org_unit\"\n", id, field->name );
889                         rc = 1;
890                 }
891         } else if( DT_LINK == datatype ) {
892                 if( warn && !strcmp( classref, "aou" ) ) {
893                         printf( "WARNING: In class \"%s\", field \"%s\": Consider changing datatype "
894                                         "to \"org_unit\"\n", id, (char*) field->name );
895                 }
896         } else {
897                 // Datatype should be "link", or maybe "org_unit"
898                 if( !strcmp( classref, "aou" ) ) {
899                         printf( "In class \"%s\": \"%s\" field should have a datatype "
900                                         "\"org_unit\" or \"link\"\n",
901                                         id, (char*) field->name );
902                         rc = 1;
903                 } else {
904                         printf( "In class \"%s\": \"%s\" field should have a datatype \"link\"\n",
905                                         id, (char*) field->name );
906                         rc = 1;
907                 }
908         }
909
910         return rc;
911 }
912
913 /**
914         @brief See if every linked field has a counterpart in the links aggregate.
915         @param class Pointer to the current class.
916         @param id Class id.
917         @return 1 if errors found, or 0 if not.
918
919         Rules:
920         - If a field has a datatype of "link" or "org_unit, there must be a corresponding
921         entry in the links aggregate.
922 */
923 static int val_fields_to_links( const Class* class, const char* id ) {
924         int rc = 0;
925         const Field* field = class->fields;
926         while( field ) {
927                 if( DT_LINK != field->datatype && DT_ORG_UNIT != field->datatype ) {
928                         field = field->next;
929                         continue;  // not a link?  skip it
930                 }
931                 // See if there's a matching entry in the <links> aggregate
932                 const Link* link = class->links;
933                 while( link ) {
934                         if( !strcmp( (char*) field->name, (char*) link->field ) )
935                                 break;
936                         link = link->next;
937                 }
938
939                 if( !link ) {
940                         if( !strcmp( (char*) field->name, "id" ) && !strcmp( id, "aou" ) ) {
941                                 // Special exception: primary key of "aou" is of
942                                 // datatype "org_unit", but it's not a foreign key.
943                                 ;
944                         } else {
945                                 printf( "In class \"%s\": Linked field \"%s\" has no matching <link>\n",
946                                                 id, (char*) field->name );
947                                 rc = 1;
948                         }
949                 }
950                 field = field->next;
951         }
952         return rc;
953 }
954
955 /**
956         @brief Search a given Class for a Field with a given name.
957         @param class Pointer to the class in which to search.
958         @param field_name The field name for which to search.
959         @return Pointer to the Field if found, or NULL if not.
960 */
961 static const Field* searchFieldByName( const Class* class, const xmlChar* field_name ) {
962         if( ! class || ! field_name || ! *field_name )
963                 return NULL;
964
965         const char* name = (const char*) field_name;
966         const Field* field = class->fields;
967         while( field ) {
968                 if( field->name && !strcmp( (char*) field->name, name ) )
969                         return field;
970                 field = field->next;
971         }
972
973         return NULL;
974 }
975
976 /**
977         @brief Validate a fields element.
978         @param class Pointer to the current Class.
979         @param id Id of the current Class.
980         @param fields Pointer to the XML node for the fields element.
981         @return 1 if errors found, or 0 if not.
982
983         Rules:
984         - There must be at least one field element.
985         - No other elements are allowed.
986         - Text is not allowed, other than white space.
987         - Comments are allowed (and ignored).
988 */
989 static int val_fields( Class* class, const char* id, xmlNodePtr fields ) {
990         int rc = 0;
991         int field_found = 0;    // boolean
992
993         xmlNodePtr child = fields->children;
994         while( child ) {
995                 const char* child_name = (char*) child->name;
996                 if( xmlNodeIsText( child ) ) {
997                         if( ! xmlIsBlankNode( child ) ) {
998                                 // Found unexpected text.  After removing leading and
999                                 // trailing white space, complain about it.
1000                                 xmlChar* content = xmlNodeGetContent( child );
1001
1002                                 xmlChar* begin = content;
1003                                 while( *begin && isspace( *begin ) )
1004                                         ++begin;
1005                                 if( *begin ) {
1006                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1007                                         while( (isspace( *end ) ) )
1008                                                 --end;
1009                                         end[ 1 ] = '\0';
1010                                 }
1011
1012                                 printf( "Unexpected text in <fields> element of class \"%s\": \"%s\"\n", id,
1013                                         (char*) begin );
1014                                 xmlFree( content );
1015                         }
1016                 } else if( ! strcmp( child_name, "field" ) ) {
1017                         field_found = 1;
1018                         if( val_one_field( class, id, child ) )
1019                                 rc = 1;
1020                 } else if( !strcmp( child_name, "comment" ) )
1021                         ;  // ignore comment
1022                 else {
1023                         printf( "Line %ld: Unexpected <%s> element in <fields> of class \"%s\"\n",
1024                                 xmlGetLineNo( child ), child_name, id );
1025                         rc = 1;
1026                 }
1027                 child = child->next;
1028         }
1029
1030         if( !field_found ) {
1031                 printf( "No <field> element in class \"%s\"\n", id );
1032                 rc = 1;
1033         }
1034
1035         return rc;
1036 }
1037
1038 /**
1039         @brief Validate a field element within a fields element.
1040         @param class Pointer to the current Class.
1041         @param id Class id.
1042         @param field Pointer to the XML node for the field element.
1043         @return 1 if errors found, or 0 if not.
1044
1045         Rules:
1046         - attribute names are limited to: "name", "virtual", "label", "datatype", "array_position",
1047         "selector", "i18n", "primitive".
1048         - "name" attribute is required.
1049         - label attribute, if present, must have a non-empty value.
1050         - virtual and i18n attributes, if present, must have a value of "true" or "false".
1051         - if the datatype attribute is present, its value must be one of: "bool", "float", "id",
1052         "int", "interval", "link", "money", "number", "org_unit", "text", "timestamp".
1053
1054         Warnings:
1055         - A non-virtual field should have a datatype attribute.
1056         - Attribute "array_position" is deprecated.
1057 */
1058 static int val_one_field( Class* class, const char* id, xmlNodePtr field ) {
1059         int rc = 0;
1060         xmlChar* label = NULL;
1061         xmlChar* field_name = NULL;
1062         int is_virtual = 0;
1063         Datatype datatype = DT_NONE;
1064
1065         // Traverse the attributes
1066         xmlAttrPtr attr = field->properties;
1067         while( attr ) {
1068                 const char* attr_name = (char*) attr->name;
1069                 if( !strcmp( attr_name, "name" ) ) {
1070                         field_name = xmlGetProp( field, (xmlChar*) "name" );
1071                 } else if( !strcmp( attr_name, "virtual" ) ) {
1072                         xmlChar* virt = xmlGetProp( field, (xmlChar*) "virtual" );
1073                         if( !strcmp( (char*) virt, "true" ) )
1074                                 is_virtual = 1;
1075                         else if( strcmp( (char*) virt, "false" ) ) {
1076                                 printf( "Line %ld: Invalid value for virtual attribute: \"%s\"\n",
1077                                         xmlGetLineNo( field ), (char*) virt );
1078                                 rc = 1;
1079                         }
1080                         xmlFree( virt );
1081                         // To do: verify that the namespace is oils_persist
1082                 } else if( !strcmp( attr_name, "label" ) ) {
1083                         label = xmlGetProp( field, (xmlChar*) "label" );
1084                         if( '\0' == *label ) {
1085                                 printf( "Line %ld: Empty value for label attribute for class \"%s\"\n",
1086                                         xmlGetLineNo( field ), id );
1087                                 xmlFree( label );
1088                                 label = NULL;
1089                                 rc = 1;
1090                         }
1091                         // To do: verify that the namespace is reporter
1092                 } else if( !strcmp( attr_name, "datatype" ) ) {
1093                         xmlChar* dt_str = xmlGetProp( field, (xmlChar*) "datatype" );
1094                         datatype = translate_datatype( dt_str );
1095                         if( DT_INVALID == datatype ) {
1096                                 printf( "Line %ld: Invalid datatype \"%s\" in class \"%s\"\n",
1097                                         xmlGetLineNo( field ), (char*) dt_str, id );
1098                                 rc = 1;
1099                         }
1100                         xmlFree( dt_str );
1101                         // To do: make sure that the namespace is reporter
1102                 } else if( !strcmp( attr_name, "array_position" ) ) {
1103                         printf( "Line %ld: WARNING: Deprecated array_position attribute "
1104                                         "for field \"%s\" in class \"%s\"\n",
1105                                         xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1106                 } else if( !strcmp( attr_name, "selector" ) ) {
1107                         ;  // Ignore for now
1108                 } else if( !strcmp( attr_name, "i18n" ) ) {
1109                         xmlChar* i18n = xmlGetProp( field, (xmlChar*) "i18n" );
1110                         if( strcmp( (char*) i18n, "true" ) && strcmp( (char*) i18n, "false" ) ) {
1111                                 printf( "Line %ld: Invalid value for i18n attribute: \"%s\"\n",
1112                                         xmlGetLineNo( field ), (char*) i18n );
1113                                 rc = 1;
1114                         }
1115                         xmlFree( i18n );
1116                         // To do: verify that the namespace is oils_persist
1117                 } else if( !strcmp( attr_name, "primitive" ) ) {
1118                         xmlChar* primitive = xmlGetProp( field, (xmlChar*) "primitive" );
1119                         if( strcmp( (char*) primitive, "string" ) && strcmp( (char*) primitive, "number" ) ) {
1120                                 printf( "Line %ld: Invalid value for primitive attribute: \"%s\"\n",
1121                                         xmlGetLineNo( field ), (char*) primitive );
1122                                 rc = 1;
1123                         }
1124                         xmlFree( primitive );
1125                 } else if( !strcmp( attr_name, "validate" )) {
1126                         xmlChar* validate = xmlGetProp( field, (xmlChar*) "validate" );
1127                         if( !*validate ) {
1128                                 // Value should be a regular expression to define a validation rule
1129                                 printf( "Line %ld: Empty value for \"validate\" attribute "
1130                                         "for field \"%s\" in class \"%s\"\n",
1131                                         xmlGetLineNo( field ), (char*) field_name ? : "", id );
1132                                 rc = 1;
1133                         }
1134                         xmlFree( validate );
1135                         // To do: verify that the namespace is oils_obj
1136                 } else if( !strcmp( attr_name, "required" )) {
1137                         xmlChar* required = xmlGetProp( field, (xmlChar*) "required" );
1138                         if( strcmp( (char*) required, "true" ) && strcmp( (char*) required, "false" )) {
1139                                 printf( 
1140                                         "Line %ld: Invalid value \"%s\" for \"required\" attribute "
1141                                         "for field \"%s\" in class \"%s\"\n",
1142                                         xmlGetLineNo( field ), (char*) required,
1143                                         (char*) field_name ? : "", id );
1144                                 rc = 1;
1145                         }
1146                         xmlFree( required );
1147                         // To do: verify that the namespace is oils_obj
1148                 } else {
1149                         printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n",
1150                                 xmlGetLineNo( field ), attr_name, id );
1151                         rc = 1;
1152                 }
1153
1154                 attr = attr->next;
1155         }
1156
1157         if( warn && (!is_virtual) && DT_NONE == datatype ) {
1158                 printf( "Line %ld: WARNING: No datatype attribute for field \"%s\" in class \"%s\"\n",
1159                         xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1160         }
1161
1162         if( ! field_name ) {
1163                 printf( "Line %ld: No name attribute for <field> element in class \"%s\"\n",
1164                         xmlGetLineNo( field ), id );
1165                 rc = 1;
1166         } else if( '\0' == *field_name ) {
1167                 printf( "Line %ld: Field name is empty for <field> element in class \"%s\"\n",
1168                         xmlGetLineNo( field ), id );
1169                 rc = 1;
1170         } else {
1171                 // Add to the class's field list
1172                 Field* new_field = newField( field_name );
1173                 new_field->is_virtual = is_virtual;
1174                 new_field->label = label;
1175                 new_field->datatype = datatype;
1176                 if( addField( class, id, new_field ) )
1177                         rc = 1;
1178         }
1179
1180         return rc;
1181 }
1182
1183 /**
1184         @brief Translate a datatype string into a Dataype (an enum).
1185         @param value The value of a datatype attribute.
1186         @return The datatype in the form of an enum.
1187 */
1188 static Datatype translate_datatype( const xmlChar* value ) {
1189         const char* val = (const char*) value;
1190         Datatype type;
1191
1192         if( !value || !*value )
1193                 type = DT_NONE;
1194         else if( !strcmp( val, "bool" ) )
1195                 type = DT_BOOL;
1196         else if( !strcmp( val, "float" ) )
1197                 type = DT_FLOAT;
1198         else if( !strcmp( val, "id" ) )
1199                 type = DT_ID;
1200         else if( !strcmp( val, "int" ) )
1201                 type = DT_INT;
1202         else if( !strcmp( val, "interval" ) )
1203                 type = DT_INTERVAL;
1204         else if( !strcmp( val, "link" ) )
1205                 type = DT_LINK;
1206         else if( !strcmp( val, "money" ) )
1207                 type = DT_MONEY;
1208         else if( !strcmp( val, "number" ) )
1209                 type = DT_NUMBER;
1210         else if( !strcmp( val, "org_unit" ) )
1211                 type = DT_ORG_UNIT;
1212         else if( !strcmp( val, "text" ) )
1213                 type = DT_TEXT;
1214         else if( !strcmp( val, "timestamp" ) )
1215                 type = DT_TIMESTAMP;
1216         else
1217                 type = DT_INVALID;
1218
1219         return type;
1220 }
1221
1222 /**
1223         @brief Validate a links element.
1224         @param class Pointer to the current Class.
1225         @param id Id of the current Class.
1226         @param links Pointer to the XML node for the links element.
1227         @return 1 if errors found, or 0 if not.
1228
1229         Rules:
1230         - No elements other than "link" are allowed.
1231         - Text is not allowed, other than white space.
1232         - Comments are allowed (and ignored).
1233
1234         Warnings:
1235         - There is usually at least one link element.
1236 */
1237 static int val_links( Class* class, const char* id, xmlNodePtr links ) {
1238         int rc = 0;
1239         int link_found = 0;    // boolean
1240
1241         xmlNodePtr child = links->children;
1242         while( child ) {
1243                 const char* child_name = (char*) child->name;
1244                 if( xmlNodeIsText( child ) ) {
1245                         if( ! xmlIsBlankNode( child ) ) {
1246                                 // Found unexpected text.  After removing leading and
1247                                 // trailing white space, complain about it.
1248                                 xmlChar* content = xmlNodeGetContent( child );
1249
1250                                 xmlChar* begin = content;
1251                                 while( *begin && isspace( *begin ) )
1252                                         ++begin;
1253                                 if( *begin ) {
1254                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1255                                         while( (isspace( *end ) ) )
1256                                                 --end;
1257                                         end[ 1 ] = '\0';
1258                                 }
1259
1260                                 printf( "Unexpected text in <links> element of class \"%s\": \"%s\"\n", id,
1261                                         (char*) begin );
1262                                 xmlFree( content );
1263                         }
1264                 } else if( ! strcmp( child_name, "link" ) ) {
1265                         link_found = 1;
1266                         if( val_one_link( class, id, child ) )
1267                                 rc = 1;
1268                 } else if( !strcmp( child_name, "comment" ) )
1269                         ;  // ignore comment
1270                 else {
1271                         printf( "Line %ld: Unexpected <%s> element in <link> of class \"%s\"\n",
1272                                 xmlGetLineNo( child ), child_name, id );
1273                                 rc = 1;
1274                 }
1275                 child = child->next;
1276         }
1277
1278         if( warn && !link_found ) {
1279                 printf( "WARNING: No <link> element in class \"%s\"\n", id );
1280         }
1281
1282         return rc;
1283 }
1284
1285 /**
1286                 @brief Validate one link element.
1287                 @param class Pointer to the current Class.
1288                 @param id Id of the current Class.
1289                 @param link Pointer to the XML node for the link element.
1290                 @return 1 if errors found, or 0 if not.
1291
1292         Rules:
1293         - The only allowed attributes are "field", "reltype", "key", "map", and "class".
1294         - Except for map, every attribute is required.
1295         - Except for map, every attribute must have a non-empty value.
1296         - The value of the reltype attribute must be one of "has_a", "might_have", or "has_many".
1297 */
1298 static int val_one_link( Class* class, const char* id, xmlNodePtr link ) {
1299         int rc = 0;
1300         xmlChar* field_name = NULL;
1301         Reltype reltype = RT_NONE;
1302         xmlChar* key = NULL;
1303         xmlChar* classref = NULL;
1304
1305         // Traverse the attributes
1306         xmlAttrPtr attr = link->properties;
1307         while( attr ) {
1308                 const char* attr_name = (const char*) attr->name;
1309                 if( !strcmp( attr_name, "field" ) ) {
1310                         field_name = xmlGetProp( link, (xmlChar*) "field" );
1311                 } else if (!strcmp( attr_name, "reltype" ) ) {
1312                         ;
1313                         xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" );
1314                         if( *rt ) {
1315                                 reltype = translate_reltype( rt );
1316                                 if( RT_INVALID == reltype ) {
1317                                         printf(
1318                                                 "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n",
1319                                                 xmlGetLineNo( link ), (char*) rt, id );
1320                                         rc = 1;
1321                                 }
1322                         } else {
1323                                 printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n",
1324                                         xmlGetLineNo( link ), id );
1325                                 rc = 1;
1326                         }
1327                         xmlFree( rt );
1328                 } else if (!strcmp( attr_name, "key" ) ) {
1329                         key = xmlGetProp( link, (xmlChar*) "key" );
1330                 } else if (!strcmp( attr_name, "map" ) ) {
1331                         ;   // ignore for now
1332                 } else if (!strcmp( attr_name, "class" ) ) {
1333                         classref = xmlGetProp( link, (xmlChar*) "class" );
1334                 } else {
1335                         printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n",
1336                                 xmlGetLineNo( link ), attr_name, id );
1337                         rc = 1;
1338                 }
1339                 attr = attr->next;
1340         }
1341
1342         if( !field_name ) {
1343                 printf( "Line %ld: No field attribute found in <link> in class \"%s\"\n",
1344                         xmlGetLineNo( link ), id );
1345                 rc = 1;
1346         } else if( '\0' == *field_name ) {
1347                 printf( "Line %ld: Field name is empty for <link> element in class \"%s\"\n",
1348                         xmlGetLineNo( link ), id );
1349                 rc = 1;
1350         } else if( !reltype ) {
1351                 printf( "Line %ld: No reltype attribute found in <link> in class \"%s\"\n",
1352                         xmlGetLineNo( link ), id );
1353                 rc = 1;
1354         } else if( !key ) {
1355                 printf( "Line %ld: No key attribute found in <link> in class \"%s\"\n",
1356                                 xmlGetLineNo( link ), id );
1357                 rc = 1;
1358         } else if( '\0' == *key ) {
1359                 printf( "Line %ld: key attribute is empty for <link> element in class \"%s\"\n",
1360                         xmlGetLineNo( link ), id );
1361                 rc = 1;
1362         } else if( !classref ) {
1363                 printf( "Line %ld: No class attribute found in <link> in class \"%s\"\n",
1364                          xmlGetLineNo( link ), id );
1365                 rc = 1;
1366         } else if( '\0' == *classref ) {
1367                 printf( "Line %ld: class attribute is empty for <link> element in class \"%s\"\n",
1368                         xmlGetLineNo( link ), id );
1369                 rc = 1;
1370         } else {
1371                 // Add to Link list
1372                 Link* new_link = newLink( field_name );
1373                 new_link->reltype = reltype;
1374                 new_link->key = key;
1375                 new_link->classref = classref;
1376                 if( addLink( class, id, new_link ) )
1377                         rc = 1;
1378         }
1379
1380         return rc;
1381 }
1382
1383 /**
1384         @brief Translate an attribute value into a Reltype (an enum).
1385         @param value The value of a reltype attribute.
1386         @return The value of the attribute translated into the enum Reltype.
1387 */
1388 static Reltype translate_reltype( const xmlChar* value ) {
1389         const char* val = (char*) value;
1390         Reltype reltype;
1391
1392         if( !val || !*val )
1393                 reltype = RT_NONE;
1394         else if( !strcmp( val, "has_a" ) )
1395                 reltype = RT_HAS_A;
1396         else if( !strcmp( val, "might_have" ) )
1397                 reltype = RT_MIGHT_HAVE;
1398         else if( !strcmp( val, "has_many" ) )
1399                 reltype = RT_HAS_MANY;
1400         else
1401                 reltype = RT_INVALID;
1402
1403         return reltype;
1404 }
1405
1406 /**
1407         @brief Build a list of classes, while checking for several errors.
1408         @param doc Pointer to the xmlDoc loaded from the IDL.
1409         @return 1 if errors found, or 0 if not.
1410
1411         Rules:
1412         - Every child element of the root must be of the element "class".
1413         - No text is allowed, other than white space, between classes.
1414         - Comments are allowed (and ignored) between classes.
1415 */
1416 static int scan_idl( xmlDocPtr doc ) {
1417         int rc = 0;
1418
1419         xmlNodePtr child = xmlDocGetRootElement( doc )->children;
1420         while( child ) {
1421                 char* child_name = (char*) child->name;
1422                 if( xmlNodeIsText( child ) ) {
1423                         if( ! xmlIsBlankNode( child ) ) {
1424                                 // Found unexpected text.  After removing leading and
1425                                 // trailing white space, complain about it.
1426                                 xmlChar* content = xmlNodeGetContent( child );
1427
1428                                 xmlChar* begin = content;
1429                                 while( *begin && isspace( *begin ) )
1430                                         ++begin;
1431                                 if( *begin ) {
1432                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1433                                         while( (isspace( *end ) ) )
1434                                                 --end;
1435                                         end[ 1 ] = '\0';
1436                                 }
1437
1438                                 printf( "Unexpected text between class elements: \"%s\"\n",
1439                                         (char*) begin );
1440                                 xmlFree( content );
1441                         }
1442                 } else if( !strcmp( child_name, "class" ) ) {
1443                         if( register_class( child ) )
1444                                 rc = 1;
1445                 } else if( !strcmp( child_name, "comment" ) )
1446                         ;  // ignore comment
1447                 else {
1448                         printf( "Line %ld: Unexpected <%s> element under root\n",
1449                                 xmlGetLineNo( child ), child_name );
1450                         rc = 1;
1451                 }
1452
1453                 child = child->next;
1454         }
1455         return rc;
1456 }
1457
1458 /**
1459         @brief Register a class.
1460         @param class Pointer to the class node.
1461         @return 1 if errors found, or 0 if not.
1462
1463         Rules:
1464         - Every class element must have an "id" attribute.
1465         - A class id must not be an empty string.
1466         - Every class id must be unique.
1467
1468         Warnings:
1469         - A class id normally consists entirely of lower case letters, digits and underscores.
1470         - A class id longer than 12 characters is suspiciously long.
1471 */
1472 static int register_class( xmlNodePtr class ) {
1473         int rc = 0;
1474         xmlChar* id = xmlGetProp( class, (xmlChar*) "id" );
1475
1476         if( ! id ) {
1477                 printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) );
1478                 rc = 1;
1479         } else if( ! *id ) {
1480                 printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) );
1481                 rc = 1;
1482         } else {
1483
1484                 // In principle a class id could contain any arbitrary characters, but in practice
1485                 // anything but lower case, digits, and underscores is probably a mistake.
1486                 const xmlChar* p = id;
1487                 while( *p ) {
1488                         if( islower( *p ) || isdigit( *p ) || '_' == *p )
1489                                 ++p;
1490                         else if( warn ) {
1491                                 printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, "
1492                                                 "digits, and underscores\n", xmlGetLineNo( class ), (char*) id );
1493                                 break;
1494                         }
1495                 }
1496
1497                 // Warn about a suspiciously long id
1498                 if( warn && strlen( (char*) id ) > 12 ) {
1499                         printf( "Line %ld: WARNING: Class id is unusually long: \"%s\"\n",
1500                                 xmlGetLineNo( class ), (char*) id );
1501                 }
1502
1503                 // Add the classname to the list of classes.  If the size of
1504                 // the list doesn't change, then we must have a duplicate.
1505                 Class* entry = newClass( class );
1506                 unsigned long class_count = osrfHashGetCount( classes );
1507                 osrfHashSet( classes, entry, (char*) id );
1508                 if( osrfHashGetCount( classes ) == class_count ) {
1509                         printf( "Line %ld: Duplicate class name \"%s\"\n",
1510                                 xmlGetLineNo( class ), (char*) id );
1511                         rc = 1;
1512                 }
1513                 xmlFree( id );
1514         }
1515         return rc;
1516 }
1517
1518 /**
1519         @brief Add a field to a class's field list (unless the id collides with an earlier entry).
1520         @param class Pointer to the current class.
1521         @param id The class id.
1522         @param new_field Pointer to the Field to be added.
1523         @return 0 if successful, or 1 if not (probably due to a duplicate key).
1524
1525         If the id collides with a previous entry, we free the new Field instead of adding it
1526         to the list.  If the label collides with a previous entry, we complain, but we go
1527         ahead and add the Field to the list.
1528
1529         RULES:
1530         - Each field name should be unique within the fields element.
1531         - Each label should be unique within the fields element.
1532 */
1533 static int addField( Class* class, const char* id, Field* new_field ) {
1534         if( ! class || ! new_field )
1535                 return 1;
1536
1537         int rc = 0;
1538         int dup_name = 0;
1539
1540         // See if the class has any other fields with the same name or label.
1541         const Field* old_field = class->fields;
1542         while( old_field ) {
1543
1544                 // Compare the ids
1545                 if( !strcmp( (char*) old_field->name, (char*) new_field->name ) ) {
1546                         printf( "Duplicate field name \"%s\" in class \"%s\"\n",
1547                                 (char*) new_field->name, id );
1548                         dup_name = 1;
1549                         rc = 1;
1550                         break;
1551                 }
1552
1553                 // Compare the labels. if they're both non-empty
1554                 if( old_field->label && *old_field->label
1555                  && new_field->label && *new_field->label
1556                  && !strcmp( (char*) old_field->label, (char*) new_field->label )) {
1557                         printf( "Duplicate labels \"%s\" in class \"%s\"\n",
1558                                 (char*) old_field->label, id );
1559                         rc = 1;
1560                 }
1561
1562                 old_field = old_field->next;
1563         }
1564
1565         if( dup_name ) {
1566                 free( new_field );
1567         } else {
1568                 new_field->next = class->fields;
1569                 class->fields = new_field;
1570         }
1571
1572         return rc;
1573 }
1574
1575 /**
1576         @brief Add a Link to the Link list of a specified Class (unless it's a duplicate).
1577         @param class Pointer to the Class to whose list to add the Link.
1578         @param id Class id.
1579         @param new_link Pointer to the Link to be added.
1580         @return 0 if successful, or 1 if not (probably due to a duplicate).
1581
1582         If there's already a Link in the list with the same field name, free the new Link
1583         instead of adding it.
1584 */
1585 static int addLink( Class* class, const char* id, Link* new_link ) {
1586         if( ! class || ! new_link )
1587                 return 1;
1588
1589         int rc = 0;
1590         int dup_name = 0;
1591
1592         // See if the class has any other links with the same field
1593         const Link* old_link = class->links;
1594         while( old_link ) {
1595
1596                 if( !strcmp( (char*) old_link->field, (char*) new_link->field ) ) {
1597                         printf( "Duplicate field name \"%s\" in links of class \"%s\"\n",
1598                                 (char*) old_link->field, id );
1599                         rc = 1;
1600                         dup_name = 1;
1601                         break;
1602                 }
1603
1604                 old_link = old_link->next;
1605         }
1606
1607         if( dup_name ) {
1608                 freeLink( new_link );
1609         } else {
1610                 // Add to the linked list
1611                 new_link->next = class->links;
1612                 class->links = new_link;
1613         }
1614
1615         return rc;
1616 }
1617
1618 /**
1619         @brief Create and initialize a new Class.
1620         @param node Pointer to the XML node for a class element.
1621         @return Pointer to the newly created Class.
1622
1623         The calling code is responsible for freeing the Class by calling freeClass().  In practice
1624         this happens automagically when we free the osrfHash classes.
1625 */
1626 static Class* newClass( xmlNodePtr node ) {
1627         Class* class = safe_malloc( sizeof( Class ) );
1628         class->node = node;
1629         class->loaded = 0;
1630         class->is_virtual = 0;
1631         xmlFree( class->primary );
1632         class->fields = NULL;
1633         class->links = NULL;
1634         return class;
1635 }
1636
1637 /**
1638         @brief Free a Class and everything it owns.
1639         @param key The class id (not used).
1640         @param p A pointer to the Class to be freed, cast to a void pointer.
1641
1642         This function is designed to be a freeItem callback for an osrfHash.
1643 */
1644 static void freeClass( char* key, void* p ) {
1645         Class* class = p;
1646
1647         // Free the linked list of Fields
1648         Field* next_field = NULL;
1649         Field* field = class->fields;
1650         while( field ) {
1651                 next_field = field->next;
1652                 freeField( field );
1653                 field = next_field;
1654         }
1655
1656         // Free the linked list of Links
1657         Link* next_link = NULL;
1658         Link* link = class->links;
1659         while( link ) {
1660                 next_link = link->next;
1661                 freeLink( link );
1662                 link = next_link;
1663         }
1664
1665         free( class );
1666 }
1667
1668 /**
1669         @brief Allocate and initialize a Field.
1670         @param name Field name.
1671         @return Pointer to a new Field.
1672
1673         It is the responsibility of the caller to free the Field by calling freeField().
1674 */
1675 static Field* newField( xmlChar* name ) {
1676         Field* field = safe_malloc( sizeof( Field ) );
1677         field->next         = NULL;
1678         field->name         = name;
1679         field->is_virtual   = 0;
1680         field->label        = NULL;
1681         field->datatype     = DT_NONE;
1682         return field;
1683 }
1684
1685 /**
1686         @brief Free a Field and everything in it.
1687         @param field Pointer to the Field to be freed.
1688 */
1689 static void freeField( Field* field ) {
1690         if( field ) {
1691                 xmlFree( field->name );
1692                 if( field->label )
1693                         xmlFree( field->label );
1694                 free( field );
1695         }
1696 }
1697
1698 /**
1699         @brief Allocate and initialize a Link.
1700         @param field Field name.
1701         @return Pointer to a new Link.
1702
1703         It is the responsibility of the caller to free the Link by calling freeLink().
1704 */
1705 static Link* newLink( xmlChar* field ) {
1706         Link* link = safe_malloc( sizeof( Link ) );
1707         link->next         = NULL;
1708         link->field        = field;
1709         link->reltype      = RT_NONE;
1710         link->key          = NULL;
1711         link->classref     = NULL;
1712         return link;
1713 }
1714
1715 /**
1716         @brief Free a Link and everything it owns.
1717         @param link Pointer to the Link to be freed.
1718 */
1719 static void freeLink( Link* link ) {
1720         if( link ) {
1721                 xmlFree( link->field );
1722                 xmlFree( link->key );
1723                 xmlFree( link->classref );
1724                 free( link );
1725         }
1726 }