]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/idlval.c
Recognize new "required" and "validate" attributes for <field> element.
[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\" may need 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 if( !strcmp( attr_name, "validate" )) {
1121                         xmlChar* validate = xmlGetProp( field, (xmlChar*) "validate" );
1122                         if( !*validate ) {
1123                                 // Value should be a regular expression to define a validation rule
1124                                 printf( "Line %ld: Empty value for \"validate\" attribute "
1125                                         "for field \"%s\" in class \"%s\"\n",
1126                                         xmlGetLineNo( field ), (char*) field_name ? : "", id );
1127                                 rc = 1;
1128                         }
1129                         xmlFree( validate );
1130                         // To do: verify that the namespace is oils_obj
1131                 } else if( !strcmp( attr_name, "required" )) {
1132                         xmlChar* required = xmlGetProp( field, (xmlChar*) "required" );
1133                         if( strcmp( (char*) required, "true" ) && strcmp( (char*) required, "false" )) {
1134                                 printf( 
1135                                         "Line %ld: Invalid value \"%s\" for \"required\" attribute "
1136                                         "for field \"%s\" in class \"%s\"\n",
1137                                         xmlGetLineNo( field ), (char*) required,
1138                                         (char*) field_name ? : "", id );
1139                                 rc = 1;
1140                         }
1141                         xmlFree( required );
1142                         // To do: verify that the namespace is oils_obj
1143                 } else {
1144                         printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n",
1145                                 xmlGetLineNo( field ), attr_name, id );
1146                         rc = 1;
1147                 }
1148
1149                 attr = attr->next;
1150         }
1151
1152         if( warn && (!is_virtual) && DT_NONE == datatype ) {
1153                 printf( "Line %ld: WARNING: No datatype attribute for field \"%s\" in class \"%s\"\n",
1154                         xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1155         }
1156
1157         if( ! field_name ) {
1158                 printf( "Line %ld: No name attribute for <field> element in class \"%s\"\n",
1159                         xmlGetLineNo( field ), id );
1160                 rc = 1;
1161         } else if( '\0' == *field_name ) {
1162                 printf( "Line %ld: Field name is empty for <field> element in class \"%s\"\n",
1163                         xmlGetLineNo( field ), id );
1164                 rc = 1;
1165         } else {
1166                 // Add to the class's field list
1167                 Field* new_field = newField( field_name );
1168                 new_field->is_virtual = is_virtual;
1169                 new_field->label = label;
1170                 new_field->datatype = datatype;
1171                 if( addField( class, id, new_field ) )
1172                         rc = 1;
1173         }
1174
1175         return rc;
1176 }
1177
1178 /**
1179         @brief Translate a datatype string into a Dataype (an enum).
1180         @param value The value of a datatype attribute.
1181         @return The datatype in the form of an enum.
1182 */
1183 static Datatype translate_datatype( const xmlChar* value ) {
1184         const char* val = (const char*) value;
1185         Datatype type;
1186
1187         if( !value || !*value )
1188                 type = DT_NONE;
1189         else if( !strcmp( val, "bool" ) )
1190                 type = DT_BOOL;
1191         else if( !strcmp( val, "float" ) )
1192                 type = DT_FLOAT;
1193         else if( !strcmp( val, "id" ) )
1194                 type = DT_ID;
1195         else if( !strcmp( val, "int" ) )
1196                 type = DT_INT;
1197         else if( !strcmp( val, "interval" ) )
1198                 type = DT_INTERVAL;
1199         else if( !strcmp( val, "link" ) )
1200                 type = DT_LINK;
1201         else if( !strcmp( val, "money" ) )
1202                 type = DT_MONEY;
1203         else if( !strcmp( val, "number" ) )
1204                 type = DT_NUMBER;
1205         else if( !strcmp( val, "org_unit" ) )
1206                 type = DT_ORG_UNIT;
1207         else if( !strcmp( val, "text" ) )
1208                 type = DT_TEXT;
1209         else if( !strcmp( val, "timestamp" ) )
1210                 type = DT_TIMESTAMP;
1211         else
1212                 type = DT_INVALID;
1213
1214         return type;
1215 }
1216
1217 /**
1218         @brief Validate a links element.
1219         @param class Pointer to the current Class.
1220         @param id Id of the current Class.
1221         @param links Pointer to the XML node for the links element.
1222         @return 1 if errors found, or 0 if not.
1223
1224         Rules:
1225         - No elements other than "link" are allowed.
1226         - Text is not allowed, other than white space.
1227         - Comments are allowed (and ignored).
1228
1229         Warnings:
1230         - There is usually at least one link element.
1231 */
1232 static int val_links( Class* class, const char* id, xmlNodePtr links ) {
1233         int rc = 0;
1234         int link_found = 0;    // boolean
1235
1236         xmlNodePtr child = links->children;
1237         while( child ) {
1238                 const char* child_name = (char*) child->name;
1239                 if( xmlNodeIsText( child ) ) {
1240                         if( ! xmlIsBlankNode( child ) ) {
1241                                 // Found unexpected text.  After removing leading and
1242                                 // trailing white space, complain about it.
1243                                 xmlChar* content = xmlNodeGetContent( child );
1244
1245                                 xmlChar* begin = content;
1246                                 while( *begin && isspace( *begin ) )
1247                                         ++begin;
1248                                 if( *begin ) {
1249                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1250                                         while( (isspace( *end ) ) )
1251                                                 --end;
1252                                         end[ 1 ] = '\0';
1253                                 }
1254
1255                                 printf( "Unexpected text in <links> element of class \"%s\": \"%s\"\n", id,
1256                                         (char*) begin );
1257                                 xmlFree( content );
1258                         }
1259                 } else if( ! strcmp( child_name, "link" ) ) {
1260                         link_found = 1;
1261                         if( val_one_link( class, id, child ) )
1262                                 rc = 1;
1263                 } else if( !strcmp( child_name, "comment" ) )
1264                         ;  // ignore comment
1265                 else {
1266                         printf( "Line %ld: Unexpected <%s> element in <link> of class \"%s\"\n",
1267                                 xmlGetLineNo( child ), child_name, id );
1268                                 rc = 1;
1269                 }
1270                 child = child->next;
1271         }
1272
1273         if( warn && !link_found ) {
1274                 printf( "WARNING: No <link> element in class \"%s\"\n", id );
1275         }
1276
1277         return rc;
1278 }
1279
1280 /**
1281                 @brief Validate one link element.
1282                 @param class Pointer to the current Class.
1283                 @param id Id of the current Class.
1284                 @param link Pointer to the XML node for the link element.
1285                 @return 1 if errors found, or 0 if not.
1286
1287         Rules:
1288         - The only allowed attributes are "field", "reltype", "key", "map", and "class".
1289         - Except for map, every attribute is required.
1290         - Except for map, every attribute must have a non-empty value.
1291         - The value of the reltype attribute must be one of "has_a", "might_have", or "has_many".
1292 */
1293 static int val_one_link( Class* class, const char* id, xmlNodePtr link ) {
1294         int rc = 0;
1295         xmlChar* field_name = NULL;
1296         Reltype reltype = RT_NONE;
1297         xmlChar* key = NULL;
1298         xmlChar* classref = NULL;
1299
1300         // Traverse the attributes
1301         xmlAttrPtr attr = link->properties;
1302         while( attr ) {
1303                 const char* attr_name = (const char*) attr->name;
1304                 if( !strcmp( attr_name, "field" ) ) {
1305                         field_name = xmlGetProp( link, (xmlChar*) "field" );
1306                 } else if (!strcmp( attr_name, "reltype" ) ) {
1307                         ;
1308                         xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" );
1309                         if( *rt ) {
1310                                 reltype = translate_reltype( rt );
1311                                 if( RT_INVALID == reltype ) {
1312                                         printf(
1313                                                 "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n",
1314                                                 xmlGetLineNo( link ), (char*) rt, id );
1315                                         rc = 1;
1316                                 }
1317                         } else {
1318                                 printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n",
1319                                         xmlGetLineNo( link ), id );
1320                                 rc = 1;
1321                         }
1322                         xmlFree( rt );
1323                 } else if (!strcmp( attr_name, "key" ) ) {
1324                         key = xmlGetProp( link, (xmlChar*) "key" );
1325                 } else if (!strcmp( attr_name, "map" ) ) {
1326                         ;   // ignore for now
1327                 } else if (!strcmp( attr_name, "class" ) ) {
1328                         classref = xmlGetProp( link, (xmlChar*) "class" );
1329                 } else {
1330                         printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n",
1331                                 xmlGetLineNo( link ), attr_name, id );
1332                         rc = 1;
1333                 }
1334                 attr = attr->next;
1335         }
1336
1337         if( !field_name ) {
1338                 printf( "Line %ld: No field attribute found in <link> in class \"%s\"\n",
1339                         xmlGetLineNo( link ), id );
1340                 rc = 1;
1341         } else if( '\0' == *field_name ) {
1342                 printf( "Line %ld: Field name is empty for <link> element in class \"%s\"\n",
1343                         xmlGetLineNo( link ), id );
1344                 rc = 1;
1345         } else if( !reltype ) {
1346                 printf( "Line %ld: No reltype attribute found in <link> in class \"%s\"\n",
1347                         xmlGetLineNo( link ), id );
1348                 rc = 1;
1349         } else if( !key ) {
1350                 printf( "Line %ld: No key attribute found in <link> in class \"%s\"\n",
1351                                 xmlGetLineNo( link ), id );
1352                 rc = 1;
1353         } else if( '\0' == *key ) {
1354                 printf( "Line %ld: key attribute is empty for <link> element in class \"%s\"\n",
1355                         xmlGetLineNo( link ), id );
1356                 rc = 1;
1357         } else if( !classref ) {
1358                 printf( "Line %ld: No class attribute found in <link> in class \"%s\"\n",
1359                          xmlGetLineNo( link ), id );
1360                 rc = 1;
1361         } else if( '\0' == *classref ) {
1362                 printf( "Line %ld: class attribute is empty for <link> element in class \"%s\"\n",
1363                         xmlGetLineNo( link ), id );
1364                 rc = 1;
1365         } else {
1366                 // Add to Link list
1367                 Link* new_link = newLink( field_name );
1368                 new_link->reltype = reltype;
1369                 new_link->key = key;
1370                 new_link->classref = classref;
1371                 if( addLink( class, id, new_link ) )
1372                         rc = 1;
1373         }
1374
1375         return rc;
1376 }
1377
1378 /**
1379         @brief Translate an attribute value into a Reltype (an enum).
1380         @param value The value of a reltype attribute.
1381         @return The value of the attribute translated into the enum Reltype.
1382 */
1383 static Reltype translate_reltype( const xmlChar* value ) {
1384         const char* val = (char*) value;
1385         Reltype reltype;
1386
1387         if( !val || !*val )
1388                 reltype = RT_NONE;
1389         else if( !strcmp( val, "has_a" ) )
1390                 reltype = RT_HAS_A;
1391         else if( !strcmp( val, "might_have" ) )
1392                 reltype = RT_MIGHT_HAVE;
1393         else if( !strcmp( val, "has_many" ) )
1394                 reltype = RT_HAS_MANY;
1395         else
1396                 reltype = RT_INVALID;
1397
1398         return reltype;
1399 }
1400
1401 /**
1402         @brief Build a list of classes, while checking for several errors.
1403         @param doc Pointer to the xmlDoc loaded from the IDL.
1404         @return 1 if errors found, or 0 if not.
1405
1406         Rules:
1407         - Every child element of the root must be of the element "class".
1408         - No text is allowed, other than white space, between classes.
1409         - Comments are allowed (and ignored) between classes.
1410 */
1411 static int scan_idl( xmlDocPtr doc ) {
1412         int rc = 0;
1413
1414         xmlNodePtr child = xmlDocGetRootElement( doc )->children;
1415         while( child ) {
1416                 char* child_name = (char*) child->name;
1417                 if( xmlNodeIsText( child ) ) {
1418                         if( ! xmlIsBlankNode( child ) ) {
1419                                 // Found unexpected text.  After removing leading and
1420                                 // trailing white space, complain about it.
1421                                 xmlChar* content = xmlNodeGetContent( child );
1422
1423                                 xmlChar* begin = content;
1424                                 while( *begin && isspace( *begin ) )
1425                                         ++begin;
1426                                 if( *begin ) {
1427                                         xmlChar* end = begin + strlen( (char*) begin ) - 1;
1428                                         while( (isspace( *end ) ) )
1429                                                 --end;
1430                                         end[ 1 ] = '\0';
1431                                 }
1432
1433                                 printf( "Unexpected text between class elements: \"%s\"\n",
1434                                         (char*) begin );
1435                                 xmlFree( content );
1436                         }
1437                 } else if( !strcmp( child_name, "class" ) ) {
1438                         if( register_class( child ) )
1439                                 rc = 1;
1440                 } else if( !strcmp( child_name, "comment" ) )
1441                         ;  // ignore comment
1442                 else {
1443                         printf( "Line %ld: Unexpected <%s> element under root\n",
1444                                 xmlGetLineNo( child ), child_name );
1445                         rc = 1;
1446                 }
1447
1448                 child = child->next;
1449         }
1450         return rc;
1451 }
1452
1453 /**
1454         @brief Register a class.
1455         @param class Pointer to the class node.
1456         @return 1 if errors found, or 0 if not.
1457
1458         Rules:
1459         - Every class element must have an "id" attribute.
1460         - A class id must not be an empty string.
1461         - Every class id must be unique.
1462
1463         Warnings:
1464         - A class id normally consists entirely of lower case letters, digits and underscores.
1465         - A class id longer than 12 characters is suspiciously long.
1466 */
1467 static int register_class( xmlNodePtr class ) {
1468         int rc = 0;
1469         xmlChar* id = xmlGetProp( class, (xmlChar*) "id" );
1470
1471         if( ! id ) {
1472                 printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) );
1473                 rc = 1;
1474         } else if( ! *id ) {
1475                 printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) );
1476                 rc = 1;
1477         } else {
1478
1479                 // In principle a class id could contain any arbitrary characters, but in practice
1480                 // anything but lower case, digits, and underscores is probably a mistake.
1481                 const xmlChar* p = id;
1482                 while( *p ) {
1483                         if( islower( *p ) || isdigit( *p ) || '_' == *p )
1484                                 ++p;
1485                         else if( warn ) {
1486                                 printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, "
1487                                                 "digits, and underscores\n", xmlGetLineNo( class ), (char*) id );
1488                                 break;
1489                         }
1490                 }
1491
1492                 // Warn about a suspiciously long id
1493                 if( warn && strlen( (char*) id ) > 12 ) {
1494                         printf( "Line %ld: WARNING: Class id is unusually long: \"%s\"\n",
1495                                 xmlGetLineNo( class ), (char*) id );
1496                 }
1497
1498                 // Add the classname to the list of classes.  If the size of
1499                 // the list doesn't change, then we must have a duplicate.
1500                 Class* entry = newClass( class );
1501                 unsigned long class_count = osrfHashGetCount( classes );
1502                 osrfHashSet( classes, entry, (char*) id );
1503                 if( osrfHashGetCount( classes ) == class_count ) {
1504                         printf( "Line %ld: Duplicate class name \"%s\"\n",
1505                                 xmlGetLineNo( class ), (char*) id );
1506                         rc = 1;
1507                 }
1508                 xmlFree( id );
1509         }
1510         return rc;
1511 }
1512
1513 /**
1514         @brief Add a field to a class's field list (unless the id collides with an earlier entry).
1515         @param class Pointer to the current class.
1516         @param id The class id.
1517         @param new_field Pointer to the Field to be added.
1518         @return 0 if successful, or 1 if not (probably due to a duplicate key).
1519
1520         If the id collides with a previous entry, we free the new Field instead of adding it
1521         to the list.  If the label collides with a previous entry, we complain, but we go
1522         ahead and add the Field to the list.
1523
1524         RULES:
1525         - Each field name should be unique within the fields element.
1526         - Each label should be unique within the fields element.
1527 */
1528 static int addField( Class* class, const char* id, Field* new_field ) {
1529         if( ! class || ! new_field )
1530                 return 1;
1531
1532         int rc = 0;
1533         int dup_name = 0;
1534
1535         // See if the class has any other fields with the same name or label.
1536         const Field* old_field = class->fields;
1537         while( old_field ) {
1538
1539                 // Compare the ids
1540                 if( !strcmp( (char*) old_field->name, (char*) new_field->name ) ) {
1541                         printf( "Duplicate field name \"%s\" in class \"%s\"\n",
1542                                 (char*) new_field->name, id );
1543                         dup_name = 1;
1544                         rc = 1;
1545                         break;
1546                 }
1547
1548                 // Compare the labels. if they're both non-empty
1549                 if( old_field->label && *old_field->label
1550                  && new_field->label && *new_field->label
1551                  && !strcmp( (char*) old_field->label, (char*) new_field->label )) {
1552                         printf( "Duplicate labels \"%s\" in class \"%s\"\n",
1553                                 (char*) old_field->label, id );
1554                         rc = 1;
1555                 }
1556
1557                 old_field = old_field->next;
1558         }
1559
1560         if( dup_name ) {
1561                 free( new_field );
1562         } else {
1563                 new_field->next = class->fields;
1564                 class->fields = new_field;
1565         }
1566
1567         return rc;
1568 }
1569
1570 /**
1571         @brief Add a Link to the Link list of a specified Class (unless it's a duplicate).
1572         @param class Pointer to the Class to whose list to add the Link.
1573         @param id Class id.
1574         @param new_link Pointer to the Link to be added.
1575         @return 0 if successful, or 1 if not (probably due to a duplicate).
1576
1577         If there's already a Link in the list with the same field name, free the new Link
1578         instead of adding it.
1579 */
1580 static int addLink( Class* class, const char* id, Link* new_link ) {
1581         if( ! class || ! new_link )
1582                 return 1;
1583
1584         int rc = 0;
1585         int dup_name = 0;
1586
1587         // See if the class has any other links with the same field
1588         const Link* old_link = class->links;
1589         while( old_link ) {
1590
1591                 if( !strcmp( (char*) old_link->field, (char*) new_link->field ) ) {
1592                         printf( "Duplicate field name \"%s\" in links of class \"%s\"\n",
1593                                 (char*) old_link->field, id );
1594                         rc = 1;
1595                         dup_name = 1;
1596                         break;
1597                 }
1598
1599                 old_link = old_link->next;
1600         }
1601
1602         if( dup_name ) {
1603                 freeLink( new_link );
1604         } else {
1605                 // Add to the linked list
1606                 new_link->next = class->links;
1607                 class->links = new_link;
1608         }
1609
1610         return rc;
1611 }
1612
1613 /**
1614         @brief Create and initialize a new Class.
1615         @param node Pointer to the XML node for a class element.
1616         @return Pointer to the newly created Class.
1617
1618         The calling code is responsible for freeing the Class by calling freeClass().  In practice
1619         this happens automagically when we free the osrfHash classes.
1620 */
1621 static Class* newClass( xmlNodePtr node ) {
1622         Class* class = safe_malloc( sizeof( Class ) );
1623         class->node = node;
1624         class->loaded = 0;
1625         class->is_virtual = 0;
1626         xmlFree( class->primary );
1627         class->fields = NULL;
1628         class->links = NULL;
1629         return class;
1630 }
1631
1632 /**
1633         @brief Free a Class and everything it owns.
1634         @param key The class id (not used).
1635         @param p A pointer to the Class to be freed, cast to a void pointer.
1636
1637         This function is designed to be a freeItem callback for an osrfHash.
1638 */
1639 static void freeClass( char* key, void* p ) {
1640         Class* class = p;
1641
1642         // Free the linked list of Fields
1643         Field* next_field = NULL;
1644         Field* field = class->fields;
1645         while( field ) {
1646                 next_field = field->next;
1647                 freeField( field );
1648                 field = next_field;
1649         }
1650
1651         // Free the linked list of Links
1652         Link* next_link = NULL;
1653         Link* link = class->links;
1654         while( link ) {
1655                 next_link = link->next;
1656                 freeLink( link );
1657                 link = next_link;
1658         }
1659
1660         free( class );
1661 }
1662
1663 /**
1664         @brief Allocate and initialize a Field.
1665         @param name Field name.
1666         @return Pointer to a new Field.
1667
1668         It is the responsibility of the caller to free the Field by calling freeField().
1669 */
1670 static Field* newField( xmlChar* name ) {
1671         Field* field = safe_malloc( sizeof( Field ) );
1672         field->next         = NULL;
1673         field->name         = name;
1674         field->is_virtual   = 0;
1675         field->label        = NULL;
1676         field->datatype     = DT_NONE;
1677         return field;
1678 }
1679
1680 /**
1681         @brief Free a Field and everything in it.
1682         @param field Pointer to the Field to be freed.
1683 */
1684 static void freeField( Field* field ) {
1685         if( field ) {
1686                 xmlFree( field->name );
1687                 if( field->label )
1688                         xmlFree( field->label );
1689                 free( field );
1690         }
1691 }
1692
1693 /**
1694         @brief Allocate and initialize a Link.
1695         @param field Field name.
1696         @return Pointer to a new Link.
1697
1698         It is the responsibility of the caller to free the Link by calling freeLink().
1699 */
1700 static Link* newLink( xmlChar* field ) {
1701         Link* link = safe_malloc( sizeof( Link ) );
1702         link->next         = NULL;
1703         link->field        = field;
1704         link->reltype      = RT_NONE;
1705         link->key          = NULL;
1706         link->classref     = NULL;
1707         return link;
1708 }
1709
1710 /**
1711         @brief Free a Link and everything it owns.
1712         @param link Pointer to the Link to be freed.
1713 */
1714 static void freeLink( Link* link ) {
1715         if( link ) {
1716                 xmlFree( link->field );
1717                 xmlFree( link->key );
1718                 xmlFree( link->classref );
1719                 free( link );
1720         }
1721 }