3 @brief Validator for IDL files.
7 Copyright (C) 2009 Georgia Public Library Service
8 Scott McKellar <scott@esilibrary.com>
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.
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.
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>
33 #include "osrf_hash.h"
35 /* Represents the command line */
41 int idl_file_name_found;
44 typedef struct Opts Opts;
46 /* datatype attribute of <field> element */
63 /* Represents a <Field> aggregate */
65 struct Field_struct* next;
67 int is_virtual; // boolean
71 typedef struct Field_struct Field;
73 /* reltype attribute of <link> element */
82 /* Represents a <link> element */
84 struct Link_struct* next;
90 typedef struct Link_struct Link;
92 /* Represents a <class> aggregate */
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
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 );
132 /* Stores an in-memory representation of the IDL */
133 static osrfHash* classes = NULL;
135 static int warn = 0; // boolean; true if -w present on command line
137 int main( int argc, char* argv[] ) {
139 // Examine command line
141 if( get_Opts( argc, argv, &opts ) )
144 const char* IDL_filename = NULL;
145 if( opts.idl_file_name_found )
146 IDL_filename = opts.idl_file_name;
148 IDL_filename = getenv( "OILS_IDL_FILENAME" );
150 IDL_filename = "/openils/conf/fm_IDL.xml";
158 xmlLineNumbersDefault(1);
159 xmlDocPtr doc = xmlReadFile( IDL_filename, NULL, XML_PARSE_XINCLUDE );
161 fprintf( stderr, "Could not load or parse the IDL XML file %s\n", IDL_filename );
164 printf( "Validating: %s\n", IDL_filename );
165 classes = osrfNewHash();
166 osrfHashSetCallback( classes, freeClass );
169 if( scan_idl( doc ) )
172 if( opts.new_argc < 2 ) {
174 // No classes specified: validate all classes
179 // Validate one or more specified classes
181 while( i < opts.new_argc ) {
182 const char* classname = opts.new_argv[ i ];
183 Class* class = osrfHashGet( classes, classname );
185 printf( "Class \"%s\" does not exist\n", classname );
188 // Validate the class in isolation
189 if( val_class( class, classname ) )
191 // Cross-validate with linked classes
192 if( cross_validate_classes( class, classname ) )
198 osrfHashFree( classes );
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
212 static int get_Opts( int argc, char * argv[], Opts * pOpts ) {
213 int rc = 0; /* return code */
216 /* Define valid option characters */
218 const char optstring[] = ":f:w";
220 /* Initialize members of struct */
223 pOpts->new_argv = NULL;
225 pOpts->idl_file_name_found = 0;
226 pOpts->idl_file_name = NULL;
229 /* Suppress error messages from getopt() */
233 /* Examine command line options */
235 while( ( opt = getopt( argc, argv, optstring ) ) != -1 ) {
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" );
243 pOpts->idl_file_name_found = 1;
245 pOpts->idl_file_name = optarg;
247 case 'w' : /* Get warning */
250 case ':' : /* Missing argument */
251 fprintf( stderr, "Required argument missing on -%c option\n",
255 case '?' : /* Invalid option */
256 fprintf( stderr, "Invalid option '-%c' on command line\n",
260 default : /* Programmer error */
261 fprintf( stderr, "Internal error: unexpected value '-%c'"
262 "for optopt", (char) optopt );
268 if( optind > argc ) {
269 /* This should never happen! */
271 fprintf( stderr, "Program error: found more arguments than expected\n" );
274 /* Calculate new_argcv and new_argc to reflect */
275 /* the number of arguments consumed */
277 pOpts->new_argc = argc - optind + 1;
278 pOpts->new_argv = argv + optind - 1;
285 @brief Validate all classes.
286 @return 1 if errors found, or 0 if not.
288 Traverse the class list and validate each class in turn.
290 static int val_idl( void ) {
292 osrfHashIterator* itr = osrfNewHashIterator( classes );
296 while( (class = osrfHashIteratorNext( itr )) ) {
297 const char* id = osrfHashIteratorKey( itr );
298 if( val_class( class, id ) ) // validate class separately
300 if( cross_validate_classes( class, id ) ) // cross-validate with linked classes
304 osrfHashIteratorFree( itr );
309 @brief Make sure that every linkage appropriately matches the linked class.
310 @param class Pointer to the current Class.
312 @return 1 if errors found, or 0 if not.
314 static int cross_validate_classes( Class* class, const char* id ) {
316 Link* link = class->links;
318 if( cross_validate_linkage( class, id, link ) )
327 @brief Make sure that a linkage appropriately matches the linked class.
328 @param class Pointer to the current class.
330 @param link Pointer to the link being validated.
331 @return 1 if errors found, or 0 if not.
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".
339 It is not an error if the linkage is not reciprocated.
341 static int cross_validate_linkage( Class* class, const char*id, Link* link ) {
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 );
349 // Make sure the other class is loaded before we look at it further
350 if( val_class( other_class, (char*) link->classref ) )
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
361 other_link = other_link->next;
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 );
374 // The link is reciprocated. Make sure that exactly one of the links
375 // has a reltype of "has_many"
377 if( RT_HAS_MANY == link->reltype )
379 if( RT_HAS_MANY == other_link->reltype )
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 );
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 );
398 @brief Validate a single class.
400 @param class Pointer to the XML node for the class element.
401 @return 1 if errors found, or 0 if not.
403 We have already validated the id.
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).
412 static int val_class( Class* class, const char* id ) {
415 else if( class->loaded )
416 return 0; // We've already validated this one locally
420 if( val_class_attributes( class, id ) )
423 xmlNodePtr fields = NULL;
424 xmlNodePtr links = NULL;
425 xmlNodePtr permacrud = NULL;
426 xmlNodePtr src_def = NULL;
428 // Examine every child element of the <class> element.
429 xmlNodePtr child = class->node->children;
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 );
438 xmlChar* begin = content;
439 while( *begin && isspace( *begin ) )
442 xmlChar* end = begin + strlen( (char*) begin ) - 1;
443 while( (isspace( *end ) ) )
448 printf( "Unexpected text in class \"%s\": \"%s\"\n", id,
452 } else if( !strcmp( child_name, "fields" ) ) {
454 printf( "Multiple <fields> elements in class \"%s\"\n", id );
458 // Identify the primary key, if any
459 class->primary = xmlGetProp( fields, (xmlChar*) "primary" );
460 if( val_fields( class, id, fields ) )
463 } else if( !strcmp( child_name, "links" ) ) {
465 printf( "Multiple <links> elements in class \"%s\"\n", id );
469 if( val_links( class, id, links ) )
472 } else if( !strcmp( child_name, "permacrud" ) ) {
474 printf( "Multiple <permacrud> elements in class \"%s\"\n", id );
479 } else if( !strcmp( child_name, "source_definition" ) ) {
481 printf( "Multiple <source_definition> elements in class \"%s\"\n", id );
484 // To do: verify that there is nothing in <source_definition> except text and
485 // comments, and that the text is non-empty.
488 } else if( !strcmp( child_name, "comment" ) )
491 printf( "Line %ld: Unexpected <%s> element in class \"%s\"\n",
492 xmlGetLineNo( child ), child_name, id );
499 if( check_labels( class, id ) )
501 if( val_fields_attributes( class, id, fields ) )
504 printf( "No <fields> element in class \"%s\"\n", id );
508 if( val_links_to_fields( class, id ) )
511 if( val_fields_to_links( class, id ) )
519 @brief Validate the class attributes.
520 @param class Pointer to the current Class.
522 @return if errors found, or 0 if not.
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
532 - A virtual class must not have a tablename attribute.
534 static int val_class_attributes( Class* class, const char* id ) {
537 int controller_found = 0; // boolean
538 int fieldmapper_found = 0; // boolean
539 int tablename_found = 0; // boolean
541 xmlAttrPtr attr = class->node->properties;
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 );
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 );
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 );
572 } else if( !strcmp( (char*) attr_name, "tablename" ) ) {
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 );
581 } else if( !strcmp( (char*) attr_name, "virtual" ) ) {
582 xmlChar* virtual_str = xmlGetProp( class->node, (xmlChar*) "virtual" );
584 if( !strcmp( (char*) virtual_str, "true" ) ) {
585 class->is_virtual = 1;
586 } else if( strcmp( (char*) virtual_str, "false" ) ) {
588 "Line %ld: Invalid value \"%s\" for virtual attribute of class\"%s\"\n",
589 xmlGetLineNo( class->node ), (char*) virtual_str, id );
592 xmlFree( virtual_str );
594 } else if( !strcmp( (char*) attr_name, "readonly" ) ) {
595 xmlChar* readonly = xmlGetProp( class->node, (xmlChar*) "readonly" );
597 if( strcmp( (char*) readonly, "true" )
598 && strcmp( (char*) readonly, "false" ) ) {
600 "Line %ld: Invalid value \"%s\" for readonly attribute of class\"%s\"\n",
601 xmlGetLineNo( class->node ), (char*) readonly, id );
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 );
614 } else if( !strcmp( (char*) attr_name, "core" ) ) {
615 xmlChar* core = xmlGetProp( class->node, (xmlChar*) "core" );
617 if( strcmp( (char*) core, "true" )
618 && strcmp( (char*) core, "false" ) ) {
620 "Line %ld: Invalid value \"%s\" for core attribute of class\"%s\"\n",
621 xmlGetLineNo( class->node ), (char*) core, id );
626 } else if( !strcmp( (char*) attr_name, "field_safe" ) ) {
627 xmlChar* field_safe = xmlGetProp( class->node, (xmlChar*) "field_safe" );
629 if( strcmp( (char*) field_safe, "true" )
630 && strcmp( (char*) field_safe, "false" ) ) {
632 "Line %ld: Invalid value \"%s\" for field_safe attribute of class\"%s\"\n",
633 xmlGetLineNo( class->node ), (char*) field_safe, id );
636 xmlFree( field_safe );
639 printf( "Line %ld: Unrecognized class attribute \"%s\" in class \"%s\"\n",
640 xmlGetLineNo( class->node ), attr_name, id );
646 if( ! controller_found ) {
647 printf( "Line %ld: No controller attribute for class \"%s\"\n",
648 xmlGetLineNo( class->node ), id );
652 if( ! fieldmapper_found ) {
653 printf( "Line %ld: No fieldmapper attribute for class \"\%s\"\n",
654 xmlGetLineNo( class->node ), id );
658 if( class->is_virtual && tablename_found ) {
659 printf( "Line %ld: Virtual class \"%s\" shouldn't have a tablename",
660 xmlGetLineNo( class->node ), id );
668 @brief Determine whether fields are either all labeled or all unlabeled.
669 @param class Pointer to the current Class.
671 @return 1 if errors found, or 0 if not.
674 - The fields for a given class must either all be labeled or all unlabeled.
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.
679 static int check_labels( const Class* class, const char* id ) {
682 int label_found = 0; // boolean
683 int unlabel_found = 0; // boolean
685 Field* field = class->fields;
694 if( label_found && unlabel_found ) {
695 printf( "Class \"%s\" has a mixture of labeled and unlabeled fields\n", id );
703 @brief Validate the fields attributes.
704 @param class Pointer to the current Class.
706 @param fields Pointer to the XML node for the fields element.
707 @return if errors found, or 0 if not.
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
718 static int val_fields_attributes( Class* class, const char* id, xmlNodePtr fields ) {
721 xmlChar* sequence = NULL;
722 xmlChar* primary = NULL;
724 // Traverse the attributes
725 xmlAttrPtr attr = fields->properties;
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] ) {
732 "Line %ld: value of primary attribute is an empty string for class \"%s\"\n",
733 xmlGetLineNo( fields ), id );
736 } else if( !strcmp( attr_name, "sequence" ) ) {
737 sequence = xmlGetProp( fields, (xmlChar*) "sequence" );
738 if( '\0' == sequence[0] ) {
740 "Line %ld: value of sequence attribute is an empty string for class \"%s\"\n",
741 xmlGetLineNo( fields ), id );
745 printf( "Line %ld: Unexpected fields attribute \"%s\" in class \"%s\"\n",
746 xmlGetLineNo( fields ), attr_name, id );
753 if( sequence && ! primary ) {
754 printf( "Line %ld: class \"%s\" has a sequence identified but no primary key\n",
755 xmlGetLineNo( fields ), id );
760 // look for the primary key
761 Field* field = class->fields;
763 if( !strcmp( (char*) field->name, (char*) primary ) )
768 printf( "Primary key field \"%s\" does not exist for class \"%s\"\n",
769 (char*) primary, id );
771 } else if( DT_ID == field->datatype && ! sequence && ! class->is_virtual ) {
773 "Line %ld: Primary key is an id; class \"%s\" should have a sequence attribute\n",
774 xmlGetLineNo( fields ), id );
776 } else if( DT_ID != field->datatype
777 && DT_INT != field->datatype
778 && DT_ORG_UNIT != field->datatype
781 "Line %ld: Datatype of key for class \"%s\" does not allow a sequence attribute\n",
782 xmlGetLineNo( fields ), id );
793 @brief Verify that every Link has a matching Field for a given Class.
794 @param class Pointer to the current class.
796 @return 1 if errors found, or 0 if not.
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
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.
805 static int val_links_to_fields( const Class* class, const char* id ) {
811 const Link* link = class->links;
813 if( link->field && *link->field ) {
814 const Field* field = searchFieldByName( class, link->field );
816 if( compareFieldAndLink( class, id, field, link ) )
819 printf( "\"%s\" class has no <field> corresponding to <link> for \"%s\"\n",
820 id, (char*) link->field );
831 @brief Compare matching field and link elements to see if they are compatible
832 @param class Pointer to the current Class.
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.
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".
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".
849 static int compareFieldAndLink( const Class* class, const char* id,
850 const Field* field, const Link* link ) {
853 Datatype datatype = field->datatype;
854 const char* classref = (const char*) link->classref;
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 );
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 );
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 );
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 );
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 );
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 );
899 printf( "In class \"%s\": \"%s\" field should have a datatype \"link\"\n",
900 id, (char*) field->name );
909 @brief See if every linked field has a counterpart in the links aggregate.
910 @param class Pointer to the current class.
912 @return 1 if errors found, or 0 if not.
915 - If a field has a datatype of "link" or "org_unit, there must be a corresponding
916 entry in the links aggregate.
918 static int val_fields_to_links( const Class* class, const char* id ) {
920 const Field* field = class->fields;
922 if( DT_LINK != field->datatype && DT_ORG_UNIT != field->datatype ) {
924 continue; // not a link? skip it
926 // See if there's a matching entry in the <links> aggregate
927 const Link* link = class->links;
929 if( !strcmp( (char*) field->name, (char*) link->field ) )
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.
940 printf( "In class \"%s\": Linked field \"%s\" has no matching <link>\n",
941 id, (char*) field->name );
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.
956 static const Field* searchFieldByName( const Class* class, const xmlChar* field_name ) {
957 if( ! class || ! field_name || ! *field_name )
960 const char* name = (const char*) field_name;
961 const Field* field = class->fields;
963 if( field->name && !strcmp( (char*) field->name, name ) )
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.
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).
984 static int val_fields( Class* class, const char* id, xmlNodePtr fields ) {
986 int field_found = 0; // boolean
988 xmlNodePtr child = fields->children;
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 );
997 xmlChar* begin = content;
998 while( *begin && isspace( *begin ) )
1001 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1002 while( (isspace( *end ) ) )
1007 printf( "Unexpected text in <fields> element of class \"%s\": \"%s\"\n", id,
1011 } else if( ! strcmp( child_name, "field" ) ) {
1013 if( val_one_field( class, id, child ) )
1015 } else if( !strcmp( child_name, "comment" ) )
1018 printf( "Line %ld: Unexpected <%s> element in <fields> of class \"%s\"\n",
1019 xmlGetLineNo( child ), child_name, id );
1022 child = child->next;
1025 if( !field_found ) {
1026 printf( "No <field> element in class \"%s\"\n", id );
1034 @brief Validate a field element within a fields element.
1035 @param class Pointer to the current Class.
1037 @param field Pointer to the XML node for the field element.
1038 @return 1 if errors found, or 0 if not.
1041 - id attribute must be present with a non-empty value.
1042 - label attribute, if present, must have a non-empty value.
1043 - virtual attribute, if present, must have a value of "true" or "false".
1045 static int val_one_field( Class* class, const char* id, xmlNodePtr field ) {
1047 xmlChar* label = NULL;
1048 xmlChar* field_name = NULL;
1050 Datatype datatype = DT_NONE;
1052 // Traverse the attributes
1053 xmlAttrPtr attr = field->properties;
1055 const char* attr_name = (char*) attr->name;
1056 if( !strcmp( attr_name, "name" ) ) {
1057 field_name = xmlGetProp( field, (xmlChar*) "name" );
1058 } else if( !strcmp( attr_name, "virtual" ) ) {
1059 xmlChar* virt = xmlGetProp( field, (xmlChar*) "virtual" );
1060 if( !strcmp( (char*) virt, "true" ) )
1062 else if( strcmp( (char*) virt, "false" ) ) {
1063 printf( "Line %ld: Invalid value for virtual attribute: \"%s\"\n",
1064 xmlGetLineNo( field ), (char*) virt );
1068 // To do: verify that the namespace is oils_persist
1069 } else if( !strcmp( attr_name, "label" ) ) {
1070 label = xmlGetProp( field, (xmlChar*) "label" );
1071 if( '\0' == *label ) {
1072 printf( "Line %ld: Empty value for label attribute for class \"%s\"\n",
1073 xmlGetLineNo( field ), id );
1078 // To do: verify that the namespace is reporter
1079 } else if( !strcmp( attr_name, "datatype" ) ) {
1080 xmlChar* dt_str = xmlGetProp( field, (xmlChar*) "datatype" );
1081 datatype = translate_datatype( dt_str );
1082 if( DT_INVALID == datatype ) {
1083 printf( "Line %ld: Invalid datatype \"%s\" in class \"%s\"\n",
1084 xmlGetLineNo( field ), (char*) dt_str, id );
1088 // To do: make sure that the namespace is reporter
1089 } else if( !strcmp( attr_name, "array_position" ) ) {
1090 ; // Ignore for now, but it should be deprecated
1091 } else if( !strcmp( attr_name, "selector" ) ) {
1093 } else if( !strcmp( attr_name, "i18n" ) ) {
1094 xmlChar* i18n = xmlGetProp( field, (xmlChar*) "i18n" );
1095 if( strcmp( (char*) i18n, "true" ) && strcmp( (char*) i18n, "false" ) ) {
1096 printf( "Line %ld: Invalid value for i18n attribute: \"%s\"\n",
1097 xmlGetLineNo( field ), (char*) i18n );
1101 // To do: verify that the namespace is oils_persist
1102 } else if( !strcmp( attr_name, "primitive" ) ) {
1103 xmlChar* primitive = xmlGetProp( field, (xmlChar*) "primitive" );
1104 if( strcmp( (char*) primitive, "string" ) && strcmp( (char*) primitive, "number" ) ) {
1105 printf( "Line %ld: Invalid value for primitive attribute: \"%s\"\n",
1106 xmlGetLineNo( field ), (char*) primitive );
1109 xmlFree( primitive );
1111 printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n",
1112 xmlGetLineNo( field ), attr_name, id );
1119 if( warn && DT_NONE == datatype ) {
1120 printf( "Line %ld: WARNING: No datatype attribute for field \"%s\" in class \"%s\"\n",
1121 xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1124 if( ! field_name ) {
1125 printf( "Line %ld: No name attribute for <field> element in class \"%s\"\n",
1126 xmlGetLineNo( field ), id );
1128 } else if( '\0' == *field_name ) {
1129 printf( "Line %ld: Field name is empty for <field> element in class \"%s\"\n",
1130 xmlGetLineNo( field ), id );
1133 // Add to the class's field list
1134 Field* new_field = newField( field_name );
1135 new_field->is_virtual = is_virtual;
1136 new_field->label = label;
1137 new_field->datatype = datatype;
1138 if( addField( class, id, new_field ) )
1146 @brief Translate a datatype string into a Dataype (an enum).
1147 @param value The value of a datatype attribute.
1148 @return The datatype in the form of an enum.
1150 static Datatype translate_datatype( const xmlChar* value ) {
1151 const char* val = (const char*) value;
1154 if( !value || !*value )
1156 else if( !strcmp( val, "bool" ) )
1158 else if( !strcmp( val, "float" ) )
1160 else if( !strcmp( val, "id" ) )
1162 else if( !strcmp( val, "int" ) )
1164 else if( !strcmp( val, "interval" ) )
1166 else if( !strcmp( val, "link" ) )
1168 else if( !strcmp( val, "money" ) )
1170 else if( !strcmp( val, "number" ) )
1172 else if( !strcmp( val, "org_unit" ) )
1174 else if( !strcmp( val, "text" ) )
1176 else if( !strcmp( val, "timestamp" ) )
1177 type = DT_TIMESTAMP;
1185 @brief Validate a links element.
1186 @param class Pointer to the current Class.
1187 @param id Id of the current Class.
1188 @param links Pointer to the XML node for the links element.
1189 @return 1 if errors found, or 0 if not.
1192 - No elements other than "link" are allowed.
1193 - Text is not allowed, other than white space.
1194 - Comments are allowed (and ignored).
1197 - There is usually at least one link element.
1199 static int val_links( Class* class, const char* id, xmlNodePtr links ) {
1201 int link_found = 0; // boolean
1203 xmlNodePtr child = links->children;
1205 const char* child_name = (char*) child->name;
1206 if( xmlNodeIsText( child ) ) {
1207 if( ! xmlIsBlankNode( child ) ) {
1208 // Found unexpected text. After removing leading and
1209 // trailing white space, complain about it.
1210 xmlChar* content = xmlNodeGetContent( child );
1212 xmlChar* begin = content;
1213 while( *begin && isspace( *begin ) )
1216 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1217 while( (isspace( *end ) ) )
1222 printf( "Unexpected text in <links> element of class \"%s\": \"%s\"\n", id,
1226 } else if( ! strcmp( child_name, "link" ) ) {
1228 if( val_one_link( class, id, child ) )
1230 } else if( !strcmp( child_name, "comment" ) )
1233 printf( "Line %ld: Unexpected <%s> element in <link> of class \"%s\"\n",
1234 xmlGetLineNo( child ), child_name, id );
1237 child = child->next;
1240 if( warn && !link_found ) {
1241 printf( "WARNING: No <link> element in class \"%s\"\n", id );
1248 @brief Validate one link element.
1249 @param class Pointer to the current Class.
1250 @param id Id of the current Class.
1251 @param link Pointer to the XML node for the link element.
1252 @return 1 if errors found, or 0 if not.
1255 - The only allowed attributes are "field", "reltype", "key", "map", and "class".
1256 - Except for map, every attribute is required.
1257 - Except for map, every attribute must have a non-empty value.
1258 - The value of the reltype attribute must be one of "has_a", "might_have", or "has_many".
1260 static int val_one_link( Class* class, const char* id, xmlNodePtr link ) {
1262 xmlChar* field_name = NULL;
1263 Reltype reltype = RT_NONE;
1264 xmlChar* key = NULL;
1265 xmlChar* classref = NULL;
1267 // Traverse the attributes
1268 xmlAttrPtr attr = link->properties;
1270 const char* attr_name = (const char*) attr->name;
1271 if( !strcmp( attr_name, "field" ) ) {
1272 field_name = xmlGetProp( link, (xmlChar*) "field" );
1273 } else if (!strcmp( attr_name, "reltype" ) ) {
1275 xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" );
1277 reltype = translate_reltype( rt );
1278 if( RT_INVALID == reltype ) {
1280 "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n",
1281 xmlGetLineNo( link ), (char*) rt, id );
1285 printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n",
1286 xmlGetLineNo( link ), id );
1290 } else if (!strcmp( attr_name, "key" ) ) {
1291 key = xmlGetProp( link, (xmlChar*) "key" );
1292 } else if (!strcmp( attr_name, "map" ) ) {
1294 } else if (!strcmp( attr_name, "class" ) ) {
1295 classref = xmlGetProp( link, (xmlChar*) "class" );
1297 printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n",
1298 xmlGetLineNo( link ), attr_name, id );
1305 printf( "Line %ld: No field attribute found in <link> in class \"%s\"\n",
1306 xmlGetLineNo( link ), id );
1308 } else if( '\0' == *field_name ) {
1309 printf( "Line %ld: Field name is empty for <link> element in class \"%s\"\n",
1310 xmlGetLineNo( link ), id );
1312 } else if( !reltype ) {
1313 printf( "Line %ld: No reltype attribute found in <link> in class \"%s\"\n",
1314 xmlGetLineNo( link ), id );
1317 printf( "Line %ld: No key attribute found in <link> in class \"%s\"\n",
1318 xmlGetLineNo( link ), id );
1320 } else if( '\0' == *key ) {
1321 printf( "Line %ld: key attribute is empty for <link> element in class \"%s\"\n",
1322 xmlGetLineNo( link ), id );
1324 } else if( !classref ) {
1325 printf( "Line %ld: No class attribute found in <link> in class \"%s\"\n",
1326 xmlGetLineNo( link ), id );
1328 } else if( '\0' == *classref ) {
1329 printf( "Line %ld: class attribute is empty for <link> element in class \"%s\"\n",
1330 xmlGetLineNo( link ), id );
1334 Link* new_link = newLink( field_name );
1335 new_link->reltype = reltype;
1336 new_link->key = key;
1337 new_link->classref = classref;
1338 if( addLink( class, id, new_link ) )
1346 @brief Translate an attribute value into a Reltype (an enum).
1347 @param value The value of a reltype attribute.
1348 @return The value of the attribute translated into the enum Reltype.
1350 static Reltype translate_reltype( const xmlChar* value ) {
1351 const char* val = (char*) value;
1356 else if( !strcmp( val, "has_a" ) )
1358 else if( !strcmp( val, "might_have" ) )
1359 reltype = RT_MIGHT_HAVE;
1360 else if( !strcmp( val, "has_many" ) )
1361 reltype = RT_HAS_MANY;
1363 reltype = RT_INVALID;
1369 @brief Build a list of classes, while checking for several errors.
1370 @param doc Pointer to the xmlDoc loaded from the IDL.
1371 @return 1 if errors found, or 0 if not.
1374 - Every child element of the root must be of the element "class".
1375 - No text is allowed, other than white space, between classes.
1376 - Comments are allowed (and ignored) between classes.
1378 static int scan_idl( xmlDocPtr doc ) {
1381 xmlNodePtr child = xmlDocGetRootElement( doc )->children;
1383 char* child_name = (char*) child->name;
1384 if( xmlNodeIsText( child ) ) {
1385 if( ! xmlIsBlankNode( child ) ) {
1386 // Found unexpected text. After removing leading and
1387 // trailing white space, complain about it.
1388 xmlChar* content = xmlNodeGetContent( child );
1390 xmlChar* begin = content;
1391 while( *begin && isspace( *begin ) )
1394 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1395 while( (isspace( *end ) ) )
1400 printf( "Unexpected text between class elements: \"%s\"\n",
1404 } else if( !strcmp( child_name, "class" ) ) {
1405 if( register_class( child ) )
1407 } else if( !strcmp( child_name, "comment" ) )
1410 printf( "Line %ld: Unexpected <%s> element under root\n",
1411 xmlGetLineNo( child ), child_name );
1415 child = child->next;
1421 @brief Register a class.
1422 @param class Pointer to the class node.
1423 @return 1 if errors found, or 0 if not.
1426 - Every class element must have an "id" attribute.
1427 - A class id must not be an empty string.
1428 - Every class id must be unique.
1431 - A class id normally consists entirely of lower case letters, digits and underscores.
1432 - A class id longer than 12 characters is suspiciously long.
1434 static int register_class( xmlNodePtr class ) {
1436 xmlChar* id = xmlGetProp( class, (xmlChar*) "id" );
1439 printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) );
1441 } else if( ! *id ) {
1442 printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) );
1446 // In principle a class id could contain any arbitrary characters, but in practice
1447 // anything but lower case, digits, and underscores is probably a mistake.
1448 const xmlChar* p = id;
1450 if( islower( *p ) || isdigit( *p ) || '_' == *p )
1453 printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, "
1454 "digits, and underscores\n", xmlGetLineNo( class ), (char*) id );
1459 // Warn about a suspiciously long id
1460 if( warn && strlen( (char*) id ) > 12 ) {
1461 printf( "Line %ld: WARNING: Class id is unusually long: \"%s\"\n",
1462 xmlGetLineNo( class ), (char*) id );
1465 // Add the classname to the list of classes. If the size of
1466 // the list doesn't change, then we must have a duplicate.
1467 Class* entry = newClass( class );
1468 unsigned long class_count = osrfHashGetCount( classes );
1469 osrfHashSet( classes, entry, (char*) id );
1470 if( osrfHashGetCount( classes ) == class_count ) {
1471 printf( "Line %ld: Duplicate class name \"%s\"\n",
1472 xmlGetLineNo( class ), (char*) id );
1481 @brief Add a field to a class's field list (unless the id collides with an earlier entry).
1482 @param class Pointer to the current class.
1483 @param id The class id.
1484 @param new_field Pointer to the Field to be added.
1485 @return 0 if successful, or 1 if not (probably due to a duplicate key).
1487 If the id collides with a previous entry, we free the new Field instead of adding it
1488 to the list. If the label collides with a previous entry, we complain, but we go
1489 ahead and add the Field to the list.
1492 - Each field name should be unique within the fields element.
1493 - Each label should be unique within the fields element.
1495 static int addField( Class* class, const char* id, Field* new_field ) {
1496 if( ! class || ! new_field )
1502 // See if the class has any other fields with the same name or label.
1503 const Field* old_field = class->fields;
1504 while( old_field ) {
1507 if( !strcmp( (char*) old_field->name, (char*) new_field->name ) ) {
1508 printf( "Duplicate field name \"%s\" in class \"%s\"\n",
1509 (char*) new_field->name, id );
1515 // Compare the labels. if they're both non-empty
1516 if( old_field->label && *old_field->label
1517 && new_field->label && *new_field->label
1518 && !strcmp( (char*) old_field->label, (char*) new_field->label )) {
1519 printf( "Duplicate labels \"%s\" in class \"%s\"\n",
1520 (char*) old_field->label, id );
1524 old_field = old_field->next;
1530 new_field->next = class->fields;
1531 class->fields = new_field;
1538 @brief Add a Link to the Link list of a specified Class (unless it's a duplicate).
1539 @param class Pointer to the Class to whose list to add the Link.
1541 @param new_link Pointer to the Link to be added.
1542 @return 0 if successful, or 1 if not (probably due to a duplicate).
1544 If there's already a Link in the list with the same field name, free the new Link
1545 instead of adding it.
1547 static int addLink( Class* class, const char* id, Link* new_link ) {
1548 if( ! class || ! new_link )
1554 // See if the class has any other links with the same field
1555 const Link* old_link = class->links;
1558 if( !strcmp( (char*) old_link->field, (char*) new_link->field ) ) {
1559 printf( "Duplicate field name \"%s\" in links of class \"%s\"\n",
1560 (char*) old_link->field, id );
1566 old_link = old_link->next;
1570 freeLink( new_link );
1572 // Add to the linked list
1573 new_link->next = class->links;
1574 class->links = new_link;
1581 @brief Create and initialize a new Class.
1582 @param node Pointer to the XML node for a class element.
1583 @return Pointer to the newly created Class.
1585 The calling code is responsible for freeing the Class by calling freeClass(). In practice
1586 this happens automagically when we free the osrfHash classes.
1588 static Class* newClass( xmlNodePtr node ) {
1589 Class* class = safe_malloc( sizeof( Class ) );
1592 class->is_virtual = 0;
1593 xmlFree( class->primary );
1594 class->fields = NULL;
1595 class->links = NULL;
1600 @brief Free a Class and everything it owns.
1601 @param key The class id (not used).
1602 @param p A pointer to the Class to be freed, cast to a void pointer.
1604 This function is designed to be a freeItem callback for an osrfHash.
1606 static void freeClass( char* key, void* p ) {
1609 // Free the linked list of Fields
1610 Field* next_field = NULL;
1611 Field* field = class->fields;
1613 next_field = field->next;
1618 // Free the linked list of Links
1619 Link* next_link = NULL;
1620 Link* link = class->links;
1622 next_link = link->next;
1631 @brief Allocate and initialize a Field.
1632 @param name Field name.
1633 @return Pointer to a new Field.
1635 It is the responsibility of the caller to free the Field by calling freeField().
1637 static Field* newField( xmlChar* name ) {
1638 Field* field = safe_malloc( sizeof( Field ) );
1641 field->is_virtual = 0;
1642 field->label = NULL;
1643 field->datatype = DT_NONE;
1648 @brief Free a Field and everything in it.
1649 @param field Pointer to the Field to be freed.
1651 static void freeField( Field* field ) {
1653 xmlFree( field->name );
1655 xmlFree( field->label );
1661 @brief Allocate and initialize a Link.
1662 @param field Field name.
1663 @return Pointer to a new Link.
1665 It is the responsibility of the caller to free the Link by calling freeLink().
1667 static Link* newLink( xmlChar* field ) {
1668 Link* link = safe_malloc( sizeof( Link ) );
1670 link->field = field;
1671 link->reltype = RT_NONE;
1673 link->classref = NULL;
1678 @brief Free a Link and everything it owns.
1679 @param link Pointer to the Link to be freed.
1681 static void freeLink( Link* link ) {
1683 xmlFree( link->field );
1684 xmlFree( link->key );
1685 xmlFree( link->classref );