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 );
743 } else if( !strchr( (const char*) sequence, '.' )) {
745 "Line %ld: name of sequence for class \"%s\" is not qualified by schema\n",
746 xmlGetLineNo( fields ), id );
750 printf( "Line %ld: Unexpected fields attribute \"%s\" in class \"%s\"\n",
751 xmlGetLineNo( fields ), attr_name, id );
758 if( sequence && ! primary ) {
759 printf( "Line %ld: class \"%s\" has a sequence identified but no primary key\n",
760 xmlGetLineNo( fields ), id );
765 // look for the primary key
766 Field* field = class->fields;
768 if( !strcmp( (char*) field->name, (char*) primary ) )
773 printf( "Primary key field \"%s\" does not exist for class \"%s\"\n",
774 (char*) primary, id );
776 } else if( DT_ID == field->datatype && ! sequence && ! class->is_virtual ) {
778 "Line %ld: Primary key is an id; class \"%s\" may need a sequence attribute\n",
779 xmlGetLineNo( fields ), id );
781 } else if( DT_ID != field->datatype
782 && DT_INT != field->datatype
783 && DT_ORG_UNIT != field->datatype
786 "Line %ld: Datatype of key for class \"%s\" does not allow a sequence attribute\n",
787 xmlGetLineNo( fields ), id );
798 @brief Verify that every Link has a matching Field for a given Class.
799 @param class Pointer to the current class.
801 @return 1 if errors found, or 0 if not.
804 - For every link element, there must be a matching field element in the same class.
805 - If the link's reltype is "has_many", the field must be a virtual field of type link
807 - If the link's reltype is "has_a" or "might_have", the field must be a non-virtual link
808 of type link or org_unit.
810 static int val_links_to_fields( const Class* class, const char* id ) {
816 const Link* link = class->links;
818 if( link->field && *link->field ) {
819 const Field* field = searchFieldByName( class, link->field );
821 if( compareFieldAndLink( class, id, field, link ) )
824 printf( "\"%s\" class has no <field> corresponding to <link> for \"%s\"\n",
825 id, (char*) link->field );
836 @brief Compare matching field and link elements to see if they are compatible
837 @param class Pointer to the current Class.
839 @param field Pointer to the Field to be compared to the Link.
840 @param link Pointer to the Link to be compared to the Field.
841 @return 0 if they are compatible, or 1 if not.
844 - If the reltype is "has_many", the field must be virtual.
845 - If a field corresponds to a link, and is not the primary key, then it must have a
846 datatype "link" or "org_unit".
847 - If the datatype is "org_unit", the linkage must be to the class "aou".
850 - If the reltype is "has_a" or "might_have", the the field should probably @em not
851 be virtual, but there are legitimate exceptions.
852 - If the linkage is to the class "aou", then the datatype should probably be "org_unit".
854 static int compareFieldAndLink( const Class* class, const char* id,
855 const Field* field, const Link* link ) {
858 Datatype datatype = field->datatype;
859 const char* classref = (const char*) link->classref;
861 // Validate the virtuality of the field
862 if( RT_HAS_A == link->reltype || RT_MIGHT_HAVE == link->reltype ) {
863 if( warn && field->is_virtual ) {
864 // This is the child class; field should usually be non-virtual,
865 // but there are legitimate exceptions.
866 printf( "WARNING: In class \"%s\": field \"%s\" is tied to a \"has_a\" or "
867 "\"might_have\" link; perhaps should not be virtual\n",
868 id, (char*) field->name );
870 } else if ( RT_HAS_MANY == link->reltype ) {
871 if( ! field->is_virtual ) {
872 printf( "In class \"%s\": field \"%s\" is tied to a \"has_many\" link "
873 "and therefore should be virtual\n", id, (char*) field->name );
878 // Validate the datatype of the field
879 if( class->primary && !strcmp( (char*) class->primary, (char*) field->name ) ) {
880 ; // For the primary key field, the datatype can be anything
881 } else if( DT_NONE == datatype || DT_INVALID == datatype ) {
882 printf( "In class \"%s\": \"%s\" field should have a datatype for linkage\n",
883 id, (char*) field->name );
885 } else if( DT_ORG_UNIT == datatype ) {
886 if( strcmp( classref, "aou" ) ) {
887 printf( "In class \"%s\": \"%s\" field should have a datatype "
888 "\"link\", not \"org_unit\"\n", id, field->name );
891 } else if( DT_LINK == datatype ) {
892 if( warn && !strcmp( classref, "aou" ) ) {
893 printf( "WARNING: In class \"%s\", field \"%s\": Consider changing datatype "
894 "to \"org_unit\"\n", id, (char*) field->name );
897 // Datatype should be "link", or maybe "org_unit"
898 if( !strcmp( classref, "aou" ) ) {
899 printf( "In class \"%s\": \"%s\" field should have a datatype "
900 "\"org_unit\" or \"link\"\n",
901 id, (char*) field->name );
904 printf( "In class \"%s\": \"%s\" field should have a datatype \"link\"\n",
905 id, (char*) field->name );
914 @brief See if every linked field has a counterpart in the links aggregate.
915 @param class Pointer to the current class.
917 @return 1 if errors found, or 0 if not.
920 - If a field has a datatype of "link" or "org_unit, there must be a corresponding
921 entry in the links aggregate.
923 static int val_fields_to_links( const Class* class, const char* id ) {
925 const Field* field = class->fields;
927 if( DT_LINK != field->datatype && DT_ORG_UNIT != field->datatype ) {
929 continue; // not a link? skip it
931 // See if there's a matching entry in the <links> aggregate
932 const Link* link = class->links;
934 if( !strcmp( (char*) field->name, (char*) link->field ) )
940 if( !strcmp( (char*) field->name, "id" ) && !strcmp( id, "aou" ) ) {
941 // Special exception: primary key of "aou" is of
942 // datatype "org_unit", but it's not a foreign key.
945 printf( "In class \"%s\": Linked field \"%s\" has no matching <link>\n",
946 id, (char*) field->name );
956 @brief Search a given Class for a Field with a given name.
957 @param class Pointer to the class in which to search.
958 @param field_name The field name for which to search.
959 @return Pointer to the Field if found, or NULL if not.
961 static const Field* searchFieldByName( const Class* class, const xmlChar* field_name ) {
962 if( ! class || ! field_name || ! *field_name )
965 const char* name = (const char*) field_name;
966 const Field* field = class->fields;
968 if( field->name && !strcmp( (char*) field->name, name ) )
977 @brief Validate a fields element.
978 @param class Pointer to the current Class.
979 @param id Id of the current Class.
980 @param fields Pointer to the XML node for the fields element.
981 @return 1 if errors found, or 0 if not.
984 - There must be at least one field element.
985 - No other elements are allowed.
986 - Text is not allowed, other than white space.
987 - Comments are allowed (and ignored).
989 static int val_fields( Class* class, const char* id, xmlNodePtr fields ) {
991 int field_found = 0; // boolean
993 xmlNodePtr child = fields->children;
995 const char* child_name = (char*) child->name;
996 if( xmlNodeIsText( child ) ) {
997 if( ! xmlIsBlankNode( child ) ) {
998 // Found unexpected text. After removing leading and
999 // trailing white space, complain about it.
1000 xmlChar* content = xmlNodeGetContent( child );
1002 xmlChar* begin = content;
1003 while( *begin && isspace( *begin ) )
1006 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1007 while( (isspace( *end ) ) )
1012 printf( "Unexpected text in <fields> element of class \"%s\": \"%s\"\n", id,
1016 } else if( ! strcmp( child_name, "field" ) ) {
1018 if( val_one_field( class, id, child ) )
1020 } else if( !strcmp( child_name, "comment" ) )
1023 printf( "Line %ld: Unexpected <%s> element in <fields> of class \"%s\"\n",
1024 xmlGetLineNo( child ), child_name, id );
1027 child = child->next;
1030 if( !field_found ) {
1031 printf( "No <field> element in class \"%s\"\n", id );
1039 @brief Validate a field element within a fields element.
1040 @param class Pointer to the current Class.
1042 @param field Pointer to the XML node for the field element.
1043 @return 1 if errors found, or 0 if not.
1046 - attribute names are limited to: "name", "virtual", "label", "datatype", "array_position",
1047 "selector", "i18n", "primitive".
1048 - "name" attribute is required.
1049 - label attribute, if present, must have a non-empty value.
1050 - virtual and i18n attributes, if present, must have a value of "true" or "false".
1051 - if the datatype attribute is present, its value must be one of: "bool", "float", "id",
1052 "int", "interval", "link", "money", "number", "org_unit", "text", "timestamp".
1055 - A non-virtual field should have a datatype attribute.
1056 - Attribute "array_position" is deprecated.
1058 static int val_one_field( Class* class, const char* id, xmlNodePtr field ) {
1060 xmlChar* label = NULL;
1061 xmlChar* field_name = NULL;
1063 Datatype datatype = DT_NONE;
1065 // Traverse the attributes
1066 xmlAttrPtr attr = field->properties;
1068 const char* attr_name = (char*) attr->name;
1069 if( !strcmp( attr_name, "name" ) ) {
1070 field_name = xmlGetProp( field, (xmlChar*) "name" );
1071 } else if( !strcmp( attr_name, "virtual" ) ) {
1072 xmlChar* virt = xmlGetProp( field, (xmlChar*) "virtual" );
1073 if( !strcmp( (char*) virt, "true" ) )
1075 else if( strcmp( (char*) virt, "false" ) ) {
1076 printf( "Line %ld: Invalid value for virtual attribute: \"%s\"\n",
1077 xmlGetLineNo( field ), (char*) virt );
1081 // To do: verify that the namespace is oils_persist
1082 } else if( !strcmp( attr_name, "label" ) ) {
1083 label = xmlGetProp( field, (xmlChar*) "label" );
1084 if( '\0' == *label ) {
1085 printf( "Line %ld: Empty value for label attribute for class \"%s\"\n",
1086 xmlGetLineNo( field ), id );
1091 // To do: verify that the namespace is reporter
1092 } else if( !strcmp( attr_name, "datatype" ) ) {
1093 xmlChar* dt_str = xmlGetProp( field, (xmlChar*) "datatype" );
1094 datatype = translate_datatype( dt_str );
1095 if( DT_INVALID == datatype ) {
1096 printf( "Line %ld: Invalid datatype \"%s\" in class \"%s\"\n",
1097 xmlGetLineNo( field ), (char*) dt_str, id );
1101 // To do: make sure that the namespace is reporter
1102 } else if( !strcmp( attr_name, "array_position" ) ) {
1103 printf( "Line %ld: WARNING: Deprecated array_position attribute "
1104 "for field \"%s\" in class \"%s\"\n",
1105 xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1106 } else if( !strcmp( attr_name, "selector" ) ) {
1108 } else if( !strcmp( attr_name, "i18n" ) ) {
1109 xmlChar* i18n = xmlGetProp( field, (xmlChar*) "i18n" );
1110 if( strcmp( (char*) i18n, "true" ) && strcmp( (char*) i18n, "false" ) ) {
1111 printf( "Line %ld: Invalid value for i18n attribute: \"%s\"\n",
1112 xmlGetLineNo( field ), (char*) i18n );
1116 // To do: verify that the namespace is oils_persist
1117 } else if( !strcmp( attr_name, "primitive" ) ) {
1118 xmlChar* primitive = xmlGetProp( field, (xmlChar*) "primitive" );
1119 if( strcmp( (char*) primitive, "string" ) && strcmp( (char*) primitive, "number" ) ) {
1120 printf( "Line %ld: Invalid value for primitive attribute: \"%s\"\n",
1121 xmlGetLineNo( field ), (char*) primitive );
1124 xmlFree( primitive );
1125 } else if( !strcmp( attr_name, "validate" )) {
1126 xmlChar* validate = xmlGetProp( field, (xmlChar*) "validate" );
1128 // Value should be a regular expression to define a validation rule
1129 printf( "Line %ld: Empty value for \"validate\" attribute "
1130 "for field \"%s\" in class \"%s\"\n",
1131 xmlGetLineNo( field ), (char*) field_name ? : "", id );
1134 xmlFree( validate );
1135 // To do: verify that the namespace is oils_obj
1136 } else if( !strcmp( attr_name, "required" )) {
1137 xmlChar* required = xmlGetProp( field, (xmlChar*) "required" );
1138 if( strcmp( (char*) required, "true" ) && strcmp( (char*) required, "false" )) {
1140 "Line %ld: Invalid value \"%s\" for \"required\" attribute "
1141 "for field \"%s\" in class \"%s\"\n",
1142 xmlGetLineNo( field ), (char*) required,
1143 (char*) field_name ? : "", id );
1146 xmlFree( required );
1147 // To do: verify that the namespace is oils_obj
1149 printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n",
1150 xmlGetLineNo( field ), attr_name, id );
1157 if( warn && (!is_virtual) && DT_NONE == datatype ) {
1158 printf( "Line %ld: WARNING: No datatype attribute for field \"%s\" in class \"%s\"\n",
1159 xmlGetLineNo( field ), ((char*) field_name ? : ""), id );
1162 if( ! field_name ) {
1163 printf( "Line %ld: No name attribute for <field> element in class \"%s\"\n",
1164 xmlGetLineNo( field ), id );
1166 } else if( '\0' == *field_name ) {
1167 printf( "Line %ld: Field name is empty for <field> element in class \"%s\"\n",
1168 xmlGetLineNo( field ), id );
1171 // Add to the class's field list
1172 Field* new_field = newField( field_name );
1173 new_field->is_virtual = is_virtual;
1174 new_field->label = label;
1175 new_field->datatype = datatype;
1176 if( addField( class, id, new_field ) )
1184 @brief Translate a datatype string into a Dataype (an enum).
1185 @param value The value of a datatype attribute.
1186 @return The datatype in the form of an enum.
1188 static Datatype translate_datatype( const xmlChar* value ) {
1189 const char* val = (const char*) value;
1192 if( !value || !*value )
1194 else if( !strcmp( val, "bool" ) )
1196 else if( !strcmp( val, "float" ) )
1198 else if( !strcmp( val, "id" ) )
1200 else if( !strcmp( val, "int" ) )
1202 else if( !strcmp( val, "interval" ) )
1204 else if( !strcmp( val, "link" ) )
1206 else if( !strcmp( val, "money" ) )
1208 else if( !strcmp( val, "number" ) )
1210 else if( !strcmp( val, "org_unit" ) )
1212 else if( !strcmp( val, "text" ) )
1214 else if( !strcmp( val, "timestamp" ) )
1215 type = DT_TIMESTAMP;
1223 @brief Validate a links element.
1224 @param class Pointer to the current Class.
1225 @param id Id of the current Class.
1226 @param links Pointer to the XML node for the links element.
1227 @return 1 if errors found, or 0 if not.
1230 - No elements other than "link" are allowed.
1231 - Text is not allowed, other than white space.
1232 - Comments are allowed (and ignored).
1235 - There is usually at least one link element.
1237 static int val_links( Class* class, const char* id, xmlNodePtr links ) {
1239 int link_found = 0; // boolean
1241 xmlNodePtr child = links->children;
1243 const char* child_name = (char*) child->name;
1244 if( xmlNodeIsText( child ) ) {
1245 if( ! xmlIsBlankNode( child ) ) {
1246 // Found unexpected text. After removing leading and
1247 // trailing white space, complain about it.
1248 xmlChar* content = xmlNodeGetContent( child );
1250 xmlChar* begin = content;
1251 while( *begin && isspace( *begin ) )
1254 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1255 while( (isspace( *end ) ) )
1260 printf( "Unexpected text in <links> element of class \"%s\": \"%s\"\n", id,
1264 } else if( ! strcmp( child_name, "link" ) ) {
1266 if( val_one_link( class, id, child ) )
1268 } else if( !strcmp( child_name, "comment" ) )
1271 printf( "Line %ld: Unexpected <%s> element in <link> of class \"%s\"\n",
1272 xmlGetLineNo( child ), child_name, id );
1275 child = child->next;
1278 if( warn && !link_found ) {
1279 printf( "WARNING: No <link> element in class \"%s\"\n", id );
1286 @brief Validate one link element.
1287 @param class Pointer to the current Class.
1288 @param id Id of the current Class.
1289 @param link Pointer to the XML node for the link element.
1290 @return 1 if errors found, or 0 if not.
1293 - The only allowed attributes are "field", "reltype", "key", "map", and "class".
1294 - Except for map, every attribute is required.
1295 - Except for map, every attribute must have a non-empty value.
1296 - The value of the reltype attribute must be one of "has_a", "might_have", or "has_many".
1298 static int val_one_link( Class* class, const char* id, xmlNodePtr link ) {
1300 xmlChar* field_name = NULL;
1301 Reltype reltype = RT_NONE;
1302 xmlChar* key = NULL;
1303 xmlChar* classref = NULL;
1305 // Traverse the attributes
1306 xmlAttrPtr attr = link->properties;
1308 const char* attr_name = (const char*) attr->name;
1309 if( !strcmp( attr_name, "field" ) ) {
1310 field_name = xmlGetProp( link, (xmlChar*) "field" );
1311 } else if (!strcmp( attr_name, "reltype" ) ) {
1313 xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" );
1315 reltype = translate_reltype( rt );
1316 if( RT_INVALID == reltype ) {
1318 "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n",
1319 xmlGetLineNo( link ), (char*) rt, id );
1323 printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n",
1324 xmlGetLineNo( link ), id );
1328 } else if (!strcmp( attr_name, "key" ) ) {
1329 key = xmlGetProp( link, (xmlChar*) "key" );
1330 } else if (!strcmp( attr_name, "map" ) ) {
1332 } else if (!strcmp( attr_name, "class" ) ) {
1333 classref = xmlGetProp( link, (xmlChar*) "class" );
1335 printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n",
1336 xmlGetLineNo( link ), attr_name, id );
1343 printf( "Line %ld: No field attribute found in <link> in class \"%s\"\n",
1344 xmlGetLineNo( link ), id );
1346 } else if( '\0' == *field_name ) {
1347 printf( "Line %ld: Field name is empty for <link> element in class \"%s\"\n",
1348 xmlGetLineNo( link ), id );
1350 } else if( !reltype ) {
1351 printf( "Line %ld: No reltype attribute found in <link> in class \"%s\"\n",
1352 xmlGetLineNo( link ), id );
1355 printf( "Line %ld: No key attribute found in <link> in class \"%s\"\n",
1356 xmlGetLineNo( link ), id );
1358 } else if( '\0' == *key ) {
1359 printf( "Line %ld: key attribute is empty for <link> element in class \"%s\"\n",
1360 xmlGetLineNo( link ), id );
1362 } else if( !classref ) {
1363 printf( "Line %ld: No class attribute found in <link> in class \"%s\"\n",
1364 xmlGetLineNo( link ), id );
1366 } else if( '\0' == *classref ) {
1367 printf( "Line %ld: class attribute is empty for <link> element in class \"%s\"\n",
1368 xmlGetLineNo( link ), id );
1372 Link* new_link = newLink( field_name );
1373 new_link->reltype = reltype;
1374 new_link->key = key;
1375 new_link->classref = classref;
1376 if( addLink( class, id, new_link ) )
1384 @brief Translate an attribute value into a Reltype (an enum).
1385 @param value The value of a reltype attribute.
1386 @return The value of the attribute translated into the enum Reltype.
1388 static Reltype translate_reltype( const xmlChar* value ) {
1389 const char* val = (char*) value;
1394 else if( !strcmp( val, "has_a" ) )
1396 else if( !strcmp( val, "might_have" ) )
1397 reltype = RT_MIGHT_HAVE;
1398 else if( !strcmp( val, "has_many" ) )
1399 reltype = RT_HAS_MANY;
1401 reltype = RT_INVALID;
1407 @brief Build a list of classes, while checking for several errors.
1408 @param doc Pointer to the xmlDoc loaded from the IDL.
1409 @return 1 if errors found, or 0 if not.
1412 - Every child element of the root must be of the element "class".
1413 - No text is allowed, other than white space, between classes.
1414 - Comments are allowed (and ignored) between classes.
1416 static int scan_idl( xmlDocPtr doc ) {
1419 xmlNodePtr child = xmlDocGetRootElement( doc )->children;
1421 char* child_name = (char*) child->name;
1422 if( xmlNodeIsText( child ) ) {
1423 if( ! xmlIsBlankNode( child ) ) {
1424 // Found unexpected text. After removing leading and
1425 // trailing white space, complain about it.
1426 xmlChar* content = xmlNodeGetContent( child );
1428 xmlChar* begin = content;
1429 while( *begin && isspace( *begin ) )
1432 xmlChar* end = begin + strlen( (char*) begin ) - 1;
1433 while( (isspace( *end ) ) )
1438 printf( "Unexpected text between class elements: \"%s\"\n",
1442 } else if( !strcmp( child_name, "class" ) ) {
1443 if( register_class( child ) )
1445 } else if( !strcmp( child_name, "comment" ) )
1448 printf( "Line %ld: Unexpected <%s> element under root\n",
1449 xmlGetLineNo( child ), child_name );
1453 child = child->next;
1459 @brief Register a class.
1460 @param class Pointer to the class node.
1461 @return 1 if errors found, or 0 if not.
1464 - Every class element must have an "id" attribute.
1465 - A class id must not be an empty string.
1466 - Every class id must be unique.
1469 - A class id normally consists entirely of lower case letters, digits and underscores.
1470 - A class id longer than 12 characters is suspiciously long.
1472 static int register_class( xmlNodePtr class ) {
1474 xmlChar* id = xmlGetProp( class, (xmlChar*) "id" );
1477 printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) );
1479 } else if( ! *id ) {
1480 printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) );
1484 // In principle a class id could contain any arbitrary characters, but in practice
1485 // anything but lower case, digits, and underscores is probably a mistake.
1486 const xmlChar* p = id;
1488 if( islower( *p ) || isdigit( *p ) || '_' == *p )
1491 printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, "
1492 "digits, and underscores\n", xmlGetLineNo( class ), (char*) id );
1497 // Warn about a suspiciously long id
1498 if( warn && strlen( (char*) id ) > 12 ) {
1499 printf( "Line %ld: WARNING: Class id is unusually long: \"%s\"\n",
1500 xmlGetLineNo( class ), (char*) id );
1503 // Add the classname to the list of classes. If the size of
1504 // the list doesn't change, then we must have a duplicate.
1505 Class* entry = newClass( class );
1506 unsigned long class_count = osrfHashGetCount( classes );
1507 osrfHashSet( classes, entry, (char*) id );
1508 if( osrfHashGetCount( classes ) == class_count ) {
1509 printf( "Line %ld: Duplicate class name \"%s\"\n",
1510 xmlGetLineNo( class ), (char*) id );
1519 @brief Add a field to a class's field list (unless the id collides with an earlier entry).
1520 @param class Pointer to the current class.
1521 @param id The class id.
1522 @param new_field Pointer to the Field to be added.
1523 @return 0 if successful, or 1 if not (probably due to a duplicate key).
1525 If the id collides with a previous entry, we free the new Field instead of adding it
1526 to the list. If the label collides with a previous entry, we complain, but we go
1527 ahead and add the Field to the list.
1530 - Each field name should be unique within the fields element.
1531 - Each label should be unique within the fields element.
1533 static int addField( Class* class, const char* id, Field* new_field ) {
1534 if( ! class || ! new_field )
1540 // See if the class has any other fields with the same name or label.
1541 const Field* old_field = class->fields;
1542 while( old_field ) {
1545 if( !strcmp( (char*) old_field->name, (char*) new_field->name ) ) {
1546 printf( "Duplicate field name \"%s\" in class \"%s\"\n",
1547 (char*) new_field->name, id );
1553 // Compare the labels. if they're both non-empty
1554 if( old_field->label && *old_field->label
1555 && new_field->label && *new_field->label
1556 && !strcmp( (char*) old_field->label, (char*) new_field->label )) {
1557 printf( "Duplicate labels \"%s\" in class \"%s\"\n",
1558 (char*) old_field->label, id );
1562 old_field = old_field->next;
1568 new_field->next = class->fields;
1569 class->fields = new_field;
1576 @brief Add a Link to the Link list of a specified Class (unless it's a duplicate).
1577 @param class Pointer to the Class to whose list to add the Link.
1579 @param new_link Pointer to the Link to be added.
1580 @return 0 if successful, or 1 if not (probably due to a duplicate).
1582 If there's already a Link in the list with the same field name, free the new Link
1583 instead of adding it.
1585 static int addLink( Class* class, const char* id, Link* new_link ) {
1586 if( ! class || ! new_link )
1592 // See if the class has any other links with the same field
1593 const Link* old_link = class->links;
1596 if( !strcmp( (char*) old_link->field, (char*) new_link->field ) ) {
1597 printf( "Duplicate field name \"%s\" in links of class \"%s\"\n",
1598 (char*) old_link->field, id );
1604 old_link = old_link->next;
1608 freeLink( new_link );
1610 // Add to the linked list
1611 new_link->next = class->links;
1612 class->links = new_link;
1619 @brief Create and initialize a new Class.
1620 @param node Pointer to the XML node for a class element.
1621 @return Pointer to the newly created Class.
1623 The calling code is responsible for freeing the Class by calling freeClass(). In practice
1624 this happens automagically when we free the osrfHash classes.
1626 static Class* newClass( xmlNodePtr node ) {
1627 Class* class = safe_malloc( sizeof( Class ) );
1630 class->is_virtual = 0;
1631 xmlFree( class->primary );
1632 class->fields = NULL;
1633 class->links = NULL;
1638 @brief Free a Class and everything it owns.
1639 @param key The class id (not used).
1640 @param p A pointer to the Class to be freed, cast to a void pointer.
1642 This function is designed to be a freeItem callback for an osrfHash.
1644 static void freeClass( char* key, void* p ) {
1647 // Free the linked list of Fields
1648 Field* next_field = NULL;
1649 Field* field = class->fields;
1651 next_field = field->next;
1656 // Free the linked list of Links
1657 Link* next_link = NULL;
1658 Link* link = class->links;
1660 next_link = link->next;
1669 @brief Allocate and initialize a Field.
1670 @param name Field name.
1671 @return Pointer to a new Field.
1673 It is the responsibility of the caller to free the Field by calling freeField().
1675 static Field* newField( xmlChar* name ) {
1676 Field* field = safe_malloc( sizeof( Field ) );
1679 field->is_virtual = 0;
1680 field->label = NULL;
1681 field->datatype = DT_NONE;
1686 @brief Free a Field and everything in it.
1687 @param field Pointer to the Field to be freed.
1689 static void freeField( Field* field ) {
1691 xmlFree( field->name );
1693 xmlFree( field->label );
1699 @brief Allocate and initialize a Link.
1700 @param field Field name.
1701 @return Pointer to a new Link.
1703 It is the responsibility of the caller to free the Link by calling freeLink().
1705 static Link* newLink( xmlChar* field ) {
1706 Link* link = safe_malloc( sizeof( Link ) );
1708 link->field = field;
1709 link->reltype = RT_NONE;
1711 link->classref = NULL;
1716 @brief Free a Link and everything it owns.
1717 @param link Pointer to the Link to be freed.
1719 static void freeLink( Link* link ) {
1721 xmlFree( link->field );
1722 xmlFree( link->key );
1723 xmlFree( link->classref );