From: scottmk Date: Tue, 8 Dec 2009 16:53:24 +0000 (+0000) Subject: Add idlval.c -- source for a new utility for X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=c320fd119126d34123303990ea379bfb0734ae99;p=evergreen%2Fbjwebb.git Add idlval.c -- source for a new utility for validating IDL files. This commit does not include any changes to the Makefile. A Open-ILS/src/c-apps/idlval.c git-svn-id: svn://svn.open-ils.org/ILS/trunk@15102 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/src/c-apps/idlval.c b/Open-ILS/src/c-apps/idlval.c new file mode 100644 index 000000000..4ee5f089a --- /dev/null +++ b/Open-ILS/src/c-apps/idlval.c @@ -0,0 +1,1688 @@ +/** + @file idlval.c + @brief Validator for IDL files. +*/ + +/* +Copyright (C) 2009 Georgia Public Library Service +Scott McKellar + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "osrf_hash.h" + +/* Represents the command line */ +struct Opts { + int new_argc; + char ** new_argv; + + char * idl_file_name; + int idl_file_name_found; + int warning; +}; +typedef struct Opts Opts; + +/* datatype attribute of element */ +typedef enum { + DT_NONE, + DT_BOOL, + DT_FLOAT, + DT_ID, + DT_INT, + DT_INTERVAL, + DT_LINK, + DT_MONEY, + DT_NUMBER, + DT_ORG_UNIT, + DT_TEXT, + DT_TIMESTAMP, + DT_INVALID +} Datatype; + +/* Represents a aggregate */ +struct Field_struct { + struct Field_struct* next; + xmlChar* name; + int is_virtual; // boolean + xmlChar* label; + Datatype datatype; +}; +typedef struct Field_struct Field; + +/* reltype attribute of element */ +typedef enum { + RT_NONE, + RT_HAS_A, + RT_MIGHT_HAVE, + RT_HAS_MANY, + RT_INVALID +} Reltype; + +/* Represents a element */ +struct Link_struct { + struct Link_struct* next; + xmlChar* field; + Reltype reltype; + xmlChar* key; + xmlChar* classref; +}; +typedef struct Link_struct Link; + +/* Represents a aggregate */ +typedef struct { + xmlNodePtr node; + int loaded; // boolean + int is_virtual; // boolean + xmlChar* primary; // name of primary key column + Field* fields; // linked list + Link* links; // linked list +} Class; + +static int get_Opts( int argc, char * argv[], Opts * pOpts );; +static int val_idl( void ); +static int cross_validate_classes( Class* class, const char* id ); +static int cross_validate_linkage( Class* class, const char*id, Link* link ); +static int val_class( Class* class, const char* id ); +static int val_class_attributes( Class* class, const char* id ); +static int check_labels( const Class* class, const char* id ); +static int val_fields_attributes( Class* class, const char* id, xmlNodePtr fields ); +static int val_links_to_fields( const Class* class, const char* id ); +static int compareFieldAndLink( const Class* class, const char* id, + const Field* field, const Link* link ); +static int val_fields_to_links( const Class* class, const char* id ); +static const Field* searchFieldByName( const Class* class, const xmlChar* field_name ); +static int val_fields( Class* class, const char* id, xmlNodePtr fields ); +static int val_one_field( Class* class, const char* id, xmlNodePtr field ); +static Datatype translate_datatype( const xmlChar* value ); +static int val_links( Class* class, const char* id, xmlNodePtr links ); +static int val_one_link( Class* class, const char* id, xmlNodePtr link ); +static Reltype translate_reltype( const xmlChar* value ); +static int scan_idl( xmlDocPtr doc ); +static int register_class( xmlNodePtr child ); +static int addField( Class* class, const char* id, Field* new_field ); +static int addLink( Class* class, const char* id, Link* new_link ); +static Class* newClass( xmlNodePtr node ); +static void freeClass( char* key, void* p ); +static Field* newField( xmlChar* name ); +static void freeField( Field* field ); +static Link* newLink( xmlChar* field ); +static void freeLink( Link* link ); + +/* Stores an in-memory representation of the IDL */ +static osrfHash* classes = NULL; + +static int warn = 0; // boolean; true if -w present on command line + +int main( int argc, char* argv[] ) { + + // Examine command line + Opts opts; + if( get_Opts( argc, argv, &opts ) ) + return 1; + + const char* IDL_filename = NULL; + if( opts.idl_file_name_found ) + IDL_filename = opts.idl_file_name; + else { + IDL_filename = getenv( "OILS_IDL_FILENAME" ); + if( ! IDL_filename ) + IDL_filename = "/openils/conf/fm_IDL.xml"; + } + + if( opts.warning ) + warn = 1; + + int rc = 0; + + xmlLineNumbersDefault(1); + xmlDocPtr doc = xmlReadFile( IDL_filename, NULL, XML_PARSE_XINCLUDE ); + if ( ! doc ) { + fprintf( stderr, "Could not load or parse the IDL XML file %s\n", IDL_filename ); + rc = 1; + } else { + printf( "Validating: %s\n", IDL_filename ); + classes = osrfNewHash(); + osrfHashSetCallback( classes, freeClass ); + + // Load the IDL + if( scan_idl( doc ) ) + rc = 1; + + if( opts.new_argc < 2 ) { + + // No classes specified: validate all classes + if( val_idl() ) + rc = 1; + } else { + + // Validate one or more specified classes + int i = 1; + while( i < opts.new_argc ) { + const char* classname = opts.new_argv[ i ]; + Class* class = osrfHashGet( classes, classname ); + if( ! class ) { + printf( "Class \"%s\" does not exist\n", classname ); + rc = 1; + } else { + // Validate the class in isolation + if( val_class( class, classname ) ) + rc = 1; + // Cross-validate with linked classes + if( cross_validate_classes( class, classname ) ) + rc = 1; + } + ++i; + } + } + osrfHashFree( classes ); + xmlFreeDoc( doc ); + } + + return rc; +} + +/** + @brief Examine the command line + @param argc Number of entries in argv[] + @param argv Array of pointers to command line strings + @param pOpts Pointer to structure to be populated + @return 0 upon success, or 1 if the command line is invalid +*/ +static int get_Opts( int argc, char * argv[], Opts * pOpts ) { + int rc = 0; /* return code */ + int opt; + + /* Define valid option characters */ + + const char optstring[] = ":f:w"; + + /* Initialize members of struct */ + + pOpts->new_argc = 0; + pOpts->new_argv = NULL; + + pOpts->idl_file_name_found = 0; + pOpts->idl_file_name = NULL; + pOpts->warning = 0; + + /* Suppress error messages from getopt() */ + + opterr = 0; + + /* Examine command line options */ + + while( ( opt = getopt( argc, argv, optstring ) ) != -1 ) { + switch( opt ) { + case 'f' : /* Get idl_file_name */ + if( pOpts->idl_file_name_found ) { + fprintf( stderr, "Only one occurrence of -f option allowed\n" ); + rc = 1; + break; + } + pOpts->idl_file_name_found = 1; + + pOpts->idl_file_name = optarg; + break; + case 'w' : /* Get warning */ + pOpts->warning = 1; + break; + case ':' : /* Missing argument */ + fprintf( stderr, "Required argument missing on -%c option\n", + (char) optopt ); + rc = 1; + break; + case '?' : /* Invalid option */ + fprintf( stderr, "Invalid option '-%c' on command line\n", + (char) optopt ); + rc = 1; + break; + default : /* Programmer error */ + fprintf( stderr, "Internal error: unexpected value '-%c'" + "for optopt", (char) optopt ); + rc = 1; + break; + } /* end switch */ + } /* end while */ + + if( optind > argc ) { + /* This should never happen! */ + + fprintf( stderr, "Program error: found more arguments than expected\n" ); + rc = 1; + } else { + /* Calculate new_argcv and new_argc to reflect */ + /* the number of arguments consumed */ + + pOpts->new_argc = argc - optind + 1; + pOpts->new_argv = argv + optind - 1; + } + + return rc; +} + +/** + @brief Validate all classes. + @return 1 if errors found, or 0 if not. + + Traverse the class list and validate each class in turn. +*/ +static int val_idl( void ) { + int rc = 0; + osrfHashIterator* itr = osrfNewHashIterator( classes ); + Class* class = NULL; + + // For each class + while( (class = osrfHashIteratorNext( itr )) ) { + const char* id = osrfHashIteratorKey( itr ); + if( val_class( class, id ) ) // validate class separately + rc = 1; + if( cross_validate_classes( class, id ) ) // cross-validate with linked classes + rc = 1; + } + + osrfHashIteratorFree( itr ); + return rc; +} + +/** + @brief Make sure that every linkage appropriately matches the linked class. + @param class Pointer to the current Class. + @param id Class id. + @return 1 if errors found, or 0 if not. +*/ +static int cross_validate_classes( Class* class, const char* id ) { + int rc = 0; + Link* link = class->links; + while( link ) { + if( cross_validate_linkage( class, id, link ) ) + rc = 1; + link = link->next; + } + + return rc; +} + +/** + @brief Make sure that a linkage appropriately matches the linked class. + @param class Pointer to the current class. + @param id Class id. + @param link Pointer to the link being validated. + @return 1 if errors found, or 0 if not. + + Rules: + - The linked class must exist. + - The field to which the linkage points must exist. + - If the linked class has a corresponding link back to the current class, then exactly + one end of the linkage must have a reltype of "has_many". + + It is not an error if the linkage is not reciprocated. +*/ +static int cross_validate_linkage( Class* class, const char*id, Link* link ) { + int rc = 0; + Class* other_class = osrfHashGet( classes, (char*) link->classref ); + if( ! other_class ) { + printf( "In class \"%s\": class \"%s\", referenced by \"%s\" field, does not exist\n", + id, (char*) link->classref, (char*) link->field ); + rc = 1; + } else { + // Make sure the other class is loaded before we look at it further + if( val_class( other_class, (char*) link->classref ) ) + rc = 1; + + // Now see if the other class links back to this one + Link* other_link = other_class->links; + while( other_link ) { + if( !strcmp( id, (char*) other_link->classref ) // class to class + && !strcmp( (char*) link->key, (char*) other_link->field ) // key to field + && !strcmp( (char*) link->field, (char*) other_link->key ) ) { // field to key + break; + } + other_link = other_link->next; + } + + if( ! other_link ) { + // Link is not reciprocated? That's okay, as long as + // the referenced field exists in the referenced class. + if( !searchFieldByName( other_class, link->key ) ) { + printf( "In class \"%s\": field \"%s\" links to field \"%s\" of class \"%s\", " + "but that field doesn't exist\n", id, (char*) link->field, + (char*) link->key, (char*) link->classref ); + rc = 1; + } + } else { + // The link is reciprocated. Make sure that exactly one of the links + // has a reltype of "has_many" + int many_count = 0; + if( RT_HAS_MANY == link->reltype ) + ++many_count; + if( RT_HAS_MANY == other_link->reltype ) + ++many_count; + + if( 0 == many_count ) { + printf( "Classes \"%s\" and \"%s\" link to each other, but neither has a reltype " + "of \"has_many\"\n", id, (char*) link->classref ); + rc = 1; + } else if( 2 == many_count ) { + printf( "Classes \"%s\" and \"%s\" link to each other, but both have a reltype " + "of \"has_many\"\n", id, (char*) link->classref ); + rc = 1; + } + } + } + + return rc; +} + +/** + @brief Validate a single class. + @param id Class id. + @param class Pointer to the XML node for the class element. + @return 1 if errors found, or 0 if not. + + We have already validated the id. + + Rules: + - Allowed elements are "fields", "links", "permacrud", and "source_definition". + - None of these elements may occur more than once in the same class. + - The "fields" element is required. + - No text allowed, other than white space. + - Comments are allowed (and ignored). +*/ +static int val_class( Class* class, const char* id ) { + if( !class ) + return 1; + else if( class->loaded ) + return 0; // We've already validated this one locally + + int rc = 0; + + if( val_class_attributes( class, id ) ) + rc = 1; + + xmlNodePtr fields = NULL; + xmlNodePtr links = NULL; + xmlNodePtr permacrud = NULL; + xmlNodePtr src_def = NULL; + + // Examine every child element of the element. + xmlNodePtr child = class->node->children; + while( child ) { + const char* child_name = (char*) child->name; + if( xmlNodeIsText( child ) ) { + if( ! xmlIsBlankNode( child ) ) { + // Found unexpected text. After removing leading and + // trailing white space, complain about it. + xmlChar* content = xmlNodeGetContent( child ); + + xmlChar* begin = content; + while( *begin && isspace( *begin ) ) + ++begin; + if( *begin ) { + xmlChar* end = begin + strlen( (char*) begin ) - 1; + while( (isspace( *end ) ) ) + --end; + end[ 1 ] = '\0'; + } + + printf( "Unexpected text in class \"%s\": \"%s\"\n", id, + (char*) begin ); + xmlFree( content ); + } + } else if( !strcmp( child_name, "fields" ) ) { + if( fields ) { + printf( "Multiple elements in class \"%s\"\n", id ); + rc = 1; + } else { + fields = child; + // Identify the primary key, if any + class->primary = xmlGetProp( fields, (xmlChar*) "primary" ); + if( val_fields( class, id, fields ) ) + rc = 1; + } + } else if( !strcmp( child_name, "links" ) ) { + if( links ) { + printf( "Multiple elements in class \"%s\"\n", id ); + rc = 1; + } else { + links = child; + if( val_links( class, id, links ) ) + rc = 1; + } + } else if( !strcmp( child_name, "permacrud" ) ) { + if( permacrud ) { + printf( "Multiple elements in class \"%s\"\n", id ); + rc = 1; + } else { + permacrud = child; + } + } else if( !strcmp( child_name, "source_definition" ) ) { + if( src_def ) { + printf( "Multiple elements in class \"%s\"\n", id ); + rc = 1; + } else { + // To do: verify that there is nothing in except text and + // comments, and that the text is non-empty. + src_def = child; + } + } else if( !strcmp( child_name, "comment" ) ) + ; // ignore comment + else { + printf( "Line %ld: Unexpected <%s> element in class \"%s\"\n", + xmlGetLineNo( child ), child_name, id ); + rc = 1; + } + child = child->next; + } + + if( fields ) { + if( check_labels( class, id ) ) + rc = 1; + if( val_fields_attributes( class, id, fields ) ) + rc = 1; + } else { + printf( "No element in class \"%s\"\n", id ); + rc = 1; + } + + if( val_links_to_fields( class, id ) ) + rc = 1; + + if( val_fields_to_links( class, id ) ) + rc = 1; + + class->loaded = 1; + return rc; +} + +/** + @brief Validate the class attributes. + @param class Pointer to the current Class. + @param id Class id. + @return if errors found, or 0 if not. + + Rules: + - Only the following attributes are valid: controller, core, field_safe, field_mapper, + id, label, readonly, restrict_primary, tablename, and virtual. + - The controller and fieldmapper attributes are required (as is the id attribute, but + that's checked elsewhere). + - Every attribute value must be non-empty. + - The values of attributes core, field_safe, reaadonly, and virtual must be either + "true" or "false". + - A virtual class must not have a tablename attribute. +*/ +static int val_class_attributes( Class* class, const char* id ) { + int rc = 0; + + int controller_found = 0; // boolean + int fieldmapper_found = 0; // boolean + int tablename_found = 0; // boolean + + xmlAttrPtr attr = class->node->properties; + while( attr ) { + const char* attr_name = (char*) attr->name; + if( !strcmp( (char*) attr_name, "id" ) ) { + ; // ignore; we already grabbed this one + } else if( !strcmp( (char*) attr_name, "controller" ) ) { + controller_found = 1; + xmlChar* value = xmlGetProp( class->node, (xmlChar*) "controller" ); + if( '\0' == *value ) { + printf( "Line %ld: Value of controller attribute is empty in class \"%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + xmlFree( value ); + } else if( !strcmp( (char*) attr_name, "fieldmapper" ) ) { + fieldmapper_found = 1; + xmlChar* value = xmlGetProp( class->node, (xmlChar*) "fieldmapper" ); + if( '\0' == *value ) { + printf( "Line %ld: Value of fieldmapper attribute is empty in class \"%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + xmlFree( value ); + } else if( !strcmp( (char*) attr_name, "label" ) ) { + xmlChar* value = xmlGetProp( class->node, (xmlChar*) "label" ); + if( '\0' == *value ) { + printf( "Line %ld: Value of label attribute is empty in class \"%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + xmlFree( value ); + } else if( !strcmp( (char*) attr_name, "tablename" ) ) { + tablename_found = 1; + xmlChar* value = xmlGetProp( class->node, (xmlChar*) "tablename" ); + if( '\0' == *value ) { + printf( "Line %ld: Value of tablename attribute is empty in class \"%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + xmlFree( value ); + } else if( !strcmp( (char*) attr_name, "virtual" ) ) { + xmlChar* virtual_str = xmlGetProp( class->node, (xmlChar*) "virtual" ); + if( virtual_str ) { + if( !strcmp( (char*) virtual_str, "true" ) ) { + class->is_virtual = 1; + } else if( strcmp( (char*) virtual_str, "false" ) ) { + printf( + "Line %ld: Invalid value \"%s\" for virtual attribute of class\"%s\"\n", + xmlGetLineNo( class->node ), (char*) virtual_str, id ); + rc = 1; + } + xmlFree( virtual_str ); + } + } else if( !strcmp( (char*) attr_name, "readonly" ) ) { + xmlChar* readonly = xmlGetProp( class->node, (xmlChar*) "readonly" ); + if( readonly ) { + if( strcmp( (char*) readonly, "true" ) + && strcmp( (char*) readonly, "false" ) ) { + printf( + "Line %ld: Invalid value \"%s\" for readonly attribute of class\"%s\"\n", + xmlGetLineNo( class->node ), (char*) readonly, id ); + rc = 1; + } + xmlFree( readonly ); + } + } else if( !strcmp( (char*) attr_name, "restrict_primary" ) ) { + xmlChar* value = xmlGetProp( class->node, (xmlChar*) "restrict_primary" ); + if( '\0' == *value ) { + printf( "Line %ld: Value of restrict_primary attribute is empty in class \"%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + xmlFree( value ); + } else if( !strcmp( (char*) attr_name, "core" ) ) { + xmlChar* core = xmlGetProp( class->node, (xmlChar*) "core" ); + if( core ) { + if( strcmp( (char*) core, "true" ) + && strcmp( (char*) core, "false" ) ) { + printf( + "Line %ld: Invalid value \"%s\" for core attribute of class\"%s\"\n", + xmlGetLineNo( class->node ), (char*) core, id ); + rc = 1; + } + xmlFree( core ); + } + } else if( !strcmp( (char*) attr_name, "field_safe" ) ) { + xmlChar* field_safe = xmlGetProp( class->node, (xmlChar*) "field_safe" ); + if( field_safe ) { + if( strcmp( (char*) field_safe, "true" ) + && strcmp( (char*) field_safe, "false" ) ) { + printf( + "Line %ld: Invalid value \"%s\" for field_safe attribute of class\"%s\"\n", + xmlGetLineNo( class->node ), (char*) field_safe, id ); + rc = 1; + } + xmlFree( field_safe ); + } + } else { + printf( "Line %ld: Unrecognized class attribute \"%s\" in class \"%s\"\n", + xmlGetLineNo( class->node ), attr_name, id ); + rc = 1; + } + attr = attr->next; + } // end while + + if( ! controller_found ) { + printf( "Line %ld: No controller attribute for class \"%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + + if( ! fieldmapper_found ) { + printf( "Line %ld: No fieldmapper attribute for class \"\%s\"\n", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + + if( class->is_virtual && tablename_found ) { + printf( "Line %ld: Virtual class \"%s\" shouldn't have a tablename", + xmlGetLineNo( class->node ), id ); + rc = 1; + } + + return rc; +} + +/** + @brief Determine whether fields are either all labeled or all unlabeled. + @param class Pointer to the current Class. + @param id Class id. + @return 1 if errors found, or 0 if not. + + Rule: + - The fields for a given class must either all be labeled or all unlabeled. + + For purposes of this validation, a field is considered labeled even if the label is an + empty string. Empty labels are reported elsewhere. +*/ +static int check_labels( const Class* class, const char* id ) { + int rc = 0; + + int label_found = 0; // boolean + int unlabel_found = 0; // boolean + + Field* field = class->fields; + while( field ) { + if( field->label ) + label_found = 1; + else + unlabel_found = 1; + field = field->next; + } + + if( label_found && unlabel_found ) { + printf( "Class \"%s\" has a mixture of labeled and unlabeled fields\n", id ); + rc = 1; + } + + return rc; +} + +/** + @brief Validate the fields attributes. + @param class Pointer to the current Class. + @param id Class id. + @param fields Pointer to the XML node for the fields element. + @return if errors found, or 0 if not. + + Rules: + - The only valid attributes for the fields element are "primary" and "sequence". + - Neither attribute may have an empty string for a value. + - If there is a sequence attribute, there must also be a primary attribute. + - If there is a primary attribute, the field identified must exist. + - If the primary key field has the datatype "id", there must be a sequence attribute. + - If the datatype of the primary key is not "id" or "int", there must @em not be a + sequence attribute. +*/ +static int val_fields_attributes( Class* class, const char* id, xmlNodePtr fields ) { + int rc = 0; + + xmlChar* sequence = NULL; + xmlChar* primary = NULL; + + // Traverse the attributes + xmlAttrPtr attr = fields->properties; + while( attr ) { + const char* attr_name = (char*) attr->name; + if( !strcmp( attr_name, "primary" ) ) { + primary = xmlGetProp( fields, (xmlChar*) "primary" ); + if( '\0' == primary[0] ) { + printf( + "Line %ld: value of primary attribute is an empty string for class \"%s\"\n", + xmlGetLineNo( fields ), id ); + rc = 1; + } + } else if( !strcmp( attr_name, "sequence" ) ) { + sequence = xmlGetProp( fields, (xmlChar*) "sequence" ); + if( '\0' == sequence[0] ) { + printf( + "Line %ld: value of sequence attribute is an empty string for class \"%s\"\n", + xmlGetLineNo( fields ), id ); + rc = 1; + } + } else { + printf( "Line %ld: Unexpected fields attribute \"%s\" in class \"%s\"\n", + xmlGetLineNo( fields ), attr_name, id ); + rc = 1; + } + + attr = attr->next; + } + + if( sequence && ! primary ) { + printf( "Line %ld: class \"%s\" has a sequence identified but no primary key\n", + xmlGetLineNo( fields ), id ); + rc = 1; + } + + if( primary ) { + // look for the primary key + Field* field = class->fields; + while( field ) { + if( !strcmp( (char*) field->name, (char*) primary ) ) + break; + field = field->next; + } + if( !field ) { + printf( "Primary key field \"%s\" does not exist for class \"%s\"\n", + (char*) primary, id ); + rc = 1; + } else if( DT_ID == field->datatype && ! sequence && ! class->is_virtual ) { + printf( + "Line %ld: Primary key is an id; class \"%s\" should have a sequence attribute\n", + xmlGetLineNo( fields ), id ); + rc = 1; + } else if( DT_ID != field->datatype + && DT_INT != field->datatype + && DT_ORG_UNIT != field->datatype + && sequence ) { + printf( + "Line %ld: Datatype of key for class \"%s\" does not allow a sequence attribute\n", + xmlGetLineNo( fields ), id ); + rc = 1; + } + } + + xmlFree( primary ); + xmlFree( sequence ); + return rc; +} + +/** + @brief Verify that every Link has a matching Field for a given Class. + @param class Pointer to the current class. + @param id Class id. + @return 1 if errors found, or 0 if not. + + Rules: + - For every link element, there must be a matching field element in the same class. + - If the link's reltype is "has_many", the field must be a virtual field of type link + or org_unit. + - If the link's reltype is "has_a" or "might_have", the field must be a non-virtual link + of type link or org_unit. +*/ +static int val_links_to_fields( const Class* class, const char* id ) { + if( !class ) + return 1; + + int rc = 0; + + const Link* link = class->links; + while( link ) { + if( link->field && *link->field ) { + const Field* field = searchFieldByName( class, link->field ); + if( field ) { + if( compareFieldAndLink( class, id, field, link ) ) + rc = 1; + } else { + printf( "\"%s\" class has no corresponding to for \"%s\"\n", + id, (char*) link->field ); + rc = 1; + } + } + link = link->next; + } + + return rc; +} + +/** + @brief Compare matching field and link elements to see if they are compatible + @param class Pointer to the current Class. + @param id Class id. + @param field Pointer to the Field to be compared to the Link. + @param link Pointer to the Link to be compared to the Field. + @return 0 if they are compatible, or 1 if not. + + Rules: + - If the reltype is "has_many", the field must be virtual. + - If a field corresponds to a link, and is not the primary key, then it must have a + datatype "link" or "org_unit". + - If the datatype is "org_unit", the linkage must be to the class "aou". + + Warnings: + - If the reltype is "has_a" or "might_have", the the field should probably @em not + be virtual, but there are legitimate exceptions. + - If the linkage is to the class "aou", then the datatype should probably be "org_unit". +*/ +static int compareFieldAndLink( const Class* class, const char* id, + const Field* field, const Link* link ) { + int rc = 0; + + Datatype datatype = field->datatype; + const char* classref = (const char*) link->classref; + + // Validate the virtuality of the field + if( RT_HAS_A == link->reltype || RT_MIGHT_HAVE == link->reltype ) { + if( warn && field->is_virtual ) { + // This is the child class; field should usually be non-virtual, + // but there are legitimate exceptions. + printf( "WARNING: In class \"%s\": field \"%s\" is tied to a \"has_a\" or " + "\"might_have\" link; perhaps should not be virtual\n", + id, (char*) field->name ); + } + } else if ( RT_HAS_MANY == link->reltype ) { + if( ! field->is_virtual ) { + printf( "In class \"%s\": field \"%s\" is tied to a \"has_many\" link " + "and therefore should be virtual\n", id, (char*) field->name ); + rc = 1; + } + } + + // Validate the datatype of the field + if( class->primary && !strcmp( (char*) class->primary, (char*) field->name ) ) { + ; // For the primary key field, the datatype can be anything + } else if( DT_NONE == datatype || DT_INVALID == datatype ) { + printf( "In class \"%s\": \"%s\" field should have a datatype for linkage\n", + id, (char*) field->name ); + rc = 1; + } else if( DT_ORG_UNIT == datatype ) { + if( strcmp( classref, "aou" ) ) { + printf( "In class \"%s\": \"%s\" field should have a datatype " + "\"link\", not \"org_unit\"\n", id, field->name ); + rc = 1; + } + } else if( DT_LINK == datatype ) { + if( warn && !strcmp( classref, "aou" ) ) { + printf( "WARNING: In class \"%s\", field \"%s\": Consider changing datatype " + "to \"org_unit\"\n", id, (char*) field->name ); + } + } else { + // Datatype should be "link", or maybe "org_unit" + if( !strcmp( classref, "aou" ) ) { + printf( "In class \"%s\": \"%s\" field should have a datatype " + "\"org_unit\" or \"link\"\n", + id, (char*) field->name ); + rc = 1; + } else { + printf( "In class \"%s\": \"%s\" field should have a datatype \"link\"\n", + id, (char*) field->name ); + rc = 1; + } + } + + return rc; +} + +/** + @brief See if every linked field has a counterpart in the links aggregate. + @param class Pointer to the current class. + @param id Class id. + @return 1 if errors found, or 0 if not. + + Rules: + - If a field has a datatype of "link" or "org_unit, there must be a corresponding + entry in the links aggregate. +*/ +static int val_fields_to_links( const Class* class, const char* id ) { + int rc = 0; + const Field* field = class->fields; + while( field ) { + if( DT_LINK != field->datatype && DT_ORG_UNIT != field->datatype ) { + field = field->next; + continue; // not a link? skip it + } + // See if there's a matching entry in the aggregate + const Link* link = class->links; + while( link ) { + if( !strcmp( (char*) field->name, (char*) link->field ) ) + break; + link = link->next; + } + + if( !link ) { + if( !strcmp( (char*) field->name, "id" ) && !strcmp( id, "aou" ) ) { + // Special exception: primary key of "aou" is of + // datatype "org_unit", but it's not a foreign key. + ; + } else { + printf( "In class \"%s\": Linked field \"%s\" has no matching \n", + id, (char*) field->name ); + rc = 1; + } + } + field = field->next; + } + return rc; +} + +/** + @brief Search a given Class for a Field with a given name. + @param class Pointer to the class in which to search. + @param field_name The field name for which to search. + @return Pointer to the Field if found, or NULL if not. +*/ +static const Field* searchFieldByName( const Class* class, const xmlChar* field_name ) { + if( ! class || ! field_name || ! *field_name ) + return NULL; + + const char* name = (const char*) field_name; + const Field* field = class->fields; + while( field ) { + if( field->name && !strcmp( (char*) field->name, name ) ) + return field; + field = field->next; + } + + return NULL; +} + +/** + @brief Validate a fields element. + @param class Pointer to the current Class. + @param id Id of the current Class. + @param fields Pointer to the XML node for the fields element. + @return 1 if errors found, or 0 if not. + + Rules: + - There must be at least one field element. + - No other elements are allowed. + - Text is not allowed, other than white space. + - Comments are allowed (and ignored). +*/ +static int val_fields( Class* class, const char* id, xmlNodePtr fields ) { + int rc = 0; + int field_found = 0; // boolean + + xmlNodePtr child = fields->children; + while( child ) { + const char* child_name = (char*) child->name; + if( xmlNodeIsText( child ) ) { + if( ! xmlIsBlankNode( child ) ) { + // Found unexpected text. After removing leading and + // trailing white space, complain about it. + xmlChar* content = xmlNodeGetContent( child ); + + xmlChar* begin = content; + while( *begin && isspace( *begin ) ) + ++begin; + if( *begin ) { + xmlChar* end = begin + strlen( (char*) begin ) - 1; + while( (isspace( *end ) ) ) + --end; + end[ 1 ] = '\0'; + } + + printf( "Unexpected text in element of class \"%s\": \"%s\"\n", id, + (char*) begin ); + xmlFree( content ); + } + } else if( ! strcmp( child_name, "field" ) ) { + field_found = 1; + if( val_one_field( class, id, child ) ) + rc = 1; + } else if( !strcmp( child_name, "comment" ) ) + ; // ignore comment + else { + printf( "Line %ld: Unexpected <%s> element in of class \"%s\"\n", + xmlGetLineNo( child ), child_name, id ); + rc = 1; + } + child = child->next; + } + + if( !field_found ) { + printf( "No element in class \"%s\"\n", id ); + rc = 1; + } + + return rc; +} + +/** + @brief Validate a field element within a fields element. + @param class Pointer to the current Class. + @param id Class id. + @param field Pointer to the XML node for the field element. + @return 1 if errors found, or 0 if not. + + Rules: + - id attribute must be present with a non-empty value. + - label attribute, if present, must have a non-empty value. + - virtual attribute, if present, must have a value of "true" or "false". +*/ +static int val_one_field( Class* class, const char* id, xmlNodePtr field ) { + int rc = 0; + xmlChar* label = NULL; + xmlChar* field_name = NULL; + int is_virtual = 0; + Datatype datatype = DT_NONE; + + // Traverse the attributes + xmlAttrPtr attr = field->properties; + while( attr ) { + const char* attr_name = (char*) attr->name; + if( !strcmp( attr_name, "name" ) ) { + field_name = xmlGetProp( field, (xmlChar*) "name" ); + } else if( !strcmp( attr_name, "virtual" ) ) { + xmlChar* virt = xmlGetProp( field, (xmlChar*) "virtual" ); + if( !strcmp( (char*) virt, "true" ) ) + is_virtual = 1; + else if( strcmp( (char*) virt, "false" ) ) { + printf( "Line %ld: Invalid value for virtual attribute: \"%s\"\n", + xmlGetLineNo( field ), (char*) virt ); + rc = 1; + } + xmlFree( virt ); + // To do: verify that the namespace is oils_persist + } else if( !strcmp( attr_name, "label" ) ) { + label = xmlGetProp( field, (xmlChar*) "label" ); + if( '\0' == *label ) { + printf( "Line %ld: Empty value for label attribute for class \"%s\"\n", + xmlGetLineNo( field ), id ); + xmlFree( label ); + label = NULL; + rc = 1; + } + // To do: verify that the namespace is reporter + } else if( !strcmp( attr_name, "datatype" ) ) { + xmlChar* dt_str = xmlGetProp( field, (xmlChar*) "datatype" ); + datatype = translate_datatype( dt_str ); + if( DT_INVALID == datatype ) { + printf( "Line %ld: Invalid datatype \"%s\" in class \"%s\"\n", + xmlGetLineNo( field ), (char*) dt_str, id ); + rc = 1; + } + xmlFree( dt_str ); + // To do: make sure that the namespace is reporter + } else if( !strcmp( attr_name, "array_position" ) ) { + ; // Ignore for now, but it should be deprecated + } else if( !strcmp( attr_name, "selector" ) ) { + ; // Ignore for now + } else if( !strcmp( attr_name, "i18n" ) ) { + xmlChar* i18n = xmlGetProp( field, (xmlChar*) "i18n" ); + if( strcmp( (char*) i18n, "true" ) && strcmp( (char*) i18n, "false" ) ) { + printf( "Line %ld: Invalid value for i18n attribute: \"%s\"\n", + xmlGetLineNo( field ), (char*) i18n ); + rc = 1; + } + xmlFree( i18n ); + // To do: verify that the namespace is oils_persist + } else if( !strcmp( attr_name, "primitive" ) ) { + xmlChar* primitive = xmlGetProp( field, (xmlChar*) "primitive" ); + if( strcmp( (char*) primitive, "string" ) && strcmp( (char*) primitive, "number" ) ) { + printf( "Line %ld: Invalid value for primitive attribute: \"%s\"\n", + xmlGetLineNo( field ), (char*) primitive ); + rc = 1; + } + xmlFree( primitive ); + } else { + printf( "Line %ld: Unexpected field attribute \"%s\" in class \"%s\"\n", + xmlGetLineNo( field ), attr_name, id ); + rc = 1; + } + + attr = attr->next; + } + + if( warn && DT_NONE == datatype ) { + printf( "Line %ld: WARNING: No datatype attribute for field \"%s\" in class \"%s\"\n", + xmlGetLineNo( field ), ((char*) field_name ? : ""), id ); + } + + if( ! field_name ) { + printf( "Line %ld: No name attribute for element in class \"%s\"\n", + xmlGetLineNo( field ), id ); + rc = 1; + } else if( '\0' == *field_name ) { + printf( "Line %ld: Field name is empty for element in class \"%s\"\n", + xmlGetLineNo( field ), id ); + rc = 1; + } else { + // Add to the class's field list + Field* new_field = newField( field_name ); + new_field->is_virtual = is_virtual; + new_field->label = label; + new_field->datatype = datatype; + if( addField( class, id, new_field ) ) + rc = 1; + } + + return rc; +} + +/** + @brief Translate a datatype string into a Dataype (an enum). + @param value The value of a datatype attribute. + @return The datatype in the form of an enum. +*/ +static Datatype translate_datatype( const xmlChar* value ) { + const char* val = (const char*) value; + Datatype type; + + if( !value || !*value ) + type = DT_NONE; + else if( !strcmp( val, "bool" ) ) + type = DT_BOOL; + else if( !strcmp( val, "float" ) ) + type = DT_FLOAT; + else if( !strcmp( val, "id" ) ) + type = DT_ID; + else if( !strcmp( val, "int" ) ) + type = DT_INT; + else if( !strcmp( val, "interval" ) ) + type = DT_INTERVAL; + else if( !strcmp( val, "link" ) ) + type = DT_LINK; + else if( !strcmp( val, "money" ) ) + type = DT_MONEY; + else if( !strcmp( val, "number" ) ) + type = DT_NUMBER; + else if( !strcmp( val, "org_unit" ) ) + type = DT_ORG_UNIT; + else if( !strcmp( val, "text" ) ) + type = DT_TEXT; + else if( !strcmp( val, "timestamp" ) ) + type = DT_TIMESTAMP; + else + type = DT_INVALID; + + return type; +} + +/** + @brief Validate a links element. + @param class Pointer to the current Class. + @param id Id of the current Class. + @param links Pointer to the XML node for the links element. + @return 1 if errors found, or 0 if not. + + Rules: + - No elements other than "link" are allowed. + - Text is not allowed, other than white space. + - Comments are allowed (and ignored). + + Warnings: + - There is usually at least one link element. +*/ +static int val_links( Class* class, const char* id, xmlNodePtr links ) { + int rc = 0; + int link_found = 0; // boolean + + xmlNodePtr child = links->children; + while( child ) { + const char* child_name = (char*) child->name; + if( xmlNodeIsText( child ) ) { + if( ! xmlIsBlankNode( child ) ) { + // Found unexpected text. After removing leading and + // trailing white space, complain about it. + xmlChar* content = xmlNodeGetContent( child ); + + xmlChar* begin = content; + while( *begin && isspace( *begin ) ) + ++begin; + if( *begin ) { + xmlChar* end = begin + strlen( (char*) begin ) - 1; + while( (isspace( *end ) ) ) + --end; + end[ 1 ] = '\0'; + } + + printf( "Unexpected text in element of class \"%s\": \"%s\"\n", id, + (char*) begin ); + xmlFree( content ); + } + } else if( ! strcmp( child_name, "link" ) ) { + link_found = 1; + if( val_one_link( class, id, child ) ) + rc = 1; + } else if( !strcmp( child_name, "comment" ) ) + ; // ignore comment + else { + printf( "Line %ld: Unexpected <%s> element in of class \"%s\"\n", + xmlGetLineNo( child ), child_name, id ); + rc = 1; + } + child = child->next; + } + + if( warn && !link_found ) { + printf( "WARNING: No element in class \"%s\"\n", id ); + } + + return rc; +} + +/** + @brief Validate one link element. + @param class Pointer to the current Class. + @param id Id of the current Class. + @param link Pointer to the XML node for the link element. + @return 1 if errors found, or 0 if not. + + Rules: + - The only allowed attributes are "field", "reltype", "key", "map", and "class". + - Except for map, every attribute is required. + - Except for map, every attribute must have a non-empty value. + - The value of the reltype attribute must be one of "has_a", "might_have", or "has_many". +*/ +static int val_one_link( Class* class, const char* id, xmlNodePtr link ) { + int rc = 0; + xmlChar* field_name = NULL; + Reltype reltype = RT_NONE; + xmlChar* key = NULL; + xmlChar* classref = NULL; + + // Traverse the attributes + xmlAttrPtr attr = link->properties; + while( attr ) { + const char* attr_name = (const char*) attr->name; + if( !strcmp( attr_name, "field" ) ) { + field_name = xmlGetProp( link, (xmlChar*) "field" ); + } else if (!strcmp( attr_name, "reltype" ) ) { + ; + xmlChar* rt = xmlGetProp( link, (xmlChar*) "reltype" ); + if( *rt ) { + reltype = translate_reltype( rt ); + if( RT_INVALID == reltype ) { + printf( + "Line %ld: Invalid value \"%s\" for reltype attribute in class \"%s\"\n", + xmlGetLineNo( link ), (char*) rt, id ); + rc = 1; + } + } else { + printf( "Line %ld: Empty value for reltype attribute in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } + xmlFree( rt ); + } else if (!strcmp( attr_name, "key" ) ) { + key = xmlGetProp( link, (xmlChar*) "key" ); + } else if (!strcmp( attr_name, "map" ) ) { + ; // ignore for now + } else if (!strcmp( attr_name, "class" ) ) { + classref = xmlGetProp( link, (xmlChar*) "class" ); + } else { + printf( "Line %ld: Unexpected attribute %s in links element of class \"%s\"\n", + xmlGetLineNo( link ), attr_name, id ); + rc = 1; + } + attr = attr->next; + } + + if( !field_name ) { + printf( "Line %ld: No field attribute found in in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else if( '\0' == *field_name ) { + printf( "Line %ld: Field name is empty for element in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else if( !reltype ) { + printf( "Line %ld: No reltype attribute found in in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else if( !key ) { + printf( "Line %ld: No key attribute found in in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else if( '\0' == *key ) { + printf( "Line %ld: key attribute is empty for element in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else if( !classref ) { + printf( "Line %ld: No class attribute found in in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else if( '\0' == *classref ) { + printf( "Line %ld: class attribute is empty for element in class \"%s\"\n", + xmlGetLineNo( link ), id ); + rc = 1; + } else { + // Add to Link list + Link* new_link = newLink( field_name ); + new_link->reltype = reltype; + new_link->key = key; + new_link->classref = classref; + if( addLink( class, id, new_link ) ) + rc = 1; + } + + return rc; +} + +/** + @brief Translate an attribute value into a Reltype (an enum). + @param value The value of a reltype attribute. + @return The value of the attribute translated into the enum Reltype. +*/ +static Reltype translate_reltype( const xmlChar* value ) { + const char* val = (char*) value; + Reltype reltype; + + if( !val || !*val ) + reltype = RT_NONE; + else if( !strcmp( val, "has_a" ) ) + reltype = RT_HAS_A; + else if( !strcmp( val, "might_have" ) ) + reltype = RT_MIGHT_HAVE; + else if( !strcmp( val, "has_many" ) ) + reltype = RT_HAS_MANY; + else + reltype = RT_INVALID; + + return reltype; +} + +/** + @brief Build a list of classes, while checking for several errors. + @param doc Pointer to the xmlDoc loaded from the IDL. + @return 1 if errors found, or 0 if not. + + Rules: + - Every child element of the root must be of the element "class". + - No text is allowed, other than white space, between classes. + - Comments are allowed (and ignored) between classes. +*/ +static int scan_idl( xmlDocPtr doc ) { + int rc = 0; + + xmlNodePtr child = xmlDocGetRootElement( doc )->children; + while( child ) { + char* child_name = (char*) child->name; + if( xmlNodeIsText( child ) ) { + if( ! xmlIsBlankNode( child ) ) { + // Found unexpected text. After removing leading and + // trailing white space, complain about it. + xmlChar* content = xmlNodeGetContent( child ); + + xmlChar* begin = content; + while( *begin && isspace( *begin ) ) + ++begin; + if( *begin ) { + xmlChar* end = begin + strlen( (char*) begin ) - 1; + while( (isspace( *end ) ) ) + --end; + end[ 1 ] = '\0'; + } + + printf( "Unexpected text between class elements: \"%s\"\n", + (char*) begin ); + xmlFree( content ); + } + } else if( !strcmp( child_name, "class" ) ) { + if( register_class( child ) ) + rc = 1; + } else if( !strcmp( child_name, "comment" ) ) + ; // ignore comment + else { + printf( "Line %ld: Unexpected <%s> element under root\n", + xmlGetLineNo( child ), child_name ); + rc = 1; + } + + child = child->next; + } + return rc; +} + +/** + @brief Register a class. + @param class Pointer to the class node. + @return 1 if errors found, or 0 if not. + + Rules: + - Every class element must have an "id" attribute. + - A class id must not be an empty string. + - Every class id must be unique. + + Warnings: + - A class id normally consists entirely of lower case letters, digits and underscores. + - A class id longer than 12 characters is suspiciously long. +*/ +static int register_class( xmlNodePtr class ) { + int rc = 0; + xmlChar* id = xmlGetProp( class, (xmlChar*) "id" ); + + if( ! id ) { + printf( "Line %ld: Class has no \"id\" attribute\n", xmlGetLineNo( class ) ); + rc = 1; + } else if( ! *id ) { + printf( "Line %ld: Class id is an empty string\n", xmlGetLineNo( class ) ); + rc = 1; + } else { + + // In principle a class id could contain any arbitrary characters, but in practice + // anything but lower case, digits, and underscores is probably a mistake. + const xmlChar* p = id; + while( *p ) { + if( islower( *p ) || isdigit( *p ) || '_' == *p ) + ++p; + else if( warn ) { + printf( "Line %ld: WARNING: Dubious class id \"%s\"; not all lower case, " + "digits, and underscores\n", xmlGetLineNo( class ), (char*) id ); + break; + } + } + + // Warn about a suspiciously long id + if( warn && strlen( (char*) id ) > 12 ) { + printf( "Line %ld: WARNING: Class id is unusually long: \"%s\"\n", + xmlGetLineNo( class ), (char*) id ); + } + + // Add the classname to the list of classes. If the size of + // the list doesn't change, then we must have a duplicate. + Class* entry = newClass( class ); + unsigned long class_count = osrfHashGetCount( classes ); + osrfHashSet( classes, entry, (char*) id ); + if( osrfHashGetCount( classes ) == class_count ) { + printf( "Line %ld: Duplicate class name \"%s\"\n", + xmlGetLineNo( class ), (char*) id ); + rc = 1; + } + xmlFree( id ); + } + return rc; +} + +/** + @brief Add a field to a class's field list (unless the id collides with an earlier entry). + @param class Pointer to the current class. + @param id The class id. + @param new_field Pointer to the Field to be added. + @return 0 if successful, or 1 if not (probably due to a duplicate key). + + If the id collides with a previous entry, we free the new Field instead of adding it + to the list. If the label collides with a previous entry, we complain, but we go + ahead and add the Field to the list. + + RULES: + - Each field name should be unique within the fields element. + - Each label should be unique within the fields element. +*/ +static int addField( Class* class, const char* id, Field* new_field ) { + if( ! class || ! new_field ) + return 1; + + int rc = 0; + int dup_name = 0; + + // See if the class has any other fields with the same name or label. + const Field* old_field = class->fields; + while( old_field ) { + + // Compare the ids + if( !strcmp( (char*) old_field->name, (char*) new_field->name ) ) { + printf( "Duplicate field name \"%s\" in class \"%s\"\n", + (char*) new_field->name, id ); + dup_name = 1; + rc = 1; + break; + } + + // Compare the labels. if they're both non-empty + if( old_field->label && *old_field->label + && new_field->label && *new_field->label + && !strcmp( (char*) old_field->label, (char*) new_field->label )) { + printf( "Duplicate labels \"%s\" in class \"%s\"\n", + (char*) old_field->label, id ); + rc = 1; + } + + old_field = old_field->next; + } + + if( dup_name ) { + free( new_field ); + } else { + new_field->next = class->fields; + class->fields = new_field; + } + + return rc; +} + +/** + @brief Add a Link to the Link list of a specified Class (unless it's a duplicate). + @param class Pointer to the Class to whose list to add the Link. + @param id Class id. + @param new_link Pointer to the Link to be added. + @return 0 if successful, or 1 if not (probably due to a duplicate). + + If there's already a Link in the list with the same field name, free the new Link + instead of adding it. +*/ +static int addLink( Class* class, const char* id, Link* new_link ) { + if( ! class || ! new_link ) + return 1; + + int rc = 0; + int dup_name = 0; + + // See if the class has any other links with the same field + const Link* old_link = class->links; + while( old_link ) { + + if( !strcmp( (char*) old_link->field, (char*) new_link->field ) ) { + printf( "Duplicate field name \"%s\" in links of class \"%s\"\n", + (char*) old_link->field, id ); + rc = 1; + dup_name = 1; + break; + } + + old_link = old_link->next; + } + + if( dup_name ) { + freeLink( new_link ); + } else { + // Add to the linked list + new_link->next = class->links; + class->links = new_link; + } + + return rc; +} + +/** + @brief Create and initialize a new Class. + @param node Pointer to the XML node for a class element. + @return Pointer to the newly created Class. + + The calling code is responsible for freeing the Class by calling freeClass(). In practice + this happens automagically when we free the osrfHash classes. +*/ +static Class* newClass( xmlNodePtr node ) { + Class* class = safe_malloc( sizeof( Class ) ); + class->node = node; + class->loaded = 0; + class->is_virtual = 0; + xmlFree( class->primary ); + class->fields = NULL; + class->links = NULL; + return class; +} + +/** + @brief Free a Class and everything it owns. + @param key The class id (not used). + @param p A pointer to the Class to be freed, cast to a void pointer. + + This function is designed to be a freeItem callback for an osrfHash. +*/ +static void freeClass( char* key, void* p ) { + Class* class = p; + + // Free the linked list of Fields + Field* next_field = NULL; + Field* field = class->fields; + while( field ) { + next_field = field->next; + freeField( field ); + field = next_field; + } + + // Free the linked list of Links + Link* next_link = NULL; + Link* link = class->links; + while( link ) { + next_link = link->next; + freeLink( link ); + link = next_link; + } + + free( class ); +} + +/** + @brief Allocate and initialize a Field. + @param name Field name. + @return Pointer to a new Field. + + It is the responsibility of the caller to free the Field by calling freeField(). +*/ +static Field* newField( xmlChar* name ) { + Field* field = safe_malloc( sizeof( Field ) ); + field->next = NULL; + field->name = name; + field->is_virtual = 0; + field->label = NULL; + field->datatype = DT_NONE; + return field; +} + +/** + @brief Free a Field and everything in it. + @param field Pointer to the Field to be freed. +*/ +static void freeField( Field* field ) { + if( field ) { + xmlFree( field->name ); + if( field->label ) + xmlFree( field->label ); + free( field ); + } +} + +/** + @brief Allocate and initialize a Link. + @param field Field name. + @return Pointer to a new Link. + + It is the responsibility of the caller to free the Link by calling freeLink(). +*/ +static Link* newLink( xmlChar* field ) { + Link* link = safe_malloc( sizeof( Link ) ); + link->next = NULL; + link->field = field; + link->reltype = RT_NONE; + link->key = NULL; + link->classref = NULL; + return link; +} + +/** + @brief Free a Link and everything it owns. + @param link Pointer to the Link to be freed. +*/ +static void freeLink( Link* link ) { + if( link ) { + xmlFree( link->field ); + xmlFree( link->key ); + xmlFree( link->classref ); + free( link ); + } +}