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\" may need 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 - 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".
1050 - A non-virtual field should have a datatype attribute.
1051 - Attribute "array_position" is deprecated.
1053 static int val_one_field( Class* class, const char* id, xmlNodePtr field ) {
1055 xmlChar* label = NULL;
1056 xmlChar* field_name = NULL;
1058 Datatype datatype = DT_NONE;
1060 // Traverse the attributes
1061 xmlAttrPtr attr = field->properties;
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" ) )
1070 else if( strcmp( (char*) virt, "false" ) ) {
1071 printf( "Line %ld: Invalid value for virtual attribute: \"%s\"\n",
1072 xmlGetLineNo( field ), (char*) 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 );
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 );
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" ) ) {
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 );
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 );
1119 xmlFree( primitive );
1120 } else if( !strcmp( attr_name, "validate" )) {
1121 xmlChar* validate = xmlGetProp( field, (xmlChar*) "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 );
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" )) {
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 );
1141 xmlFree( required );
1142 // To do: verify that the namespace is oils_obj
1144 printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n",
1145 xmlGetLineNo( field ), attr_name, id );
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 );
1157 if( ! field_name ) {
1158 printf( "Line %ld: No name attribute for <field> element in class \"%s\"\n",
1159 xmlGetLineNo( field ), id );
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 );
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 ) )
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.
1183 static Datatype translate_datatype( const xmlChar* value ) {
1184 const char* val = (const char*) value;
1187 if( !value || !*value )
1189 else if( !strcmp( val, "bool" ) )
1191 else if( !strcmp( val, "float" ) )
1193 else if( !strcmp( val, "id" ) )
1195 else if( !strcmp( val, "int" ) )
1197 else if( !strcmp( val, "interval" ) )
1199 else if( !strcmp( val, "link" ) )
1201 else if( !strcmp( val, "money" ) )
1203 else if( !strcmp( val, "number" ) )
1205 else if( !strcmp( val, "org_unit" ) )
1207 else if( !strcmp( val, "text" ) )
1209 else if( !strcmp( val, "timestamp" ) )
1210 type = DT_TIMESTAMP;
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.
1225 - No elements other than "link" are allowed.
1226 - Text is not allowed, other than white space.
1227 - Comments are allowed (and ignored).
1230 - There is usually at least one link element.
1232 static int val_links( Class* class, const char* id, xmlNodePtr links ) {
1234 int link_found = 0; // boolean
1236 xmlNodePtr child = links->children;
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 );
1245 xmlChar* begin = content;
1246 while( *begin && isspace( *begin ) )
1249 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1250 while( (isspace( *end ) ) )
1255 printf( "Unexpected text in <links> element of class \"%s\": \"%s\"\n", id,
1259 } else if( ! strcmp( child_name, "link" ) ) {
1261 if( val_one_link( class, id, child ) )
1263 } else if( !strcmp( child_name, "comment" ) )
1266 printf( "Line %ld: Unexpected <%s> element in <link> of class \"%s\"\n",
1267 xmlGetLineNo( child ), child_name, id );
1270 child = child->next;
1273 if( warn && !link_found ) {
1274 printf( "WARNING: No <link> element in class \"%s\"\n", id );
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.
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".
1293 static int val_one_link( Class* class, const char* id, xmlNodePtr link ) {
1295 xmlChar* field_name = NULL;
1296 Reltype reltype = RT_NONE;
1297 xmlChar* key = NULL;
1298 xmlChar* classref = NULL;
1300 // Traverse the attributes
1301 xmlAttrPtr attr = link->properties;
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" ) ) {
1308 xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" );
1310 reltype = translate_reltype( rt );
1311 if( RT_INVALID == reltype ) {
1313 "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n",
1314 xmlGetLineNo( link ), (char*) rt, id );
1318 printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n",
1319 xmlGetLineNo( link ), id );
1323 } else if (!strcmp( attr_name, "key" ) ) {
1324 key = xmlGetProp( link, (xmlChar*) "key" );
1325 } else if (!strcmp( attr_name, "map" ) ) {
1327 } else if (!strcmp( attr_name, "class" ) ) {
1328 classref = xmlGetProp( link, (xmlChar*) "class" );
1330 printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n",
1331 xmlGetLineNo( link ), attr_name, id );
1338 printf( "Line %ld: No field attribute found in <link> in class \"%s\"\n",
1339 xmlGetLineNo( link ), id );
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 );
1345 } else if( !reltype ) {
1346 printf( "Line %ld: No reltype attribute found in <link> in class \"%s\"\n",
1347 xmlGetLineNo( link ), id );
1350 printf( "Line %ld: No key attribute found in <link> in class \"%s\"\n",
1351 xmlGetLineNo( link ), id );
1353 } else if( '\0' == *key ) {
1354 printf( "Line %ld: key attribute is empty for <link> element in class \"%s\"\n",
1355 xmlGetLineNo( link ), id );
1357 } else if( !classref ) {
1358 printf( "Line %ld: No class attribute found in <link> in class \"%s\"\n",
1359 xmlGetLineNo( link ), id );
1361 } else if( '\0' == *classref ) {
1362 printf( "Line %ld: class attribute is empty for <link> element in class \"%s\"\n",
1363 xmlGetLineNo( link ), id );
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 ) )
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.
1383 static Reltype translate_reltype( const xmlChar* value ) {
1384 const char* val = (char*) value;
1389 else if( !strcmp( val, "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;
1396 reltype = RT_INVALID;
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.
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.
1411 static int scan_idl( xmlDocPtr doc ) {
1414 xmlNodePtr child = xmlDocGetRootElement( doc )->children;
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 );
1423 xmlChar* begin = content;
1424 while( *begin && isspace( *begin ) )
1427 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1428 while( (isspace( *end ) ) )
1433 printf( "Unexpected text between class elements: \"%s\"\n",
1437 } else if( !strcmp( child_name, "class" ) ) {
1438 if( register_class( child ) )
1440 } else if( !strcmp( child_name, "comment" ) )
1443 printf( "Line %ld: Unexpected <%s> element under root\n",
1444 xmlGetLineNo( child ), child_name );
1448 child = child->next;
1454 @brief Register a class.
1455 @param class Pointer to the class node.
1456 @return 1 if errors found, or 0 if not.
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.
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.
1467 static int register_class( xmlNodePtr class ) {
1469 xmlChar* id = xmlGetProp( class, (xmlChar*) "id" );
1472 printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) );
1474 } else if( ! *id ) {
1475 printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) );
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;
1483 if( islower( *p ) || isdigit( *p ) || '_' == *p )
1486 printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, "
1487 "digits, and underscores\n", xmlGetLineNo( class ), (char*) id );
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 );
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 );
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).
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.
1525 - Each field name should be unique within the fields element.
1526 - Each label should be unique within the fields element.
1528 static int addField( Class* class, const char* id, Field* new_field ) {
1529 if( ! class || ! new_field )
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 ) {
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 );
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 );
1557 old_field = old_field->next;
1563 new_field->next = class->fields;
1564 class->fields = new_field;
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.
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).
1577 If there's already a Link in the list with the same field name, free the new Link
1578 instead of adding it.
1580 static int addLink( Class* class, const char* id, Link* new_link ) {
1581 if( ! class || ! new_link )
1587 // See if the class has any other links with the same field
1588 const Link* old_link = class->links;
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 );
1599 old_link = old_link->next;
1603 freeLink( new_link );
1605 // Add to the linked list
1606 new_link->next = class->links;
1607 class->links = new_link;
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.
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.
1621 static Class* newClass( xmlNodePtr node ) {
1622 Class* class = safe_malloc( sizeof( Class ) );
1625 class->is_virtual = 0;
1626 xmlFree( class->primary );
1627 class->fields = NULL;
1628 class->links = NULL;
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.
1637 This function is designed to be a freeItem callback for an osrfHash.
1639 static void freeClass( char* key, void* p ) {
1642 // Free the linked list of Fields
1643 Field* next_field = NULL;
1644 Field* field = class->fields;
1646 next_field = field->next;
1651 // Free the linked list of Links
1652 Link* next_link = NULL;
1653 Link* link = class->links;
1655 next_link = link->next;
1664 @brief Allocate and initialize a Field.
1665 @param name Field name.
1666 @return Pointer to a new Field.
1668 It is the responsibility of the caller to free the Field by calling freeField().
1670 static Field* newField( xmlChar* name ) {
1671 Field* field = safe_malloc( sizeof( Field ) );
1674 field->is_virtual = 0;
1675 field->label = NULL;
1676 field->datatype = DT_NONE;
1681 @brief Free a Field and everything in it.
1682 @param field Pointer to the Field to be freed.
1684 static void freeField( Field* field ) {
1686 xmlFree( field->name );
1688 xmlFree( field->label );
1694 @brief Allocate and initialize a Link.
1695 @param field Field name.
1696 @return Pointer to a new Link.
1698 It is the responsibility of the caller to free the Link by calling freeLink().
1700 static Link* newLink( xmlChar* field ) {
1701 Link* link = safe_malloc( sizeof( Link ) );
1703 link->field = field;
1704 link->reltype = RT_NONE;
1706 link->classref = NULL;
1711 @brief Free a Link and everything it owns.
1712 @param link Pointer to the Link to be freed.
1714 static void freeLink( Link* link ) {
1716 xmlFree( link->field );
1717 xmlFree( link->key );
1718 xmlFree( link->classref );