option explicit

!INC Local Scripts.EAConstants-VBScript

'
' Script 	Name: ArcGISModelValidation
' Author: 	Sparx Systems Pty Ltd
' Purpose: 	Check the semantics of a UML model that represents an ArcGIS schema.
'
' Date:		September 19, 2016
'


''''''''''''''''''''''
''
'' Validation Codes
''
''''''''''''''''''''''

' Workspace properties
Const MVR_WS_STEREO = "MVR-WS-1;Missing stereotype;This package is not an ArcGIS Workspace package (missing stereotype <<ArcGIS>>). Ensure you have selected the Workspace package in the Project Browser. Aborting validation."
Const MVR_WS_PKG = "MVR-WS-2;Invalid Workspace Package;This package is a model root node, not an ArcGIS Workspace package. Aborting validation."
Const MVR_SR_MISSING = "MVR-WS-3;Missing SpatialReference elements;A Workspace must contain one or more elements stereotyped: SpatialReference."

' Feature Dataset properties
Const MVR_FD_NAME = "MVR-FD-1;Illegal characters in name;Feature Dataset name contains spaces or special characters. These will be converted to underscores when imported by the geodatabase."
Const MVR_FD_NESTED = "MVR-FD-2;Nested feature dataset;Feature Dataset packages cannot be nested."

' Feature Class properties
Const MVR_FC_NAME = "MVR-FC-1;Illegal characters in name;Feature/Object Class name contains spaces or special characters, or begins with a numeric character. Replace these with underscores, or remove them."
Const MVR_FC_LONG_NAME = "MVR-FC-2;Name > 32 characters;Names longer than 32 characters are invalid for some geodatabase implementations."
Const MVR_FC_DUPLICATE_NAME = "MVR-FC-3;Duplicate class name;Feature/Object Class name was already defined in this workspace."
Const MVR_FC_NAME_PREFIX = "MVR-FC-4;Illegal class name prefix;Feature/Object Class name has prefix of 'gdb_', 'sde_' or 'delta_'. This prefix is not supported by ArcGIS for Tables and Feature Class names." ' see: http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#//002200000002000000.htm
Const MVR_FC_OID_FIELD_TAG_MISSING_ATT = "MVR-FC-5;Invalid OID Field;The UML attribute that was specified as the Object ID field for this feature class does not exist in this class. To specify a different UML Attribute, use the OIDFieldName Tagged Value on this class."
Const MVR_FC_OID_FIELD_TAG_INVALID_ATT_TYPE = "MVR-FC-6;Invalid OID Field;The UML attribute that was specified as the Object ID field for this feature class does not have a data type of esriFieldTypeOID. To specify a different UML Attribute, use the OIDFieldName Tagged Value on this class."
Const MVR_FC_SHAPE_FIELD_TAG_MISSING_ATT = "MVR-FC-7;Invalid Shape Field;The UML attribute that was specified as the Shape field for this feature class does not exist in this class. To specify a different UML Attribute, use the ShapeFieldName Tagged Value on this class."
Const MVR_FC_SHAPE_FIELD_TAG_INVALID_ATT_TYPE = "MVR-FC-8;Invalid Shape Field;The UML attribute that was specified as the Shape field for this feature class does not have a data type of esriFieldTypeGeometry. To specify a different UML Attribute, use the ShapeFieldName Tagged Value on this class."
Const MVR_FC_MISSING_OID_FIELD = "MVR-FC-9;Missing Object ID Field;No Object ID was found for this feature class. A default field named OBJECTID will be added."
Const MVR_FC_MISSING_SHAPE_FIELD = "MVR-FC-10;Missing Shape Field;No Shape field was found for this feature class. A default field named Shape will be added."
Const MVR_FC_MISSING_SHAPE_LENGTH_FIELD = "MVR-FC-11;Missing Shape Length Field or LengthFieldName Tag;No Shape_Length field was found for this feature class or the LengthFieldName Tag is empty. A default Shape_Length field will be added."
Const MVR_FC_MISSING_SHAPE_AREA_FIELD = "MVR-FC-12;Missing Shape Area Field or AreaFieldName Tag;No Shape_Area field was found for this feature class or the AreaFieldName Tag is empty. A default Shape_Area field will be added."
Const MVR_FC_MISSING_ATTRIBUTE_INDEX = "MVR-FC-13;Missing Attribute Index;No Attribute Index was found for this feature class. A default index will be added for the Object ID field."
Const MVR_FC_MISSING_SPATIAL_INDEX = "MVR-FC-14;Missing Spatial Index;No Spatial Index was found for this feature class. A default index will be added for the Shape field."

' Object Class properties
Const MVR_OC_FEATURE_DATASET = "MVR-OC-1;Object Class in feature dataset;Object Class is defined within a Feature Dataset package. Move it to a non-feature dataset package."
Const MVR_OC_OID_FIELD_TAG_MISSING_ATT = "MVR-OC-2;Invalid OID Field;The UML attribute that was specified as the Object ID field for this table (object class) does not exist in this class. To specify a different UML Attribute, use the OIDFieldName Tagged Value on this class."
Const MVR_OC_OID_FIELD_TAG_INVALID_ATT_TYPE = "MVR-OC-3;Invalid OID Field;The UML attribute that was specified as the Object ID field for this table (object class) does not have a data type of esriFieldTypeOID. To specify a different UML Attribute, use the OIDFieldName Tagged Value on this class."
Const MVR_OC_MISSING_OID_FIELD = "MVR-OC-4;Missing Object ID Field;No Object ID was found for this table (object class). A default field named OBJECTID will be added."
Const MVR_OC_MISSING_ATTRIBUTE_INDEX = "MVR-OC-5;Missing Attribute Index;No Attribute Index was found for this table (object class). A default index will be added for the Object ID field."

' CodedValueDomain properties
Const MVR_CVD_NAME_ILLEGAL_CHARS = "MVR-CVD-1;Illegal characters in name;Coded Value Domain name contains apostrophe or single quote characters."
Const MVR_CVD_NAME_SPEC_CHARS = "MVR-CVD-2;Special characters in name;Coded Value Domain name contains spaces or special characters, or begins with a numeric character. Replace these with underscores, or remove them."
Const MVR_CVD_LONG_NAME = "MVR-CVD-3;Name > 32 characters;Coded Value Domain name is longer than 32 characters. Consider using a shorter name."
Const MVR_CVD_DUPLICATE_NAME = "MVR-CVD-4;Duplicate Domain name;Coded Value Domain name was already defined in this workspace."
Const MVR_CVD_INVALID_TYPE = "MVR-CVD-5;Invalid Domain type;Coded Value Domain FieldType must be one of: esriFieldTypeInteger, esriFieldTypeSmallInteger, esriFieldTypeDouble, esriFieldTypeSingle, esriFieldTypeString, esriFieldTypeDate."
Const MVR_CVD_MISSING_DESCRIPTION = "MVR-CVD-6;Missing Domain Description;Found Coded Value Domain with empty Description tagged value."

' RangeDomain properties
Const MVR_RD_DUPLICATE_NAME = "MVR-RD-1;Duplicate Range Domain name;Range Domain name was already defined in this workspace."
Const MVR_RD_INVALID_TYPE = "MVR-RD-2;Invalid Range Domain type;Range Domain FieldType must be one of: esriFieldTypeInteger, esriFieldTypeSmallInteger, esriFieldTypeDouble, esriFieldTypeSingle, esriFieldTypeDate." ' See: http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#/A_quick_tour_of_attribute_domains/001s00000001000000/
Const MVR_RD_INVALID_MIN_VAL = "MVR-RD-3;Invalid Range Domain Min Value;Found non-numeric minimum value for a Range Domain of numeric type."
Const MVR_RD_INVALID_MAX_VAL = "MVR-RD-4;Invalid Range Domain Max Value;Found non-numeric maximum value for a Range Domain of numeric type."
Const MVR_RD_INVALID_MIN_VAL_DATE = "MVR-RD-5;Invalid Range Domain Min Date Value;Found incompatible minimum value for a Range Domain of type esriFieldTypeDate."
Const MVR_RD_INVALID_MAX_VAL_DATE = "MVR-RD-6;Invalid Range Domain Max Date Value;Found incompatible maximum value for a Range Domain of type esriFieldTypeDate."

' DomainCodedValue properties
Const MVR_DCV_DUPLICATE_NAME = "MVR-DVC-1;Duplicate Domain Code;Domain Coded Value name was already defined for this Domain element."
Const MVR_DCV_DUPLICATE_VAL = "MVR-DVC-2;Duplicate Domain Value;Domain Coded Value was already defined for this Domain element."
Const MVR_DCV_MISSING_VAL = "MVR-DVC-3;Missing Domain Value;Domain Coded Value was not specified. A value must be specified in the UML Attribute's Initial Value."

' Spatial Reference values
Const MVR_SR_MISSING_VAL = "MVR-SR-1;Missing SpatialReference value;This element must specify a SpatialReference value"
Const MVR_SR_INVALID_LINK = "MVR-SR-2;Invalid SpatialReference value;The specified SpatialReference refers to an element that was not found in this workspace."

' Subtypes
Const MVR_ST_LONG_NAME = "MVR-ST-1;Name > 32 characters;Names longer than 32 characters are invalid for some geodatabase implementations."
Const MVR_ST_MISSING_REL = "MVR-ST-2;Missing Subtype Relationship;Subtype elements should have one or more relationships of stereotype <<Subtype>> to a Feature Class or Object Class."
Const MVR_ST_INVALID_PARENT = "MVR-ST-3;Invalid parent element for subtype;A relationship stereotyped <<Subtype>> must have a Feature Class or Object Class as its target."
Const MVR_ST_MISSING_PARENT = "MVR-ST-4;Missing parent element for subtype;One or more parent elements of this Subtype could not be located in this workspace."
Const MVR_ST_MISSING_CODE = "MVR-ST-5;Missing Subtype Code;Subtype elements must specify a subtype code on the <<subtype>> element tagged value, or via a <<SubtypeField>> Attribute Initial Value."
Const MVR_ST_INVALID_CODE = "MVR-ST-6;Invalid Subtype Code;Subtype Code must be an integer value."
Const MVR_ST_DUPLICATE_CODE = "MVR-ST-7;Non-unique Subtype Code;Subtype Code must be unique across all subtypes of a given Feature Class or Object Class."
Const MVR_ST_INVALID_ATT_NAME = "MVR-ST-8;Invalid Subtype Attribute Name;Each Subtype attribute (field) name must match a field in the parent Feature Class or Object Class. Expected to find field named: "
Const MVR_ST_ATT_TYPE_MISMATCH = "MVR-ST-9;Type mismatch in Subtype Attribute;Subtype attribute (field) types must match the parent field type. Expected to find field type: "
Const MVR_ST_LENGTH_MISMATCH = "MVR-ST-10;Length mismatch in Subtype Attribute;Subtype attribute (field) length value must match the parent field. Expected to find field length: "
Const MVR_ST_PRECISION_MISMATCH = "MVR-ST-11;Precision mismatch in Subtype Attribute;Subtype attribute (field) precision value must match the parent field. Expected to find field precision: "
Const MVR_ST_SCALE_MISMATCH = "MVR-ST-12;Scale mismatch in Subtype Attribute;Subtype attribute (field) scale value must match the parent field. Expected to find field scale: "

' Fields and Attributes
Const MVR_FL_NAME = "MVR-FL-1;Illegal characters in name;Field name contains spaces or special characters, or begins with a numeric character. Replace these with underscores, or remove them."
Const MVR_FL_LONG_NAME = "MVR-FL-2;Name > 32 characters;Names longer than 32 characters are invalid for some geodatabase implementations."
Const MVR_FL_DUPLICATE_NAME = "MVR-FL-3;Duplicate field name;Field name already defined in this class."
Const MVR_FL_DUPLICATE_NAME_INHERITED = "MVR-FL-4;Duplicate field inherited;Field name in this class also detected in parent class: "
Const MVR_FL_DUPLICATE_NAME_INHERITED_EX = "MVR-FL-5;Duplicate parent fields;Field name inherited by this class detected in multiple parent classes: "
Const MVR_FL_NAME_RESERVED = "MVR-FL-6;Invalid field name;Field name is a database reserved word."
Const MVR_FL_TYPE_LONGINT_MISSING_PRECISION = "MVR-FL-7;Missing Precision value;Fields with with type esriFieldTypeInteger (Long Int) must have a precision value specified between 0-10."
Const MVR_FL_TYPE_LONGINT_INVALID_PRECISION = "MVR-FL-8;Invalid Precision value;Non-numeric Precision value detected. Precision value for esriFieldTypeInteger (Long Int) must be an integer between 0-10."
Const MVR_FL_TYPE_LONGINT_OUT_OF_BOUNDS_PRECISION = "MVR-FL-9;Invalid Precision value;Out-of-bounds precision value detected. Precision values for esriFieldTypeInteger (Long Int) must be an integer between 0-10."
Const MVR_FL_TYPE_TEXT_MISSING_LENGTH = "MVR-FL-10;Missing Length value;Length value for esriFieldTypeString not specified. ArcGIS default is 50."
Const MVR_FL_TYPE_TEXT_INVALID_LENGTH = "MVR-FL-11;Invalid Length value;Non-numeric Length value detected for esriFieldTypeString."
Const MVR_FL_TYPE_TEXT_OUT_OF_BOUNDS_LENGTH = "MVR-FL-12;Invalid Length value;Out-of-bounds Length value detected for esriFieldTypeString. Length values for esriFieldTypeString (Text) must be an integer between 1-255."
Const MVR_FL_TYPE_MISSING_SCALE = "MVR-FL-13;Missing Scale value;Attributes with Double or Float data types must have a scale value set."
Const MVR_FL_TYPE_INVALID_SCALE = "MVR-FL-14;Invalid Scale value;Non-numeric Scale value detected for esriFieldTypeDouble or esriFieldTypeSingle (Float). Scale values for this type must be an integer between 1-9."
Const MVR_FL_TYPE_OUT_OF_BOUNDS_SCALE_FLOAT = "MVR-FL-15;Invalid Scale value;Out-of-bounds Scale value detected for esriFieldTypeSingle (Float). Scale values for this type must be an integer between 1-6."
Const MVR_FL_TYPE_OUT_OF_BOUNDS_SCALE_DOUBLE = "MVR-FL-16;Invalid Scale value;Out-of-bounds Scale value detected for esriFieldTypeDouble. Scale values for this type must be an integer between 0-15."

Const MVR_FL_TYPE_MISSING_PRECISION = "MVR-FL-17;Missing Precision value;Attributes with Double or Float data types must have a Precision value set."
Const MVR_FL_TYPE_INVALID_PRECISION = "MVR-FL-18;Invalid Precision value;Non-numeric Precision value detected for esriFieldTypeDouble or esriFieldTypeSingle (Float). Precision value must be an integer."
Const MVR_FL_TYPE_OUT_OF_BOUNDS_PRECISION_FLOAT = "MVR-FL-19;Invalid Precision value;Out-of-bounds Precision value detected for esriFieldTypeSingle (Float). Precision value for this type must be an integer between 1-6."
Const MVR_FL_TYPE_OUT_OF_BOUNDS_PRECISION_DOUBLE = "MVR-FL-20;Invalid Precision value;Out-of-bounds Precision value detected for esriFieldTypeDouble. Precision value for this type must be an integer between 7-15."

Const MVR_FL_TYPE_UNDEFINED_DOMAIN = "MVR-FL-21;Undefined Attribute Domain;The specified Attribute Type refers to an element that was not found in this workspace: "
Const MVR_FL_TYPE_UNDEFINED_DOMAIN_VAL = "MVR-FL-22;Undefined Attribute Domain Value;The specified Attribute Value is not a valid value for this Domain: "
Const MVR_FL_TYPE_UNLINKED_DOMAIN = "MVR-FL-23;Unlinked Attribute Domain;The specified Attribute Type refers to a domain element, but is not explicitly linked to that element. To link the Attribute Type to its corresponding Domain element, use the ellipsis button next to the Type field in the Attribute Properties dialog."
Const MVR_FL_TYPE_MISSING = "MVR-FL-24;Unspecified Attribute Type;No Type was specified for this Attribute."

Const MVR_FL_TYPE_MULTIPLE_GID = "MVR-FL-25;Duplicate esriFieldTypeGlobalID field;A field of type esriFieldTypeGlobalID was defined multiple times within the same class. ArcGIS does not support this."
Const MVR_FL_TYPE_MULTIPLE_GID_EX = "MVR-FL-26;Duplicate esriFieldTypeGlobalID field inherited;An inherited field of type esriFieldTypeGlobalID was detected and a field of the same type exists either in this class or one of its parents. ArcGIS does not support this."

Const MVR_FL_SUBTYPE_INVALID_TYPE = "MVR-FL-27;Invalid Field Type;Subtype Code Field must be of type esriFieldTypeInteger (Long Int) or esriFieldTypeSmallInteger (Short Int)."
Const MVR_FL_SUBTYPE_INVALID_CODE = "MVR-FL-28;Invalid Default Subtype Code;The default value of a Subtype Code Field must match a code specified by a descendant subtype."
Const MVR_FL_SUBTYPE_MISSING_DEFAULT_CODE = "MVR-FL-29;Missing Default Subtype Code;No default value was specified for this Subtype Field. Use the UML Attribute's Initial Value field."
Const MVR_FL_SUBTYPE_NON_NUMERIC_DEFAULT_CODE = "MVR-FL-30;Non-numeric Default Subtype Code;Non-numeric value detected for the default value of this Subtype Field. Specify an integer value instead."

Const MVR_FL_OBSOLETE_HASM = "MVR-FL-31;Obsolete HasM Value;Detected HasM value in the Shape Field's GeometryDef Tagged Value. Use the Class HasM Tagged Value instead."
Const MVR_FL_OBSOLETE_HASZ = "MVR-FL-32;Obsolete HasZ Value;Detected HasZ value in the Shape Field's GeometryDef Tagged Value. Use the Class HasZ Tagged Value instead."

Const MVR_FL_MISSING_STEREO = "MVR-FL-33;Field Missing Stereotype;This attribute does not have a stereotype. It is assumed to be a field."

Const MVR_FL_DEFAULT_VAL_INT_TYPE_MISMATCH = "MVR-FL-34;Invalid Default Value;Non-integer default value was specified for an integer field type."
Const MVR_FL_DEFAULT_VAL_DATE_TYPE_MISMATCH = "MVR-FL-35;Invalid Default Value;Non-date default value was specified for a date field type."

Const MVR_FL_REQ_OID_DUPLICATE = "MVR-FL-36;Duplicate Object ID Field;Detected multiple fields with type esriFieldTypeOID. Only one Object ID Field is allowed per class."
Const MVR_FL_REQ_SHAPE_DUPLICATE = "MVR-FL-37;Duplicate Shape Field;Detected multiple fields with type esriFieldTypeGeometry. Only one Shape Field is allowed per class."
Const MVR_FL_IDX_SPATIAL_DUPLICATE = "MVR-FL-38;Duplicate Spatial Index;Detected multiple fields with stereotype SpatialIndex. Only one Spatial Index is allowed per feature class."
Const MVR_FL_INVALID_REQUIRED_TAG = "MVR-FL-39;Invalid Required Tagged Value;The Tagged Value named Required, for this attribute, must have a value of true."

Const MVR_FL_INVALID_GRIDSIZE = "MVR-FL-40;Invalid GridSize0 Value;The value of GridSize0 is invalid. It must be an integer or a double between -2.2e308 and 1.8e308. If this model is exported, the value will be set to 0."
Const MVR_FL_INVALID_AVGNUMPOINTS = "MVR-FL-41;Invalid AvgNumPoints Value;The value of AvgNumPoints is invalid. It must be an integer between -2,147,483,648 and 2,147,483,647. If this model is exported, the value will be set to 0."
Const MVR_FL_MISSING_GRIDSIZE = "MVR-FL-42;Missing GridSize0 Value;The value of GridSize0 is missing. If this model is exported, a default value of 0 will be used."
Const MVR_FL_MISSING_AVGNUMPOINTS = "MVR-FL-43;Missing AvgNumPoints Value;The value of AvgNumPoints is missing. If this model is exported, a default value of 0 will be used."
' There may not be a precise rule that can be used to determine whether esriFieldTypeGUID fields should allow null values
'Const MVR_FL_TYPE_ISNULLABLE_CONFLICT = "MVR-FL-40;IsNullable set for esriFieldTypeGUID;Fields of type esriFieldTypeGUID should have the IsNullable Tagged Value set to false."

' RelationshipClass
Const MVR_RC_MISSING_NAME = "MVR-RC-1;Missing name;Relationship Class name was not defined. Name must contain at least 1 character."
Const MVR_RC_INVALID_NAME = "MVR-RC-2;Illegal characters in name;Relationship Class name contains spaces or special characters, or begins with a numeric character. Replace these with underscores, or remove them."
Const MVR_RC_LONG_NAME = "MVR-RC-3;Name > 32 characters;Names longer than 32 characters are invalid for some geodatabase implementations."
Const MVR_RC_DUPLICATE_NAME = "MVR-RC-4;Duplicate Relationship Class name;Relationship Class name was already defined in this workspace."

Const MVR_RC_MISSING_DESTINATION = "MVR-RC-5;Missing Destination Class;The Destination Class for this Relationship Class was not found in this workspace."
Const MVR_RC_INVALID_DESTINATION = "MVR-RC-6;Invalid Destination Class;The Destination Class for must be a Feature Class or a Table."
Const MVR_RC_MISSING_ORIG_PK = "MVR-RC-7;Missing Origin Primary Key;The Origin Primary Key was not specified for this Relationship Class."
Const MVR_RC_INVALID_ORIG_PK = "MVR-RC-8;Invalid Origin Primary Key;The Origin Primary Key was not found in the Origin Class for this Relationship Class."
Const MVR_RC_MISSING_ORIG_FK = "MVR-RC-9;Missing Origin Foreign Key;The Origin Foreign Key was not specified for this Relationship Class."
Const MVR_RC_INVALID_ORIG_FK = "MVR-RC-10;Invalid Origin Foreign Key;The Origin Foreign Key was not found in the Destination or Association Class for this Relationship Class."
Const MVR_RC_MISSING_DEST_PK = "MVR-RC-11;Missing Destination Primary Key;The Destination Primary Key was not specified for this Relationship Class."
Const MVR_RC_INVALID_DEST_PK = "MVR-RC-12;Invalid Destination Primary Key;The Destination Primary Key was not found in the Association Class for this Relationship Class."
Const MVR_RC_MISSING_DEST_FK = "MVR-RC-13;Missing Destination Foreign Key;The Destination Foreign Key was not specified for this Relationship Class."
Const MVR_RC_INVALID_DEST_FK = "MVR-RC-14;Invalid Destination Foreign Key;The Destination Foreign Key was not found in the Association Class for this Relationship Class."

Const MVR_RC_MISSING_ASSOC_CLASS = "MVR-RC-15;Missing Association Class;The Association Class for this attributed Relationship Class was not found in this workspace."
Const MVR_RC_INVALID_CARD = "MVR-RC-16;Invalid Cardinality for Non-attributed Relationship;Detected M-N relationship, with no Association Class defined."
Const MVR_RC_MISSING_KEY_TYPE = "MVR-RC-17;Missing Key Type;No data type was specified for primary or foreign key field."
Const MVR_RC_INVALID_KEY_TYPE = "MVR-RC-18;Invalid Key Type;The specified data type is not valid for a primary or foreign key field: "
Const MVR_RC_KEY_TYPE_MISMATCH = "MVR-RC-19;Non-Matching Primary and Foreign Key Types;Detected the following non-matching Primary and Foreign Key types: "
Const MVR_RC_INCOMPATIBLE_FK_TYPE = "MVR-RC-20;Incompatible Foreign Key Type;A Primary Key of type esriFieldTypeOID was detected. The corresponding Foreign Key must be of type: esriFieldTypeInteger."
Const MVR_RC_OID_FIELD_TAG_MISSING_ATT = "MVR-RC-21;Invalid OID Field;The UML attribute that was specified as the Object ID field for this Relationship Class does not exist in the UML Association Class. To specify a different UML Attribute, use the OIDFieldName Tagged Value on the UML Association Class."
Const MVR_RC_OID_FIELD_TAG_INVALID_ATT_TYPE = "MVR-RC-22;Invalid OID Field;The UML attribute that was specified as the Object ID field for this Relationship Class does not have a data type of esriFieldTypeOID. To specify a different UML Attribute, use the OIDFieldName Tagged Value on the UML Association Class."

' Stereotypes and Tagged Values
Const MVR_EL_STEREO = "MVR-CL-1;Invalid class stereotype;This non-Abstract class does not have a recognized ArcGIS UML stereotype. Skipping this class."
Const MVR_TV_MISSING = "MVR-TV-1;Missing required property;Expected Tag with boolean value (true/false)missing: "


''''''''''''''''''''''
''
'' Default values and constants for validation config options and DBMS-specific values
''
''''''''''''''''''''''

Const GDB_RESERVED_WORDS = ",ADD,ALTER,AND,AS,ASC,BETWEEN,BY,COLUMN,CREATE,DATE,DELETE,DESC,DROP,EXISTS,FOR,FROM,IN,INSERT,INTO,IS,LIKE,NOT,NULL,OR,ORDER,SELECT,SET,TABLE,UPDATE,VALUES,WHERE,"
Const SQL_RESERVED_WORDS = ",ADD,ALL,ALTER,AND,ANY,AS,ASC,AUTHORIZATION,BACKUP,BEGIN,BETWEEN,BREAK,BROWSE,BULK,BY,CASCADE,CASE,CHECK,CHECKPOINT,CLOSE,CLUSTERED,COALESCE,COLLATE,COLUMN,COMMIT,COMPUTE,CONSTRAINT,CONTAINS,CONTAINSTABLE,CONTINUE,CONVERT,CREATE,CROSS,CURRENT,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,CURSOR,DATABASE,DBCC,DEALLOCATE,DECLARE,DEFAULT,DELETE,DENY,DESC,DISK,DISTINCT,DISTRIBUTED,DOUBLE,DROP,DUMP,ELSE,END,ERRLVL,ESCAPE,EXCEPT,EXEC,EXECUTE,EXISTS,EXIT,EXTERNAL,FETCH,FILE,FILLFACTOR,FOR,FOREIGN,FREETEXT,FREETEXTTABLE,FROM,FULL,FUNCTION,GOTO,GRANT,GROUP,HAVING,HOLDLOCK,IDENTITY,IDENTITY_INSERT,IDENTITYCOL,IF,IN,INDEX,INNER,INSERT,INTERSECT,INTO,IS,JOIN,KEY,KILL,LEFT,LIKE,LINENO,LOAD,MERGE,NATIONAL,NOCHECK,NONCLUSTERED,NOT,NULL,NULLIF,OF,OFF,OFFSETS,ON,OPEN,OPENDATASOURCE,OPENQUERY,OPENROWSET,OPENXML,OPTION,OR,ORDER,OUTER,OVER,PERCENT,PIVOT,PLAN,PRECISION,PRIMARY,PRINT,PROC,PROCEDURE,PUBLIC,RAISERROR,READ,READTEXT,RECONFIGURE,REFERENCES,REPLICATION,RESTORE,RESTRICT,RETURN,REVERT,REVOKE,RIGHT,ROLLBACK,ROWCOUNT,ROWGUIDCOL,RULE,SAVE,SCHEMA,SECURITYAUDIT,SELECT,SEMANTICKEYPHRASETABLE,SEMANTICSIMILARITYDETAILSTABLE,SEMANTICSIMILARITYTABLE,SESSION_USER,SET,SETUSER,SHUTDOWN,SOME,STATISTICS,SYSTEM_USER,TABLE,TABLESAMPLE,TEXTSIZE,THEN,TO,TOP,TRAN,TRANSACTION,TRIGGER,TRUNCATE,TRY_CONVERT,TSEQUAL,UNION,UNIQUE,UNPIVOT,UPDATE,UPDATETEXT,USE,USER,VALUES,VARYING,VIEW,WAITFOR,WHEN,WHERE,WHILE,WITH,WITHIN GROUP,WRITETEXT,"
Const ORACLE_RESERVED_WORDS = ",ACCESS,ADD,ALL,ALTER,AND,ANY,ARRAYLEN,AS,ASC,AUDIT,BETWEEN,BY,CHAR,CHECK,CLUSTER,COLUMN,COMMENT,COMPRESS,CONNECT,CREATE,CURRENT,DATE,DECIMAL,DEFAULT,DELETE,DESC,DISTINCT,DROP,ELSE,EXCLUSIVE,EXISTS,FILE,FLOAT,FOR,FROM,GRANT,GROUP,HAVING,IDENTIFIED,IMMEDIATE,IN,INCREMENT,INDEX,INITIAL,INSERT,INTEGER,INTERSECT,INTO,IS,LEVEL,LIKE,LOCK,LONG,MAXEXTENTS,MINUS,MODE,MODIFY,NOAUDIT,NOCOMPRESS,NOT,NOTFOUND,NOWAIT,NULL,NUMBER,OF,OFFLINE,ON,ONLINE,OPTION,OR,ORDER,PCTFREE,PRIOR,PRIVILEGES,PUBLIC,RAW,RENAME,RESOURCE,REVOKE,ROW,ROWID,ROWLABEL,ROWNUM,ROWS,SELECT,SESSION,SET,SHARE,SIZE,SMALLINT,SQLBUF,START,SUCCESSFUL,SYNONYM,SYSDATE,TABLE,THEN,TO,TRIGGER,UID,UNION,UNIQUE,UPDATE,USER,VALIDATE,VALUES,VARCHAR,VARCHAR2,VIEW,WHENEVER,WHERE,WITH,"
Const ALL_DBMS_RESERVED_WORDS = ",ACCESS,ADD,ALL,ALTER,AND,ANY,ARRAYLEN,AS,ASC,AUDIT,AUTHORIZATION,BACKUP,BEGIN,BETWEEN,BREAK,BROWSE,BULK,BY,CASCADE,CASE,CHAR,CHECK,CHECKPOINT,CLOSE,CLUSTER,CLUSTERED,COALESCE,COLLATE,COLUMN,COMMENT,COMMIT,COMPRESS,COMPUTE,CONNECT,CONSTRAINT,CONTAINS,CONTAINSTABLE,CONTINUE,CONVERT,CREATE,CROSS,CURRENT,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,CURSOR,DATABASE,DATE,DBCC,DEALLOCATE,DECIMAL,DECLARE,DEFAULT,DELETE,DENY,DESC,DISK,DISTINCT,DISTRIBUTED,DOUBLE,DROP,DUMP,ELSE,END,ERRLVL,ESCAPE,EXCEPT,EXCLUSIVE,EXEC,EXECUTE,EXISTS,EXIT,EXTERNAL,FETCH,FILE,FILLFACTOR,FLOAT,FOR,FOREIGN,FREETEXT,FREETEXTTABLE,FROM,FULL,FUNCTION,GOTO,GRANT,GROUP,HAVING,HOLDLOCK,IDENTIFIED,IDENTITY,IDENTITY_INSERT,IDENTITYCOL,IF,IMMEDIATE,IN,INCREMENT,INDEX,INITIAL,INNER,INSERT,INTEGER,INTERSECT,INTO,IS,JOIN,KEY,KILL,LEFT,LEVEL,LIKE,LINENO,LOAD,LOCK,LONG,MAXEXTENTS,MERGE,MINUS,MODE,MODIFY,NATIONAL,NOAUDIT,NOCHECK,NOCOMPRESS,NONCLUSTERED,NOT,NOTFOUND,NOWAIT,NULL,NULLIF,NUMBER,OF,OFF,OFFSETS,OFFLINE,ON,ONLINE,OPEN,OPENDATASOURCE,OPENQUERY,OPENROWSET,OPENXML,OPTION,OR,ORDER,OUTER,OVER,PCTFREE,PERCENT,PIVOT,PLAN,PRECISION,PRIMARY,PRINT,PRIOR,PRIVILEGESPROC,PROCEDURE,PUBLIC,RAISERROR,RAW,READ,READTEXT,RECONFIGURE,REFERENCES,RENAME,REPLICATION,RESOURCE,RESTORE,RESTRICT,RETURN,REVERT,REVOKE,RIGHT,ROLLBACK,ROW,ROWCOUNT,ROWGUIDCOL,ROWID,ROWLABEL,ROWNUM,ROWS,RULE,SAVE,SCHEMA,SECURITYAUDIT,SELECT,SEMANTICKEYPHRASETABLE,SEMANTICSIMILARITYDETAILSTABLE,SEMANTICSIMILARITYTABLE,SESSION,SESSION_USER,SET,SETUSER,SHARE,SHUTDOWN,SIZE,SMALLINT,SOME,SQLBUF,START,STATISTICS,SUCCESSFUL,SYNONYM,SYSDATE,SYSTEM_USER,TABLE,TABLESAMPLE,TEXTSIZE,THEN,TO,TOP,TRAN,TRANSACTION,TRIGGER,TRUNCATE,TRY_CONVERT,TSEQUAL,UID,UNION,UNIQUE,UNPIVOT,UPDATE,UPDATETEXT,USE,USER,VALIDATE,VALUES,VARCHAR,VARCHAR2,VARYING,VIEW,WAITFOR,WHEN,WHENEVER,WHERE,WHILE,WITH,WITHIN GROUP,WRITETEXT,"

' Identifier names are converted to lower case for a case insensitive comparison with this string
Dim RESERVED_DBMS_WORDS: RESERVED_DBMS_WORDS = LCase(ALL_DBMS_RESERVED_WORDS)

' Magic numbers for field type min/max values and identifier names 
Dim MAX_FC_NAME: MAX_FC_NAME = 32 'Oracle's max field/table name length

Dim MAX_TEXT_LENGTH: MAX_TEXT_LENGTH = 255
Dim MIN_TEXT_LENGTH: MIN_TEXT_LENGTH = 1

Dim MIN_SCALE_FLOAT: MIN_SCALE_FLOAT = 1
Dim MAX_SCALE_FLOAT: MAX_SCALE_FLOAT = 6

Dim MIN_PRECISION_FLOAT: MIN_PRECISION_FLOAT = 1
Dim MAX_PRECISION_FLOAT: MAX_PRECISION_FLOAT = 6

Dim MIN_SCALE_DOUBLE: MIN_SCALE_DOUBLE = 0
Dim MAX_SCALE_DOUBLE: MAX_SCALE_DOUBLE = 15

Dim MIN_PRECISION_DOUBLE: MIN_PRECISION_DOUBLE = 7
Dim MAX_PRECISION_DOUBLE: MAX_PRECISION_DOUBLE = 15

Dim MIN_PRECISION_INT: MIN_PRECISION_INT = 0
Dim MAX_PRECISION_INT: MAX_PRECISION_INT = 10

Dim TARGET_DBMS : TARGET_DBMS = "any"

' Profile strings defined here to prevent typos scattered through code.
Const ARC_ARCGIS = "ArcGIS"
Const ARC_CODED_VALUE_DOMAIN = "CodedValueDomain"
Const ARC_DOMAIN_CODED_VALUE = "DomainCodedValue"
Const ARC_FEATURE_DATASET = "FeatureDataset"
Const ARC_FIELD = "Field"
Const ARC_OBJECT_CLASS = "ObjectClass"
Const ARC_POLYGON = "Polygon"
Const ARC_POLYLINE = "Polyline"
Const ARC_RANGE_DOMAIN = "RangeDomain"
Const ARC_RASTER_CATALOG = "RasterCatalog"
Const ARC_RELATIONSHIP_CLASS = "RelationshipClass"
Const ARC_SUBTYPE = "Subtype"
Const ARC_SUBTYPE_CODE = "SubtypeCode"
Const ARC_SUBTYPE_FIELD = "SubtypeField"
Const ARC_ATTRIBUTE_INDEX = "AttributeIndex"
Const ARC_SPATIAL_INDEX = "SpatialIndex"
Const ARC_REQUIRED_FIELD = "RequiredField"

Const ARC_SPATIAL_REFERENCE = "SpatialReference"
Const ARC_VALIDATION_OPTIONS = "ValidationOptions"

Const ARC_RANGE_MIN_VALUE = "MaxValue"
Const ARC_RANGE_MAX_VALUE = "MinValue"

Const ARC_FIELD_TYPE = "FieldType"
Const ARC_MERGE_POLICY = "MergePolicy"
Const ARC_SPLIT_POLICY = "SplitPolicy"

Const ARC_TYPE_LONG_INT = "esriFieldTypeInteger"
Const ARC_TYPE_SMALL_INT = "esriFieldTypeSmallInteger"
Const ARC_TYPE_DOUBLE = "esriFieldTypeDouble"
Const ARC_TYPE_SINGLE = "esriFieldTypeSingle"
Const ARC_TYPE_STRING = "esriFieldTypeString"
Const ARC_TYPE_DATE = "esriFieldTypeDate"
Const ARC_TYPE_GEOMETRY = "esriFieldTypeGeometry"
Const ARC_TYPE_OID = "esriFieldTypeOID"
Const ARC_TYPE_BLOB = "esriFieldTypeBlob"
Const ARC_TYPE_GID = "esriFieldTypeGlobalID"
Const ARC_TYPE_RASTER = "esriFieldTypeRaster"
Const ARC_TYPE_GUID = "esriFieldTypeGUID"
Const ARC_TYPE_XML = "esriFieldTypeXML"

Const ARC_TAG_DESCRIPTION = "Description"
Const ARC_TAG_DEST_PK = "DestinationPrimaryKey"
Const ARC_TAG_DEST_FK = "DestinationForeignKey"
Const ARC_TAG_GEOMETRY_DEF = "GeometryDef"
Const ARC_TAG_HASM = "HasM"
Const ARC_TAG_HASZ = "HasZ"
Const ARC_TAG_ISNULLABLE = "IsNullable"
Const ARC_TAG_LENGTH = "Length"
Const ARC_TAG_PRECISION = "Precision"
Const ARC_TAG_ORIGIN_FK = "OriginForeignKey"
Const ARC_TAG_ORIGIN_PK = "OriginPrimaryKey"
Const ARC_TAG_SCALE = "Scale"
Const ARC_TAG_SPATIAL_REF = "SpatialReference"
Const ARC_TAG_OID_FIELD_NAME = "OIDFieldName"
Const ARC_TAG_SPATIAL_FIELD_NAME = "ShapeFieldName"
Const ARC_TAG_REQUIRED = "Required"
Const ARC_TAG_AREA_FIELD_NAME = "AreaFieldName"
Const ARC_TAG_LENGTH_FIELD_NAME = "LengthFieldName"

Const UML_PACKAGE = "Package"
Const UML_CLASS = "Class"
Const UML_ATT = "Attribute"
Const UML_CONNECTOR = "Connector"

Const MODEL_DB_TYPE_JET = "JET"
Const MODEL_DB_TYPE_ACCESS2007 = "ACCESS2007"
Const MODEL_DB_TYPE_SQLSVR = "SQLSVR"
Const MODEL_DB_TYPE_MYSWL = "MYSQL"
Const MODEL_DB_TYPE_ORACLE = "ORACLE"
Const MODEL_DB_TYPE_POSTGRES = "POSTGRES"
Const MODEL_DB_TYPE_FIREBIRD = "FIREBIRD"

Const OPT_TAG_TARGET_DBMS = "TargetDBMS"
Const OPT_TAG_DISABLE_RULES = "DisableRules"
Const OPT_TAG_DEFAULT_LENGTH_STRING_WARN = "CheckDefaultLengthString"
Const OPT_TAG_DEFAULT_PRECISION_WARN = "CheckDefaultPrecision"
Const OPT_TAG_DEFAULT_SCALE_WARN = "CheckDefaultScale"
Const OPT_TAG_DOMAIN_DESCRIPTION_MISSING_WARN = "CheckDomainDescriptionMissing"
Const OPT_TAG_OBJECT_IN_FEATURE_DATASET_WARN = "CheckObjectInFeatureDataset"
Const OPT_TAG_STRING_LENGTH_MAX = "StringLengthMax"
Const OPT_TAG_PRECISION_MAX_LONG_INT = "PrecisionMaxLongInt"
Const OPT_TAG_PRECISION_MIN_LONG_INT = "PrecisionMinLongInt"
Const OPT_TAG_PRECISION_MAX_FLOAT = "PrecisionMaxFloat"
Const OPT_TAG_PRECISION_MIN_FLOAT = "PrecisionMinFloat"
Const OPT_TAG_PRECISION_MAX_DOUBLE = "PrecisionMaxDouble"
Const OPT_TAG_PRECISION_MIN_DOUBLE = "PrecisionMinDouble"
Const OPT_TAG_SCALE_MAX_FLOAT = "ScaleMaxFloat"
Const OPT_TAG_SCALE_MIN_FLOAT = "ScaleMinFloat"
Const OPT_TAG_SCALE_MAX_DOUBLE = "ScaleMaxDouble"
Const OPT_TAG_SCALE_MIN_DOUBLE = "ScaleMinDouble"

Const OPT_TARGET_DB_TYPE_GDB = "gdb"
Const OPT_TARGET_DB_TYPE_DB2 = "db2"
Const OPT_TARGET_DB_TYPE_INFORMIX = "informix"
Const OPT_TARGET_DB_TYPE_ORACLE = "oracle"
Const OPT_TARGET_DB_TYPE_POSTGRES = "postgresql"
Const OPT_TARGET_DB_TYPE_SQLS_SERVER = "sqlserver"


''''''''''''''''''''''
''
'' Globals (Cache)
''
''''''''''''''''''''''

Dim classNames		' Maps Class names (as a string) -> Class Element (EA.Element)
Dim subtypeCodes	' Maps Class Ids (EA.Element.ElementID) -> Two-element string array of form: {"parentDefaultCode", ",subtypeCode1,subtypeCode2,subtypeCode3,"}
Dim idClasses		' Maps Class Elements Ids (EA.Element.ElementID) -> Class Elements (EA.Element objects)
Dim pkgClasses		' Maps Package Ids (EA.Package.PackageID) -> Classes (EA.Element objects) owned by the package
Dim idPkgs			' Maps Package Ids (EA.Package.PackageID) -> Packages (EA.Package objects)
Dim domainNames		' Maps Coded Value Domain names (as a string) -> Class Element (EA.Element.ElementID)
Dim domainVals		' Maps Coded Value Domain Element ID's -> string array of form: {"fieldType", "code1=val1", "code2=val2", "coden=valn"}
Dim rangeNames		' Maps Range Domain names (as a string) -> Class Element (EA.Element.ElementID)
Dim rangeVals		' Maps Range Domain Element ID's -> string array of form: {"fieldType", "minVal", "maxVal"}
Dim rcNames			' Maps RelationshipClass names (as a string) -> Connector (EA.Connector)
Dim attGuids		' Maps Feature/Object Class Attribute Guids (EA.Attribute.AttributeGUID) -> Attribute objects (EA.Attribute)
Dim elAtts			' Maps Feature/Object Class Element IDs (EA.Element.ElementID) -> Three-element array of form: {{elAttGUIDs}, {elInheritedAttGUIDs}, inheritedFieldNames}

					' Stores all the Spatial Reference elements defined in the workspace
					' Initialized in Main
Dim spatialRefEls As EA.Collection 

Set classNames = CreateObject("Scripting.Dictionary")
Set subtypeCodes = CreateObject("Scripting.Dictionary")
Set idClasses = CreateObject("Scripting.Dictionary")
Set pkgClasses = CreateObject("Scripting.Dictionary")
Set idPkgs = CreateObject("Scripting.Dictionary")
Set domainNames = CreateObject("Scripting.Dictionary")
Set domainVals = CreateObject("Scripting.Dictionary")
Set rangeNames = CreateObject("Scripting.Dictionary")
Set rangeVals = CreateObject("Scripting.Dictionary")
Set rcNames = CreateObject("Scripting.Dictionary")
Set attGuids = CreateObject("Scripting.Dictionary")
Set elAtts = CreateObject("Scripting.Dictionary")

Dim numErrs: numErrs = 0
Dim numWarns: numWarns = 0

Const OUTPUT_TAB_NAME = "ArcGIS Model Validation"

Dim skipRules() : ReDim skipRules(-1)


''''''''''''''''''''''
''
'' Validation Routines
''
''''''''''''''''''''''

Sub Main

	' Create a dedicated tab to display validation errors and warnings, and progress
	CreateOutputTab OUTPUT_TAB_NAME
	EnsureOutputVisible OUTPUT_TAB_NAME

	OutputLine "ArcGIS Model Validation"
	OutputLine "======================================="

	' Make sure we're using Enterprise Architect 10.0.1007 or later
	If LibraryVersion < 1007 Then
		OutputLine "Error: This script requires Enterprise Architect version 10.0, Build 1007 or later. The following version was detected: " & LibraryVersion
	End If

	OutputLine "Start Time: " & Time

	' Get the selected workspace package
	Dim workspacePkg As EA.Package
	Set workspacePkg = GetTreeSelectedPackage()
	If workspacePkg Is Nothing Then
		OutputLine "Error: Failed to load the <<ArcGIS>> Workspace package. Aborting validation."
		Exit Sub
	End If

	' Load all Workspace packages
	OutputLine "Loading Feature Dataset packages..."
	Dim workspacePkgIds
	workspacePkgIds = GetWorkspacePkgIds(workspacePkg)

	MapPkgIds(workspacePkgIds)
	OutputLine VbTab & "...done"

	' Load validation options from the first <<ValidationOptions>> element
	OutputLine "Setting Model Validation Options..."
	SetValidationOptions workspacePkgIds, workspacePkg
	OutputLine VbTab & "...done"

	' Load all Spatial Reference elements
	OutputLine "Loading Spatial Reference elements..."
	Set spatialRefEls = GetSpatialReferences(workspacePkgIds, workspacePkg)
	OutputLine VbTab & "...done"

	' Load all Classes in the workspace
	OutputLine "Loading other workspace elements..."
	Dim workspaceEls As EA.Collection
	Set workspaceEls = GetWorkspaceEls(workspacePkgIds)

	' Load the elements into appropriate dictionaries (hash tables)
	MapClassIds workspaceEls
	OutputLine VbTab & "...done"

	OutputLine "Validating Workspace Properties..."
	If ValidateWorkspace(workspacePkg) = False Then
		Exit Sub
	End If

	OutputLine "Validating Spatial References..."
	ValidateSpatialRefs workspacePkg.Name, workspacePkg.PackageID

	OutputLine "Validating Domains..."
	ValidateDomains workspacePkg.Name, workspaceEls

	OutputLine "Validating Workspace Packages..."
	ValidatePackage workspacePkg, "", False

	'Check that each feature/object class that specifies a default subtype code uses a valid code
	OutputLine "Validating Default Subtype Codes for Feature Classes..."
	ValidateDefaultSubtypeCodes

	Dim errText: errText = " errors, "
	Dim warnText: warnText = " warnings."

	If numErrs = 1 Then
		errText = " error, "
	End If

	If numWarns = 1 Then
		warnText = " warning."
	End If

	OutputLine "Validation Complete! Found " & numErrs & errText & numWarns & warnText
	OutputLine "End Time: " & Time
	OutputLine "======================================="

End Sub


Sub SetValidationOptions(workspacePkgIds, workspacePkg)

	Dim optEl : Set optEl = GetValidationOptionsElement(workspacePkgIds, workspacePkg)
	If Not optEl Is Nothing Then
		OutputLine VbTab & "Found ValidationOptions element named: "&optEl.Name&". Using the following NON-DEFAULT validation options:"
		Dim tags : Set tags = optEl.TaggedValues
		Dim count : count = tags.Count - 1
		Dim tag As EA.TaggedValue
		Dim i, val, name
		For i = 0 To count
			Set tag = tags.GetAt(i)
			name = tag.Name
			val = LCase(tag.Value)

			Dim bIsNumericVal : bIsNumericVal = Len(val) > 0 And IsNumeric(val)
			Dim intVal : intVal = -1
			If bIsNumericVal = True Then
				intVal = CLng(val)
			End If

			If name = OPT_TAG_TARGET_DBMS Then
				If val = OPT_TARGET_DB_TYPE_GDB Or val = OPT_TARGET_DB_TYPE_DB2 Or val = OPT_TARGET_DB_TYPE_INFORMIX Or val = OPT_TARGET_DB_TYPE_ORACLE Or val = OPT_TARGET_DB_TYPE_POSTGRES Or val = OPT_TARGET_DB_TYPE_SQLS_SERVER Then
					TARGET_DBMS = val
					If val = OPT_TARGET_DB_TYPE_GDB Then
						RESERVED_DBMS_WORDS = LCase(GDB_RESERVED_WORDS)
					ElseIf val = OPT_TARGET_DB_TYPE_ORACLE Then
						RESERVED_DBMS_WORDS = LCase(ORACLE_RESERVED_WORDS)
					ElseIf val = OPT_TARGET_DB_TYPE_SQLS_SERVER Then
						RESERVED_DBMS_WORDS = LCase(SQL_RESERVED_WORDS)
					End If
					OutputLine VbTab & VbTab & "Target DBMS is: "&TARGET_DBMS
				End If
			ElseIf name = OPT_TAG_DISABLE_RULES Then
				If val = "<memo>" Then
					val = tag.Notes
				End If
				If Len(val) > 0 Then
					AddSkipRulesString val
					OutputLine VbTab & VbTab & "Suppressing errors and warnings for these rules: "&Replace(val, VbCrLf, ",")
				End If
			ElseIf name = OPT_TAG_DEFAULT_LENGTH_STRING_WARN And bIsNumericVal = True And intVal = 0 Then
				AddSkipRule "MVR-FL-10"
				OutputLine VbTab & VbTab & "Suppressing warnings about unspecified lengths on string fields (MVR-FL-10)"
			ElseIf name = OPT_TAG_DEFAULT_PRECISION_WARN And bIsNumericVal = True And intVal = 0 Then
				AddSkipRule "MVR-FL-7"	' This rule covers integer (long int) field types that don't have a precision value set
				AddSkipRule "MVR-FL-17"	' This rule covers double and float field types that don't have a precision value set
				OutputLine VbTab & VbTab & "Suppressing warnings about unspecified precision values on numeric fields (MVR-FL-7 and MVR-FL-17)"
			ElseIf name = OPT_TAG_DEFAULT_SCALE_WARN And bIsNumericVal = True And intVal = 0 Then
				AddSkipRule "MVR-FL-13"
				OutputLine VbTab & VbTab & "Suppressing warnings about unspecified scale values on numeric fields (MVR-FL-13)"
			ElseIf name = OPT_TAG_DOMAIN_DESCRIPTION_MISSING_WARN And bIsNumericVal = True And intVal = 0 Then
				AddSkipRule "MVR-CVD-6"
				OutputLine VbTab & VbTab & "Suppressing warnings about missing descriptions on Coded Value Domain elements (MVR-CVD-6)"
			ElseIf name = OPT_TAG_OBJECT_IN_FEATURE_DATASET_WARN And bIsNumericVal = True And intVal = 0 Then
				AddSkipRule "MVR-OC-1"
				OutputLine VbTab & VbTab & "Suppressing warnings about Tables (Object Classes) being modeled within a Feature Dataset package (MVR-OC-1)"
			Else
				' To be a valid length, precision or scale value, the tag value must non-empty, with its value being numeric and non-negative
				Dim bIsValidInt : bIsValidInt = bIsNumericVal = True And intVal > -1 

				If bIsValidInt = True Then
					If name = OPT_TAG_STRING_LENGTH_MAX Then
						MAX_TEXT_LENGTH = intVal
						OutputLine VbTab & VbTab & "Maximum allowed Length value for string fields (esriFieldTypeString) is: "&MAX_TEXT_LENGTH
					ElseIf name = OPT_TAG_PRECISION_MAX_LONG_INT Then
						MAX_PRECISION_INT = intVal
						OutputLine VbTab & VbTab & "Maximum allowed Precision value for integer fields (esriFieldTypeInteger) is: "&MAX_PRECISION_INT
					ElseIf name = OPT_TAG_PRECISION_MIN_LONG_INT Then
						MIN_PRECISION_INT = intVal
						OutputLine VbTab & VbTab & "Minimum allowed Precision value for integer fields (esriFieldTypeInteger) is: "&MIN_PRECISION_INT
					ElseIf name = OPT_TAG_PRECISION_MAX_FLOAT Then
						MAX_PRECISION_FLOAT = intVal
						OutputLine VbTab & VbTab & "Maximum allowed Precision value for float fields (esriFieldTypeSingle) is: "&MAX_PRECISION_FLOAT
					ElseIf name = OPT_TAG_PRECISION_MIN_FLOAT Then
						MIN_PRECISION_FLOAT = intVal
						OutputLine VbTab & VbTab & "Minimum allowed Precision value for float fields (esriFieldTypeSingle) is: "&MIN_PRECISION_FLOAT
					ElseIf name = OPT_TAG_PRECISION_MAX_DOUBLE Then
						MAX_PRECISION_DOUBLE = intVal
						OutputLine VbTab & VbTab & "Maximum allowed Precision value for double fields (esriFieldTypeDouble) is: "&MAX_PRECISION_DOUBLE
					ElseIf name = OPT_TAG_PRECISION_MIN_DOUBLE Then
						MIN_PRECISION_DOUBLE = intVal
						OutputLine VbTab & VbTab & "Minimum allowed Precision value for double fields (esriFieldTypeDouble) is: "&MIN_PRECISION_DOUBLE
					ElseIf name = OPT_TAG_SCALE_MAX_FLOAT Then
						MAX_SCALE_FLOAT = intVal
						OutputLine VbTab & VbTab & "Maximum allowed Scale value for float fields (esriFieldTypeSingle) is: "&MAX_SCALE_FLOAT
					ElseIf name = OPT_TAG_SCALE_MIN_FLOAT Then
						MIN_SCALE_FLOAT = intVal
						OutputLine VbTab & VbTab & "Minimum allowed Scale value for float fields (esriFieldTypeSingle) is: "&MIN_SCALE_FLOAT
					ElseIf name = OPT_TAG_SCALE_MAX_DOUBLE Then
						MAX_SCALE_DOUBLE = intVal
						OutputLine VbTab & VbTab & "Maximum allowed Scale value for double fields (esriFieldTypeDouble) is: "&MAX_SCALE_DOUBLE
					ElseIf name = OPT_TAG_SCALE_MIN_DOUBLE Then
						MIN_SCALE_DOUBLE = intVal
						OutputLine VbTab & VbTab & "Minimum allowed Scale value for double fields (esriFieldTypeDouble) is: "&MIN_SCALE_DOUBLE
					End If
				End If
			End If
		Next
	End If
End Sub


Sub AddSkipRulesString(rules)
	Dim strTmp : strTmp = Replace(Replace(rules, VbCrLf, ",")," ",",") ' Strip spaces and new lines from the input
	Dim aryRules : aryRules = Split(UCase(strTmp),",") ' Make the rules all upper case and convert to an array
	Dim r
	For Each r In aryRules
		If Len(r) > 0 Then
			AddSkipRule r
		End If
	Next
End Sub


Sub AddSkipRule(rule)
	If IsInArray(skipRules, rule) = False Then
		ReDim Preserve skipRules(UBound(skipRules) + 1)
		skipRules(UBound(skipRules)) = rule
	End If
End Sub


Function ValidateWorkspace(workspacePkg)
	Dim ret : ret = False

	If workspacePkg.IsModel = True Then
		LogError MVR_WS_PKG, "", UML_PACKAGE, workspacePkg.Name, "", workspacePkg.PackageID
	ElseIf workspacePkg.Element.Stereotype <> ARC_ARCGIS Then
		LogError MVR_WS_STEREO, "", UML_PACKAGE, workspacePkg.Name, "", workspacePkg.PackageID
	Else
		ret = True
	End If

	ValidateWorkspace = ret

End Function


Sub ValidateSpatialRefs(workspaceName, pkgID)

	If spatialRefEls.Count = 0 Then
		LogError MVR_SR_MISSING, ARC_ARCGIS, UML_PACKAGE, workspaceName, "", pkgID
	End If

End Sub


Sub ValidateDomains(workspaceName, workspaceEls)

	Dim idx
	Dim el As EA.Element
	For idx = 0 To workspaceEls.Count - 1
		Set el = workspaceEls.GetAt(idx)
		If Not el Is Nothing Then
			Dim elStereo: elStereo = el.Stereotype
			If elStereo = ARC_CODED_VALUE_DOMAIN Or elStereo = ARC_RANGE_DOMAIN Then
				Dim path : path = workspaceName
				Dim pkg As EA.Package
				Set pkg = GetCachedPkg(el.PackageID)
				If Not pkg Is Nothing Then
					path = path & "::" & pkg.Name
				End If
				If elStereo = ARC_CODED_VALUE_DOMAIN Then 
					ValidateCodedValueDomain el, el.Name, path, el.ElementID
				Else
					ValidateRangeDomain el, el.Name, path, el.ElementID
				End If
			End If
		End If
	Next

End Sub


Sub ValidatePackage(pkg, ByVal path, ByVal bInFD)

	Dim subPkg As EA.Package
	Dim pkgName : pkgName = pkg.Name

	' Validate the package properties first...
	If pkg.Element.Stereotype = ARC_FEATURE_DATASET Then
		ValidateFeatureDataset pkg, path, bInFD
		bInFD = True
	End If

	If Len(path) > 0 Then
		path = path&"::"&pkgName
	Else
		path = "<workspace>"&pkgName
	End If

	' Then validate any direct child elements...
	ValidatePackageEls pkg, path, bInFD

	' Finally recurse for any sub-packages
	Dim idx
	For idx = 0 To pkg.Packages.Count - 1
		Set subPkg = pkg.Packages.GetAt(idx)
		ValidatePackage subPkg, path, bInFD
	Next

End Sub


Sub ValidateDefaultSubtypeCodes

	Dim ids, id
	Dim el As EA.Element
	Dim att As EA.Attribute
	ids = subtypeCodes.Keys
	For Each id In ids
		' The subtypeCodes dictionary lets us lookup subtype codes by the parent ElementID.
		' The first entry in the array is the parent's default subtype code
		If subtypeCodes.Exists(id) = True Then
			Dim defaultCode: defaultCode = subtypeCodes(id)(0)
			' If the Feature Class has a subtype field, make sure the default code is valid
			If Len(defaultCode) > 0 Then
				Dim codes: codes = subtypeCodes(id)(1)
				If Instr(codes, ","&defaultCode&",") = 0 Then
					Set el = GetCachedClass(id)
					If Not el Is Nothing Then
						Set att = GetAttByStereotype(el.Attributes, ARC_SUBTYPE_FIELD)
						If Not att Is Nothing Then
							LogError MVR_FL_SUBTYPE_INVALID_CODE, ARC_SUBTYPE_FIELD, UML_ATT, att.Name, el.Name&"::"&att.Name, att.AttributeID
						End If
					End If
				End If
			End If
		End If
	Next

End Sub


Sub ValidateFeatureDataset(pkg, path, bInFD)

	Dim pkgName : pkgName = pkg.Name
	Dim pkgID : pkgID = pkg.PackageID

	ValidateFDName pkgName, path, pkgID
	ValidateSpatialReferenceValue pkg.Element.TaggedValues, ARC_FEATURE_DATASET, UML_PACKAGE, pkgName, path, pkgID

	If bInFD = True Then
		LogError MVR_FD_NESTED, ARC_FEATURE_DATASET, UML_PACKAGE, pkgName, path, pkgID
	End If

End Sub


Sub ValidatePackageEls(pkg, path, bInFD)

	Dim pkgId : pkgId = pkg.PackageID
	If pkgClasses.Exists(pkgId) = False Then
		Exit Sub
	End If

	Dim aryClasses : aryClasses = pkgClasses(pkgId)
	If UBound(aryClasses) < 0 Then
		Exit Sub
	End If

	Dim el As EA.Element
	For Each el In aryClasses
		ValidateElement el, path, bInFD
	Next

End Sub


Sub ValidateElement(el, path, bInFD)

	Dim elName : elName = el.Name
	Dim elStereo : elStereo = el.Stereotype
	Dim bAbstract : bAbstract = el.Abstract = "1"
	Dim elID: elID = el.ElementID

	'Currently we don't validate the ValidationOptions element at all
	If elStereo = ARC_VALIDATION_OPTIONS Then
		Exit Sub
	End If

	' Abstract classes don't make it to the schema, so don't include their names in test for uniqueness, special chars etc.
	If bAbstract = False Then
		If ValidateClassStereo(elStereo, elName, path, elID) = True Then
			If IsFeatureClass(elStereo) = True Then
				ValidateFeatureClass el, elStereo, elName, path, bInFD, elID
			ElseIf elStereo = ARC_SUBTYPE Then
				ValidateSubtype el, elName, path, elID
			ElseIf elStereo = ARC_OBJECT_CLASS Then
				ValidateObjectClass el, elStereo, elName, path, bInFD, elID
			ElseIf elStereo = ARC_RELATIONSHIP_CLASS Then
				ValidateRelClass el, elName, path, elID
			End If
		End If
	Else
		ValidateAbstractClass el, elStereo, elName, path, elID
	End If

	ValidateRelationships el, elName, elStereo, path, elID

End Sub

Sub ValidateFeatureClass(el, elStereo, elName, path, bInFD, elID)

	ValidateFCName elName, elStereo, path, elID
	ValidateUniqueFCName elName, elStereo, el, path, elID

	ValidateBooleanTag el.TaggedValues, ARC_TAG_HASM, elStereo, UML_CLASS, elName, path, elID
	ValidateBooleanTag el.TaggedValues, ARC_TAG_HASZ, elStereo, UML_CLASS, elName, path, elID

	If bInFD = False Then
		ValidateSpatialReferenceValue el.TaggedValues, elStereo, UML_CLASS, elName, path, elID
	End If

	ValidateOIDFieldNameTag el, elStereo, elName, path, elID
	ValidateShapeFieldNameTag el, elStereo, elName, path, elID
	ValidateClassMembers el, elStereo, elName, path, elID

End Sub


Sub ValidateObjectClass(el, elStereo, elName, path, bInFD, elID)

	ValidateUniqueFCName elName, elStereo, el, path, elID

	' Check if the ObjectClass is illegally defined within a feature dataset
	If bInFD = True Then
		LogWarning MVR_OC_FEATURE_DATASET, ARC_OBJECT_CLASS, UML_CLASS, elName, path, elID
	End If

	ValidateOIDFieldNameTag el, elStereo, elName, path, elID
	ValidateClassMembers el, elStereo, elName, path, elID

End Sub


Sub ValidateAbstractClass(el, elStereo, elName, path, elID)

	ValidateClassMembers el, elStereo, elName, path, elID

End Sub


Sub ValidateRelClass(el, elName, path, elID)

	ValidateClassMembers el, ARC_RELATIONSHIP_CLASS, elName, path, elID

End Sub


Sub ValidateOIDFieldNameTag(el, elStereo, elName, path, elID)

	Dim attGuid : attGuid = GetTagValByName(el.TaggedValues, ARC_TAG_OID_FIELD_NAME)
	Dim att : Set att = GetCachedOwnedAttByGUID(attGuid, elID)
	If att Is Nothing Then
		If IsFeatureClass(elStereo) = True Then
			LogError MVR_FC_OID_FIELD_TAG_MISSING_ATT, elStereo, UML_CLASS, elName, path, elID
		Else
			LogError MVR_OC_OID_FIELD_TAG_MISSING_ATT, elStereo, UML_CLASS, elName, path, elID
		End If
	ElseIf att.Type <> ARC_TYPE_OID Then
		If IsFeatureClass(elStereo) = True Then
			LogWarning MVR_FC_OID_FIELD_TAG_INVALID_ATT_TYPE, elStereo, UML_CLASS, elName, path, elID
		Else
			LogWarning MVR_OC_OID_FIELD_TAG_INVALID_ATT_TYPE, elStereo, UML_CLASS, elName, path, elID
		End If
	End If

End Sub


Sub ValidateShapeFieldNameTag(el, elStereo, elName, path, elID)

	Dim attGuid : attGuid = GetTagValByName(el.TaggedValues, ARC_TAG_SPATIAL_FIELD_NAME)
	Dim att : Set att = GetCachedOwnedAttByGUID(attGuid, elID)
	If att Is Nothing Then
		LogError MVR_FC_SHAPE_FIELD_TAG_MISSING_ATT, elStereo, UML_CLASS, elName, path, elID
	ElseIf att.Type <> ARC_TYPE_GEOMETRY Then
		LogWarning MVR_FC_SHAPE_FIELD_TAG_INVALID_ATT_TYPE, elStereo, UML_CLASS, elName, path, elID
	End If

End Sub


Sub ValidateClassMembers(el, elStereo, elName, path, elID)

	Dim names : names = ","
	Dim classPath : classPath = path&"::"&elName

	Dim inheritedFieldNames : inheritedFieldNames = GetInheritedAttNames(elID)
	ValidateInheritedFieldNames inheritedFieldNames, elName, path, elID

	Dim bHasOIDField : bHasOIDField = False
	Dim bHasShapeField : bHasShapeField = False
	Dim bHasShapeLengthField : bHasShapeLengthField = False
	Dim bHasShapeAreaField : bHasShapeAreaField = False
	Dim bHasAttributeIndex : bHasAttributeIndex = False
	Dim bHasSpatialIndex : bHasSpatialIndex = False
	Dim bHasGlobalId : bHasGlobalId = False
	Dim bAbstract : bAbstract = el.Abstract = "1"

	Dim bIsShapeLengthFieldRequired : bIsShapeLengthFieldRequired = IsShapeLengthFieldRequired(elstereo)
	Dim bIsShapeAreaFieldRequired : bIsShapeAreaFieldRequired = IsShapeAreaFieldRequired(elstereo)

	Dim lengthFieldName : lengthFieldName = ""
	If bIsShapeLengthFieldRequired = True Then
		lengthFieldName = LCase(GetAttNameByRefGUIDTagName(el, elStereo, elID, ARC_TAG_LENGTH_FIELD_NAME))
	End If

	Dim areaFieldName : areaFieldName = ""
	If bIsShapeAreaFieldRequired = True Then
		areaFieldName = LCase(GetAttNameByRefGUIDTagName(el, elStereo, elID, ARC_TAG_AREA_FIELD_NAME))
	End If

	Dim idx
	For idx = 0 To el.Attributes.Count -1
		Dim att As EA.Attribute
		Set att = el.Attributes.GetAt(idx)

		Dim attName : attName = att.Name
		Dim attType : attType = att.Type
		Dim attStereo : attStereo = att.Stereotype
		Dim attID: attID = att.AttributeID

		If attStereo = ARC_FIELD Or Len(attStereo) = 0 Then
			If attType = ARC_TYPE_GID Then
				If bHasGlobalId = True Then
					LogError MVR_FL_TYPE_MULTIPLE_GID, ARC_FIELD, UML_ATT, attName, classPath, attID
				Else
					bHasGlobalId = True
				End If
			End If

			ValidateFCField att, attStereo, attName, attType, classPath, attID

			If Len(attStereo) = 0 And bAbstract = False Then
				LogWarning MVR_FL_MISSING_STEREO, "", UML_ATT, attName, classPath, attID
			End If

		ElseIf attStereo = ARC_SUBTYPE_FIELD Then
			' For now, just make sure the subtypeCodeField attribute has the right data type
			' Later, we check that the default subtype code matches one of the subtypes.
			ValidateSubtypeCodeField att, attName, classPath, attID
			' Cache the parent's default subtype code as well
			Dim aryDef
			If subtypeCodes.Exists(elID) = True Then
				aryDef = subtypeCodes(elID)
				aryDef(0) = att.Default
				subtypeCodes(elID) = aryDef
			Else
				ReDim aryDef(2)
				aryDef(0) = att.Default
				aryDef(1) = ""
				subtypeCodes(elID) = aryDef
			End If
		End If

		' All name comparisons must be lower case when testing for duplicate fields
		attName = LCase(attName)

		' Is the name already defined in this class?
		If InStr(names, ","&attName&",") > 0 Then
			LogError MVR_FL_DUPLICATE_NAME, ARC_FIELD, UML_ATT, att.Name, classPath, attID
		Else
			names = names & attName & ","
		End If

		' Check on the system level fields: are they missing, duplicated, have a type mismatch etc.
		' The checks below attempt to catch these fields (in order): OBJECTID (or equivalent), OBJECTID_IX (or equivalent), Shape (or equivalent), Shape_IDX (or equivalent), Shape_Area, Shape_Length
		If attType = ARC_TYPE_OID Or attStereo = ARC_ATTRIBUTE_INDEX Or attType = ARC_TYPE_GEOMETRY Or attStereo = ARC_SPATIAL_INDEX Or attName = lengthFieldName Or attName = areaFieldName Then
			' Note: We don't care about duplicate system fields via inheritance - they are always overridden if present in the child class

			Dim tagRequired : tagRequired =  LCase(GetTagValByName(att.TaggedValues, ARC_TAG_REQUIRED))

			If attType = ARC_TYPE_OID Then
				If bHasOIDField = True Then
					LogError MVR_FL_REQ_OID_DUPLICATE, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				If tagRequired <> "true" Then
					LogWarning MVR_FL_INVALID_REQUIRED_TAG, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				bHasOIDField = True
			End If

			If attType = ARC_TYPE_GEOMETRY Then
				If bHasShapeField = True Then
					LogError MVR_FL_REQ_SHAPE_DUPLICATE, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				If tagRequired <> "true" Then
					LogWarning MVR_FL_INVALID_REQUIRED_TAG, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				bHasShapeField = True

				Dim tagVal : tagVal = LCase(GetTagValByName(att.TaggedValues, ARC_TAG_GEOMETRY_DEF))
				If Instr(tagVal, "hasm=") > 0 Then
					LogWarning MVR_FL_OBSOLETE_HASM, ARC_FIELD, UML_ATT, attName, classPath, attID
				End If
				If Instr(tagVal, "hasz=") > 0 Then
					LogWarning MVR_FL_OBSOLETE_HASZ, ARC_FIELD, UML_ATT, attName, classPath, attID
				End If

				tagVal = Replace(tagVal, "\n", "")
				Dim bHasAvgValue : bHasAvgValue = False
				Dim bHasGridValue : bHasGridValue = False

				Dim AvgValue : AvgValue = GetValue(GetOption(tagVal, ";", "avgnumpoints"))
				If AvgValue <> "" Then
					bHasAvgValue = True
					If IsNumeric(AvgValue) = False Then
						LogWarning MVR_FL_INVALID_AVGNUMPOINTS, ARC_FIELD, UML_ATT, attName, classPath, attID
					Else
						On Error Resume Next
							AvgValue = CLng(AvgValue)
						If Err.Number <> 0 Then
							LogWarning MVR_FL_INVALID_AVGNUMPOINTS, ARC_FIELD, UML_ATT, attName, classPath, attID
							Err.Clear()
						End If	
					End If	
				End If

				Dim GridValue : GridValue = GetValue(GetOption(tagVal, ";", "gridsize0"))
				If GridValue <> "" Then
					bHasGridValue = True						
					If IsNumeric(GridValue) = False Then
						LogWarning MVR_FL_INVALID_GRIDSIZE, ARC_FIELD, UML_ATT, attName, classPath, attID
					Else
						On Error Resume Next
							GridValue = CDbl(GridValue)
						If Err.Number <> 0 Then
							LogWarning MVR_FL_INVALID_GRIDSIZE, ARC_FIELD, UML_ATT, attName, classPath, attID
							Err.Clear()
						End If	
					End If
				End If

				If bHasAvgValue = False Then
					LogWarning MVR_FL_MISSING_AVGNUMPOINTS, ARC_FIELD, UML_ATT, attName, classPath, attID
				End If
				If bHasGridValue = False Then
					LogWarning MVR_FL_MISSING_GRIDSIZE, ARC_FIELD, UML_ATT, attName, classPath, attID
				End If
			End If

			If attStereo = ARC_ATTRIBUTE_INDEX Then
				bHasAttributeIndex = True
			End If

			If attStereo = ARC_SPATIAL_INDEX Then
				If bHasSpatialIndex = True Then
					LogError MVR_FL_IDX_SPATIAL_DUPLICATE, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				bHasSpatialIndex = True
			End If

			If attName = lengthFieldName Then
				If tagRequired <> "true" Then
					LogWarning MVR_FL_INVALID_REQUIRED_TAG, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				bHasShapeLengthField = True
			End If

			If attName = areaFieldName Then
				If tagRequired <> "true" Then
					LogWarning MVR_FL_INVALID_REQUIRED_TAG, ARC_REQUIRED_FIELD, UML_ATT, att.Name, classPath, attID
				End If
				bHasShapeAreaField = True
			End If
		Else
			' Is the name already defined in a parent class?
			Dim parentName : parentName = FindParentNameFromFields(inheritedFieldNames, attName)
			If Len(parentName) > 0 Then
				LogError MVR_FL_DUPLICATE_NAME_INHERITED&parentName, ARC_FIELD, UML_ATT, att.Name, classPath, attID
			End If
		End If

		' Now make sure the parent classes don't cause multiple fields of type esriFieldTypeGlobalID
		If bAbstract = False Then
			If attStereo = ARC_FIELD Or Len(attStereo) = 0 Then
				ValidateInheritedFieldTypes bHasGlobalId, elName, path, elID
			End If
		End If

	Next

	' For Tables, Points, Polylines, Polygons, etc. need to make sure some system level fields are in place
	If bAbstract = False And (IsFeatureClass(elStereo) = True Or elStereo = ARC_OBJECT_CLASS) Then
		If elStereo = ARC_OBJECT_CLASS Then
			If bHasOIDField = False Then
				LogWarning MVR_OC_MISSING_OID_FIELD, elStereo, UML_CLASS, elName, path, elID
			End If

			If bHasAttributeIndex = False Then
				LogWarning MVR_OC_MISSING_ATTRIBUTE_INDEX, elStereo, UML_CLASS, elName, path, elID
			End If
		ElseIf IsFeatureClass(elStereo) = True Then
			If bHasOIDField = False Then
				LogWarning MVR_FC_MISSING_OID_FIELD, elStereo, UML_CLASS, elName, path, elID
			End If

			If bHasShapeField = False Then
				LogWarning MVR_FC_MISSING_SHAPE_FIELD, elStereo, UML_CLASS, elName, path, elID
			End If

			If bHasShapeLengthField = False And bIsShapeLengthFieldRequired = True Then
				LogWarning MVR_FC_MISSING_SHAPE_LENGTH_FIELD, elStereo, UML_CLASS, elName, path, elID
			End If

			If bHasShapeAreaField = False And bIsShapeAreaFieldRequired = True Then
				LogWarning MVR_FC_MISSING_SHAPE_AREA_FIELD, elStereo, UML_CLASS, elName, path, elID
			End If

			If bHasAttributeIndex = False Then
				LogWarning MVR_FC_MISSING_ATTRIBUTE_INDEX, elStereo, UML_CLASS, elName, path, elID
			End If

			If bHasSpatialIndex = False Then
				LogWarning MVR_FC_MISSING_SPATIAL_INDEX, elStereo, UML_CLASS, elName, path, elID
			End If
		End If
	End If


End Sub


Sub	ValidateRelationships(el, elName, elStereo, path, elID)

	Dim idx
	Dim con As EA.Connector
	Dim conPath: conPath = path&"::"&elName
	For idx = 0 To el.Connectors.Count - 1
		Set con = el.Connectors.GetAt(idx)
		If el.ElementID = con.ClientID And con.Stereotype = ARC_RELATIONSHIP_CLASS Then
			Dim dest As EA.Element
			Set dest = GetCachedClass(con.SupplierID)
			Dim conID: conID = con.ConnectorID
			Dim conName: conName = con.Name
			ValidateRCName conName, conPath, conID
			ValidateUniqueRCName conName, con, conPath, conID
			If ValidateDestinationClass(dest, conName, conPath, conID) = True Then
				Dim sourceCard: sourceCard = con.SupplierEnd.Cardinality
				Dim destCard: destCard = con.ClientEnd.Cardinality

				' Check the origin primary and foreign keys...
				Dim origPK: origPK = GetTagValByName(con.TaggedValues, ARC_TAG_ORIGIN_PK)
				Dim origPKType: origPKType = ValidateOriginPK(origPK, el, conName, conPath, conID)

				Dim origFK: origFK = GetTagValByName(con.TaggedValues, ARC_TAG_ORIGIN_FK)
				Dim auxClass As EA.Element
				Set auxClass = Nothing
				If con.Subtype = "Class" Then
					Set auxClass = GetCachedClass(CLng(con.MiscData(0)))
					If auxClass Is Nothing Then
						LogError MVR_RC_MISSING_ASSOC_CLASS, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, conPath, conID
					Else
						' For 1-M / M-N / attributed relationship OriginForeignKey lives in the association class
						' Ror 1-1 Attributed relationships, assume OriginForeignKey lives in either the destination or auxiliary class
						ValidateOriginFK origFK, dest, auxClass, origPKType, conName, conPath, conID
					End If
				Else
					ValidateOriginFK origFK, dest, Nothing, origPKType, conName, conPath, conID
				End If

				' Now check the destination primary and foreign keys. Valid only for M-M attributed Rel Classes
				If sourceCard <> "1" And sourceCard <> "0" And  destCard <> "1" And destCard <> "0" Then
					If con.Subtype = "Class" Then
						' Already reported the missing Association Class when checking the originFK
						' So no need to do it again here
						If Not auxClass Is Nothing Then
							Dim destPK: destPK = GetTagValByName(con.TaggedValues, ARC_TAG_DEST_PK)
							Dim destFK: destFK = GetTagValByName(con.TaggedValues, ARC_TAG_DEST_FK)
							Dim destPKType: destPKType = ValidateDestPK(destPK, dest, conName, conPath, conID)
							ValidateDestFK destFK, auxClass, destPKType, conName, conPath, conID
						End If
					Else
						LogError MVR_RC_INVALID_CARD, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, conPath, conID
					End If
				End If
			End If
		End If
	Next

End Sub


Function ValidateDestinationClass(dest, conName, path, conID)

	ValidateDestinationClass = False

	If dest Is Nothing Then
		LogError MVR_RC_MISSING_DESTINATION, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		Exit Function
	End If

	' The destination class must be: an Abstract Class, a Feature Class or a Table
	Dim stereo: stereo = dest.Stereotype 
	If Len(stereo) > 0 Then
		If IsFeatureClass(stereo) = False And stereo <> ARC_OBJECT_CLASS Then
			LogError MVR_RC_INVALID_DESTINATION, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
			Exit Function
		End If
	End If

	ValidateDestinationClass = True

End Function


Function ValidateOriginPK(origPK, el, conName, path, conID)

	ValidateOriginPK = ""

	If Len(origPK) = 0 Then
		LogError MVR_RC_MISSING_ORIG_PK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	Else
		' Search for the attribute in the immediate class and all its parents
		Dim att As EA.Attribute
		Set att = GetCachedOwnedAttByGUID(origPK, el.ElementID)
		If att Is Nothing Then
			LogError MVR_RC_INVALID_ORIG_PK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		Else
			Dim attType: attType = att.Type
			ValidateOriginPK = attType
			ValidateKeyType attType, conName, path, conID
		End If
	End If

End Function


Sub ValidateOriginFK(origFK, destClassEl, auxClassEl, origPKType, conName, path, conID)

	If Len(origFK) = 0 Then
		LogError MVR_RC_MISSING_ORIG_FK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	Else
		' For 1-1 and 1-M relationships classes, the origin FK attribute lives in the destination class.
		Dim att As EA.Attribute
		Set att = GetCachedOwnedAttByGUID(origFK, destClassEl.ElementID)
		' For 1-M,  M-N and attributed relationships, the origin FK attribute lives in the auxiliary (association) class
		If att Is Nothing And Not auxClassEl Is Nothing Then
			Set att = GetCachedOwnedAttByGUID(origFK, auxClassEl.ElementID)
		End If

		If att Is Nothing Then
			LogError MVR_RC_INVALID_ORIG_FK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		Else
			Dim attType: attType = att.Type
			ValidateKeyType attType, conName, path, conID
			If origPKType = ARC_TYPE_OID And attType <> ARC_TYPE_LONG_INT Then
				LogError MVR_RC_INCOMPATIBLE_FK_TYPE, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
			Else
				' warn if the types don't match, but don't issue redundant warning if either type is missing
				If origPKType <> attType And origPKType <> ARC_TYPE_OID And Len(origPKType) > 0 And Len(attType) > 0 Then
					' check whether the primary or foreign key types is a Domain
					If IsMatchingType(attType, origPKType) = False Then
						LogError MVR_RC_KEY_TYPE_MISMATCH&"Origin Primary Key Type = "&origPKType&". Origin Foreign Key Type = "&attType, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
					End If
				End If
			End If
		End If
	End If

End Sub


Function ValidateDestPK(destPK, el, conName, path, conID)

	ValidateDestPK = ""

	If Len(destPK) = 0 Then
		LogError MVR_RC_MISSING_DEST_PK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	Else
		Dim att As EA.Attribute
		Set att = GetCachedOwnedAttByGUID(destPK, el.ElementID)
		If att Is Nothing Then
			LogError MVR_RC_INVALID_DEST_PK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		Else
			Dim attType: attType = att.Type
			ValidateDestPK = attType
			ValidateKeyType attType, conName, path, conID
		End If
	End If

End Function


Sub ValidateDestFK(destFK, el, destPKType, conName, path, conID)

	If Len(destFK) = 0 Then
		LogError MVR_RC_MISSING_DEST_FK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	Else
		Dim att As EA.Attribute
		Set att = GetCachedOwnedAttByGUID(destFK, el.ElementID)
		If att Is Nothing Then
			LogError MVR_RC_INVALID_DEST_FK, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		Else
			Dim attType: attType = att.Type
			ValidateKeyType attType, conName, path, conID
			If destPKType = ARC_TYPE_OID And attType <> ARC_TYPE_LONG_INT Then
				LogError MVR_RC_INCOMPATIBLE_FK_TYPE, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
			Else
				' warn if the types don't match, but don't issue redundant warning if either type is missing
				If destPKType <> attType  And destPKType <> ARC_TYPE_OID And Len(destPKType) > 0 And Len(attType) > 0 Then
					' check whether the primary or foreign key types is a Domain
					If IsMatchingType(attType, destPKType) = False Then		
						LogError MVR_RC_KEY_TYPE_MISMATCH&"Destination Primary Key Type = "&destPKType&". Destination Foreign Key Type = "&attType, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
					End If
				End If
			End If
		End If
	End If

End Sub


' Among the primitive data types, the only invalid key types are: Blob, Raster and Date.
' Coded Value Domains can also be used as a key type, if they are based on a valid primitive type
' see: http://resources.arcgis.com/en/help/main/10.1/index.html#//004t00000004000000
Sub ValidateKeyType(dataType, conName, path, conID)

	If Len(dataType) = 0 Then
		LogError MVR_RC_MISSING_KEY_TYPE, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	Else
		If IsValidKeyType(dataType) = False Then
			' If it's not a valid primitive, check whether it's a valid coded value domain
			Dim bValidDomain : bValidDomain = False
			Dim domType : domType = GetDomainPrimitiveType(dataType)
			If IsValidKeyType(domType) = True Then
				bValidDomain = True
			End If

			If bValidDomain = False Then
				LogError MVR_RC_INVALID_KEY_TYPE&dataType, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
			End If
		End If
	End If

End Sub


Function IsValidKeyType(dataType)

	Dim ret : ret = False
	If IsPrimitiveType(dataType) = True Then
		If dataType <> ARC_TYPE_BLOB AND dataType <> ARC_TYPE_RASTER AND dataType <> ARC_TYPE_DATE Then
			ret = True
		End If	
	End If

	IsValidKeyType = ret

End Function


' Determine if two types match on fundamental primitive type.
' Checks against the base type if either or both are CodedValueDomains
Function IsMatchingType(type1, type2)

	Dim ret : ret = False

	If Len(type1) > 0 And Len(type2) > 0 Then

		Dim bType1Primitive : type1 = IsPrimitiveType(type1)
		Dim bType2Primitive : type2 = IsPrimitiveType(type2)

		Dim domType1, domType2
		If bType1Primitive = False Then
			domType1 = GetDomainPrimitiveType(type1)
		End If

		If bType2Primitive = False Then
			domType2 = GetDomainPrimitiveType(type2)
		End If

		If bType1Primitive = True And bType2Primitive = True And type1 = type2 Then
			ret = True
		ElseIf bType1Primitive = True And bType2Primitive = False And type1 = domType2 Then
			ret = True
		ElseIf bType1Primitive = False And bType2Primitive = True And domType1 = type2 Then
			ret = True
		Else
			If domType1 = domType2 Then
				ret = True
			End If
		End If

	End If

	IsMatchingType = ret

End Function


Sub ValidateFCField(att, attStereo, attName, attType, path, attID)

	ValidateFieldName attName, attStereo, path, attID
	ValidateFieldType att, attStereo, attName, attType, path, attID
	ValidateFieldDefaultVal att, attStereo, attName, attType, path, attID
	ValidateFieldTags att, attStereo, attName, path, attID

End Sub


Sub ValidateSubtypeCodeField(att, attName, path, attID)

	Dim attType : attType = att.Type
	If attType <> ARC_TYPE_LONG_INT And attType <> ARC_TYPE_SMALL_INT Then
		' The Subtype Code can be typed by a CodedValueDomain, if the CodedValueDomain is of an integer (long or short) type
		Dim domType : domType = GetDomainPrimitiveType(attType)
		If domType <> ARC_TYPE_LONG_INT And domType <> ARC_TYPE_SMALL_INT Then
			LogError MVR_FL_SUBTYPE_INVALID_TYPE, ARC_SUBTYPE_FIELD, UML_ATT, attName, path, attID
		End If
	End If

	Dim attDflt : attDflt = att.Default
	If Len(attDflt) = 0 Then
		LogError MVR_FL_SUBTYPE_MISSING_DEFAULT_CODE, ARC_SUBTYPE_FIELD, UML_ATT, attName, path, attID
	ElseIf IsNumeric(attDflt) = False Then
		LogError MVR_FL_SUBTYPE_NON_NUMERIC_DEFAULT_CODE, ARC_SUBTYPE_FIELD, UML_ATT, attName, path, attID
	End If

End Sub


Sub ValidateRangeDomain(el, elName, path, elID)

	ValidateUniqueRangeDomainName elName, el, path, elID

	Dim aryCache(2)
	aryCache(0) = "" ' The first entry is the domain type. Set it empty by default.

	Dim names : names = ","
	Dim classPath : classPath = path&"::"&elName
	Dim idx
	For idx = 0 To el.Attributes.Count -1
		Dim att As EA.Attribute
		Set att = el.Attributes.GetAt(idx)

		Dim attName : attName = att.Name
		Dim attID: attID = att.AttributeID
		Dim attInit
		If attName = ARC_RANGE_MIN_VALUE Then
			attInit = att.Default
			aryCache(1) = attInit
		ElseIf attName = ARC_RANGE_MAX_VALUE Then
			attInit = att.Default
			aryCache(2) = attInit	
		ElseIf attName = ARC_FIELD_TYPE Then
			attInit = att.Default
			aryCache(0) = attInit
		ElseIf attName = ARC_MERGE_POLICY Then
'			ValidateRangeDomainMergePolicy att, attName, path
		ElseIf attName = ARC_SPLIT_POLICY Then
'			ValidateRangeDomainSplitPolicy att, attName, path
		End If
	Next

	ValidateRangeDomainFieldType aryCache(0), el, elName, path, elID
	ValidateRangeDomainValues aryCache(0), aryCache(1), aryCache(2), el, elName, path, elID

	rangeVals(el.ElementID) = aryCache

End Sub


Sub ValidateRangeDomainFieldType(fieldType, el, elName, path, elID)

	If IsValidRangeDomainFieldType(fieldType) = False Then
		LogError MVR_RD_INVALID_TYPE, ARC_RANGE_DOMAIN, UML_CLASS, elName, path, elID
	End If

End Sub


Function IsValidRangeDomainFieldType(fieldType)

	Dim ret: ret = False

	If fieldType = ARC_TYPE_SMALL_INT Or fieldType = ARC_TYPE_LONG_INT Or fieldType = ARC_TYPE_DOUBLE Or fieldType = ARC_TYPE_SINGLE Or fieldType = ARC_TYPE_DATE Then
		ret = True
	End If

	IsValidRangeDomainFieldType = ret

End Function


Sub ValidateRangeDomainValues(rangeType, minVal, maxVal, el, elName, path, elID)

	' If the type is not date, we can at least check the min/max values are numeric
	' Note: ArcCatalog allows the maximum value to be less than the minimum and vice-versa. This may be to allow ranges such as a ranking where 1 is considered "maximum" or top rank.
	If rangeType <> ARC_TYPE_DATE Then
		If IsNumeric(minVal) = False Then
			LogError MVR_RD_INVALID_MIN_VAL, ARC_RANGE_DOMAIN, UML_CLASS, elName, path, elID
		End If

		If IsNumeric(maxVal) = False Then
			LogError MVR_RD_INVALID_MAX_VAL, ARC_RANGE_DOMAIN, UML_CLASS, elName, path, elID
		End If
	Else
		If IsDate(minVal) = False Then
			LogError MVR_RD_INVALID_MIN_VAL_DATE, ARC_RANGE_DOMAIN, UML_CLASS, elName, path, elID
		End If

		If IsDate(maxVal) = False Then
			LogError MVR_RD_INVALID_MAX_VAL_DATE, ARC_RANGE_DOMAIN, UML_CLASS, elName, path, elID
		End If
	End If

End Sub


Sub ValidateCodedValueDomain(el, elName, path, elID)

	ValidateCodedDomainName elName, path, elID
	ValidateUniqueCodedValueName elName, el, path, elID

	Dim desc: desc = GetTagValByName(el.TaggedValues, ARC_TAG_DESCRIPTION)
	If Len(desc) = 0 Then
		LogWarning MVR_CVD_MISSING_DESCRIPTION, ARC_CODED_VALUE_DOMAIN, UML_CLASS, elName, path, elID
	End If

	Dim aryCache()
	ReDim aryCache(0)
	aryCache(0) = "" ' The first entry is the domain type. Set it empty by default.

	Dim names : names = ","
	Dim aryVals() ' There's no safe character to separate domain values, so list them with an array
	ReDim aryVals(0)
	Dim classPath : classPath = path&"::"&elName
	Dim idx
	For idx = 0 To el.Attributes.Count -1
		Dim att As EA.Attribute
		Set att = el.Attributes.GetAt(idx)

		Dim attName : attName = att.Name
		Dim attInit : attInit = att.Default
		Dim attStereo : attStereo = att.Stereotype
		Dim attID: attID = att.AttributeID
		If attStereo = ARC_DOMAIN_CODED_VALUE Then
			' Only do the name uniqueness test for attributes stereotyped <<DomainCodedValue>>
			' In theory these shouldn't clash with "FieldType", "MergePolicy" or "SplitPolicy", but we do not assume such.
			If InStr(names, ","&attName&",") > 0 Then
				LogError MVR_DCV_DUPLICATE_NAME, ARC_DOMAIN_CODED_VALUE, UML_ATT, attName, classPath, attID
			Else
				names = names & attName & ","
			End If

			' Now check for missing or duplicate values
			If Len(attInit) = 0 Then
				LogError MVR_DCV_MISSING_VAL, ARC_DOMAIN_CODED_VALUE, UML_ATT, attName, classPath, attID
			ElseIf IsInArray(aryVals, attInit) = True Then
				LogError MVR_DCV_DUPLICATE_VAL, ARC_DOMAIN_CODED_VALUE, UML_ATT, attName, classPath, attID
			Else
				ReDim Preserve aryVals(UBound(aryVals) + 1)
				aryVals(UBound(aryVals)) = attInit
			End If

			ReDim Preserve aryCache(UBound(aryCache) + 1)
			aryCache(UBound(aryCache)) = attName+"="+att.Default

		ElseIf attName = ARC_FIELD_TYPE Then
			If IsValidDomainFieldType(attInit) = True Then
				aryCache(0) = attInit
			Else
				LogError MVR_CVD_INVALID_TYPE, ARC_DOMAIN_CODED_VALUE, UML_ATT, attName, classPath, attID
			End If
		ElseIf attName = ARC_MERGE_POLICY Then
			ValidateDomainMergePolicy att, attName, path
		ElseIf attName = ARC_SPLIT_POLICY Then
			ValidateDomainSplitPolicy att, attName, path
		End If
	Next

	domainVals(el.ElementID) = aryCache

End Sub


Function IsValidDomainFieldType(fieldType)

	Dim ret: ret = False

	If fieldType = ARC_TYPE_STRING Or fieldType = ARC_TYPE_SMALL_INT Or fieldType = ARC_TYPE_LONG_INT Or fieldType = ARC_TYPE_DOUBLE Or fieldType = ARC_TYPE_SINGLE Or fieldType = ARC_TYPE_DATE Then
		ret = True
	End If

	IsValidDomainFieldType = ret

End Function


Sub ValidateDomainMergePolicy(att, attName, path)
End Sub


Sub ValidateDomainSplitPolicy(att, attName, path)
End Sub


' Check for at least one parent feature/object class via a <<Subtype>> Generalization
' Check each parent exists in workspace
' Check each parent has valid a feature class stereotype
' Check for valid connector properties
' Check that each subtype attribute matches a parent attribute
'
' Does not check for special chars in name - apparently subtypes can have these
Sub ValidateSubtype(el, elName, path, elID)

	ValidateSubtypeName elName, path, elID

	Dim bHasSubtypeRel : bHasSubtypeRel = False

	Dim idx
	Dim con As EA.Connector
	For idx = 0 To el.Connectors.Count - 1
		Set con = el.Connectors.GetAt(idx)
		If el.ElementID = con.ClientID And con.Stereotype = ARC_SUBTYPE Then
			bHasSubtypeRel = True
			Dim parent As EA.Element
			Set parent = ValidateSubtypeParent(con, el, elName, path, elID)
			If Not parent Is Nothing Then
				ValidateSubtypeCode el, parent, path
				ValidateSubtypeFields el, parent, path
			End If
		End If
	Next

	If bHasSubtypeRel = False Then
		LogError MVR_ST_MISSING_REL, ARC_SUBTYPE, UML_CLASS, elName, path, elID
	End If

End Sub


Sub ValidateSubtypeFields(subtype, parent, path)

	Dim idxSub, idxClass
	Dim attSub As EA.Attribute
	Dim attClass As EA.Attribute

	' Make sure each attribute corresponds to one in the parent
	Dim bFound : bFound = False
	Dim bType : bType = False
	For idxSub = 0 To subtype.Attributes.Count - 1

		Set attSub = subtype.Attributes.GetAt(idxSub)
		Dim attSubName: attSubName = attSub.Name
		Dim attSubID: attSubID = attSub.AttributeID
		Dim attSubStereo: attSubStereo = attSub.Stereotype

		For idxClass = 0 To parent.Attributes.Count -1
			Set attClass = parent.Attributes(idxClass)
			If attSubName = attClass.Name Then
				bFound = True
				' Try to match precision, scale, length values on the attributes that are not stereotyped SubtypeField (these values are not relevant for SubtypeFields)
				If LCase(attSubStereo) <> LCase(ARC_SUBTYPE_FIELD) Then
					Dim tagSub, tagParent
					tagSub = GetTagValByName(attSub.TaggedValues, ARC_TAG_LENGTH)
					tagParent = GetTagValByName(attClass.TaggedValues, ARC_TAG_LENGTH)
					If IsEquivalentNumericTag(tagSub, tagParent) = False Then
						LogError MVR_ST_LENGTH_MISMATCH&tagParent, ARC_FIELD, UML_ATT, attSubName, path&"::"&subtype.Name, attSubID
					End If

					tagSub = GetTagValByName(attSub.TaggedValues, ARC_TAG_PRECISION)
					tagParent = GetTagValByName(attClass.TaggedValues, ARC_TAG_PRECISION)
					If IsEquivalentNumericTag(tagSub, tagParent) = False Then
						LogError MVR_ST_PRECISION_MISMATCH&tagParent, ARC_FIELD, UML_ATT, attSubName, path&"::"&subtype.Name, attSubID
					End If

					tagSub = GetTagValByName(attSub.TaggedValues, ARC_TAG_SCALE)
					tagParent = GetTagValByName(attClass.TaggedValues, ARC_TAG_SCALE)
					If IsEquivalentNumericTag(tagSub, tagParent) = False Then
						LogError MVR_ST_SCALE_MISMATCH&tagParent, ARC_FIELD, UML_ATT, attSubName, path&"::"&subtype.Name, attSubID
					End If
				End If

				' Possible scenarios with type...
				' 	Subtype att and Class att types match. If not a valid type: Error
				' 	Subtype att and Class att are both primitive types. If non-matching: Error
				' 	Subtype att and Class att are both domains. If non-matching, or domain non-existent: Error
				' 	Subtype att only is a domain. If domain type does not match parent primitive type: Error
				' 	Subtype att has an initial value. If not in domain or not compatible with type: Error
				Dim attSubType: attSubType = attSub.Type
				Dim attSubTypeClassifier: attSubType = attSub.Type
				Dim attClassType: attClassType = attClass.Type
				Dim bIsSubPrimitive: bIsSubPrimitive = IsPrimitiveType(attSubType)
				Dim bIsClassPrimitive: bIsClassPrimitive = IsPrimitiveType(attClassType)

				If attSubType = attClassType Then
					If bIsSubPrimitive = False Then
						ValidateDomainReference attSub.ClassifierID, attSubType, attSubName, attSub.Default, path&"::"&subtype.Name, attSubID
					End If
				Else
					' The only valid scenario is for subtype att to be a domain type and class att to be primitive
					If bIsSubPrimitive = True Or bIsClassPrimitive = False Then
						LogError MVR_ST_ATT_TYPE_MISMATCH&attClassType, ARC_FIELD, UML_ATT, attSubName, path&"::"&subtype.Name, attSubID
					Else
						ValidateDomainReference attSub.ClassifierID, attSubType, attSubName, attSub.Default, path&"::"&subtype.Name, attSubID
					End If
				End If

				Exit For
			End If
		Next

		If bFound = False Then
			LogError MVR_ST_INVALID_ATT_NAME&attSubName, ARC_FIELD, UML_ATT, attSubName, path&"::"&subtype.Name, attSubID
		End If
	Next

End Sub


Function ValidateDomainReference(classifierID, attType, attName, attInitial, path, attID)

	If IsDomainDefined(classifierID) = False Then
		If IsDomainNameDefined(attType) = False Then
			LogError MVR_FL_TYPE_UNDEFINED_DOMAIN&attType, ARC_FIELD, UML_ATT, attName, path, attID
		Else
			LogWarning MVR_FL_TYPE_UNLINKED_DOMAIN, ARC_FIELD, UML_ATT, attName, path, attID
		End If
	Else
		' It is ok for a field not to specify a default domain value
		If Len(attInitial) > 0 And IsValidDomainValue(classifierID, attInitial) = False Then
			LogError MVR_FL_TYPE_UNDEFINED_DOMAIN_VAL&attInitial, ARC_FIELD, UML_ATT, attName, path, attID
		End If
	End If

End Function


' Test n1 and n2 for numeric equivalence
' Accepts an empty string as equivalent to 0
Function IsEquivalentNumericTag(n1, n2)

	Dim ret : ret = False

	If Len(n1) = 0 And Len(n2) = 0 Then 	'Case 1: Two empty strings are equivalent
			ret = True
	ElseIf Len(n1) = 0 Then
		If IsNumeric(n2) Then
			If CLng(n2) = 0 Then			'Case 2: n1 empty, n2 = 0
				ret = True
			End If
		End If
	ElseIf Len(n2) = 0 Then
		If IsNumeric(n1) Then
			If CLng(n1) = 0 Then			'Case 3: n2 empty, n1 = 0
				ret = True
			End If
		End If
	ElseIf IsNumeric(n1) And IsNumeric(n2) Then
		If n1 = n2 Then						'Case 4: n1 = n2 (non-empty strings)
			ret = True
		End If
	End If

	IsEquivalentNumericTag = ret

End Function


Function ValidateSubtypeParent(con, el, elName, path, elID)

	Dim parent : Set parent = GetCachedClass(con.SupplierID)

	If parent Is Nothing Then
		LogError MVR_ST_MISSING_PARENT, ARC_SUBTYPE, UML_CLASS, elName, path, elID
	Else
		If IsFeatureClass(parent.Stereotype) = False And parent.Stereotype <> ARC_OBJECT_CLASS Then
			LogError MVR_ST_INVALID_PARENT, ARC_SUBTYPE, UML_CLASS, elName, path, elID
		End If
	End If

	Set ValidateSubtypeParent = parent

End Function


Sub ValidateSubtypeCode(subtype, parent, path)

	' Find where the SubtypeCode has been specified
	' Order of precedence is: subtype tag first, then look for attribute with stereotype <<SubtypeField>>
	Dim subtypeCode
	subtypeCode = GetTagValByName(subtype.TaggedValues, ARC_SUBTYPE_CODE)
	If Len(subtypeCode) = 0 Or subtypeCode = "-1" Then
		Dim att As EA.Attribute
		Set att = GetAttByStereotype(parent.Attributes, ARC_SUBTYPE_FIELD)
		If Not att Is Nothing Then
			Set att = GetAttByName(subtype.Attributes, att.Name)
			If Not att Is Nothing Then
				subtypeCode = att.Default
			End If
		End If
	End If

	If Len(subtypeCode) = 0 Or subtypeCode = "-1" Then
		LogError MVR_ST_MISSING_CODE, ARC_SUBTYPE, UML_CLASS, subtype.Name, path, subtype.ElementID
	ElseIf IsNumeric(subtypeCode) = False Then
		LogError MVR_ST_INVALID_CODE, ARC_SUBTYPE, UML_CLASS, subtype.Name, path, subtype.ElementID
	Else
		' store the code for future reference, if it's not a duplicate
		Dim parentID : parentID = parent.ElementID
		If subtypeCodes.Exists(parentID) = True Then
			Dim codes
			codes = subtypeCodes(parentID)(1)
			If Len(codes) > 0 Then
				If InStr(codes, ","&subtypeCode&",") > 0 Then
					LogError MVR_ST_DUPLICATE_CODE, ARC_SUBTYPE, UML_CLASS, subtype.Name, path,  subtype.ElementID
				Else
					UpdateSubtypeCodes parentID, codes&subtypeCode&","
				End If
			Else
				UpdateSubtypeCodes parentID, ","&subtypeCode&","
			End If
		Else
			Dim ary(2)
			ary(1) = ","&subtypeCode&","
			subtypeCodes.Add parentID, ary
		End If
	End If

End Sub


Sub UpdateSubtypeCodes(parentID, newCodes)

	Dim ary: ary = subtypeCodes(parentID)
	ary(1) = newCodes
	subtypeCodes(parentID) = ary

End Sub


Sub ValidateBooleanTag(tags, tagName, elStereo, elType, elName, path, elID)

	Dim tagVal : tagVal = GetTagValByName(tags, tagName)

	If tagVal <> "true" And tagVal <> "false" Then
		LogError MVR_TV_MISSING&tagName, elStereo, elType, elName, path, elID
	End If

End Sub


' Extracts the className from a string of fieldNames of form ,class1Name:att1Name,class1Name:att2Name,class2Name:att1Name,...
' The first match in the string is returned
' Returns empty if no match found
Function FindParentNameFromFields(inheritedFieldNames, attName)

	Dim ret : ret = ""
	Dim endPos : endPos = InStr(inheritedFieldNames, ":"&attName&",")
	If  endPos > 0 Then
		Dim startPos : startPos = endPos - 1
		Do While startPos > 0
			If Mid(inheritedFieldNames,startPos,1) = "," Then
				Exit Do
			End If
			startPos = startPos - 1
		Loop
		ret = Mid(inheritedFieldNames, startPos+1, endPos-startPos-1)
	End If

	FindParentNameFromFields = ret

End Function


' Looks for duplicate field names in the input list of inherited fields. Writes an error for each duplicate found.
' inheritedFieldNames is a string of lowercase fieldNames of form ,class1Name:att1Name,class1Name:att2Name,class2Name:att1Name,...
' This function does not check for duplicates within the same class to avoid raising duplicate errors
Sub ValidateInheritedFieldNames(inheritedFieldNames, elName, path, elID)

	If Len(inheritedFieldNames) = 0 Or inheritedFieldNames = "," Then
		Exit Sub
	End If

	Dim aryAttNames
	aryAttNames = Split(inheritedFieldNames, ",")

	Dim idxClassAtt : idxClassAtt = 0
	Dim idxSearch : idxSearch = 0
	Dim name, attName, className, pos
	Do While idxClassAtt < UBound(aryAttNames)

		If idxSearch > UBound(aryAttNames) Then
			Exit Do
		End If

		name = aryAttNames(idxClassAtt)
		pos = Instr(name, ":")
		If pos > 0 Then
			className = Mid(name, 1, pos-1)
			attName = Mid(name, pos+1, Len(name)-pos)

			' Skip past attributes in the same class ie. start searching for duplicates from the next class onwards
			If idxClassAtt = idxSearch Then
				Dim tmpName, tmpPos
				For idxSearch = idxClassAtt + 1 To UBound(aryAttNames)
					tmpName = aryAttNames(idxSearch)
					tmpPos = Instr(tmpName, ":")
					If tmpPos > 0 Then
						' Get the class name for the next attribute and compare it to the current class
						tmpName = Mid(tmpName, 1, tmpPos-1)
						If tmpName <> className Then
							Exit For
						End If
					End If
				Next
			End If

			' Note: the attribute names in inheritedFieldNames are assumed to have been forced to lower case.
			If attName <> "objectid" And attName <> "objectid_idx" And attName <> "shape" And attName <> "shape_idx" And attName <> "shape_length" And attName <> "shape_area" Then
				If idxSearch < UBound(aryAttNames) Then
					' Finally we can start searching for duplicate field names
					Dim idxCurAtt
					For idxCurAtt = idxSearch To UBound(aryAttNames)
						tmpName = aryAttNames(idxCurAtt)
						tmpPos = Instr(tmpName, ":")
						If tmpPos > 0 Then
							tmpName = Mid(tmpName, tmpPos+1, Len(tmpName)-tmpPos)
							If tmpName = attName Then
								LogError MVR_FL_DUPLICATE_NAME_INHERITED_EX&aryAttNames(idxClassAtt)&", "&aryAttNames(idxCurAtt), ARC_FIELD, UML_CLASS, elName, path, elID
							End If
						End If
					Next
				End If
			End If

		End If

		idxClassAtt = idxClassAtt + 1
		If idxSearch < idxClassAtt Then
			idxSearch = idxClassAtt
		End If
	Loop

End Sub

' Searches through each inherited attribute
' Reports an error if more than one attribute has type esriFieldTypeGlobalID
Sub ValidateInheritedFieldTypes (bHasGlobalId, elName, path, elID)

	Dim bGlobalIDExists
	bGlobalIDExists = bHasGlobalId

	Dim ary, guid
	ary = elAtts(elID)(1)

	Dim att As EA.Attribute
	For Each guid In ary
		Set att = GetCachedAttByGUID(guid)
		If att.Type = ARC_TYPE_GID Then
			If bGlobalIDExists = True Then
				' The path won't be correct in this error, because of being an inherited attribute, but sufficient info is given to track the attribute.
				LogError MVR_FL_TYPE_MULTIPLE_GID_EX, att.Stereotype, UML_ATT, att.Name, path, att.AttributeID
			Else
				bGlobalIDExists = True
			End If
		End If
	Next

End Sub


' Returns all attribute names inherited by el
' The returned string has the form: ,class1Name:att1Name,class1Name:att2Name,class2Name:att1Name,...
' Populates the array parentAttGUIDS with the corresponding Attribute GUIDS of inherited attributes
Function GetInheritedAtts(el, ByRef parentAttGUIDS)

	Dim ret : ret = ","

	Dim ids
	ids = el.GetRelationSet(rsGeneralizeStart)

	Dim aryIds
	aryIds = Split(ids, ",")

	Dim count : count = 0
	Dim parentID
	For Each parentID In aryIds
		Dim parent As EA.Element
		Set parent = GetCachedClass(CLng(parentID))
		If Not parent Is Nothing Then
			Dim idx
			For idx = 0 To parent.Attributes.Count - 1
				Dim att As EA.Attribute
				Set att = parent.Attributes.GetAt(idx)
				ret = ret & parent.Name&":"&LCase(att.Name)&","
				ReDim Preserve parentAttGUIDS(count)
				parentAttGUIDS(count) = att.AttributeGUID
				count = count + 1
			Next
		End If
	Next

	GetInheritedAtts = ret

End Function


Function GetInheritedAttNames(elID)

	Dim ret
	
	If elAtts.Exists(elID) = True Then
		ret = elAtts(elID)(2)
	Else
		OutputLine "Internal Error: GetInheritedAttNames: Could not locate element with ID"&elID
	End If

	GetInheritedAttNames = ret

End Function


Function ValidateClassStereo(elStereo, elName, path, elID)

	Dim ret : ret = True

	If IsValidClassStereo(elStereo) = False Then
		LogWarning MVR_EL_STEREO, elStereo, UML_CLASS, elName, path, elID
		ret = False
	End If

	ValidateClassStereo = ret

End Function


Sub ValidateFDName(pkgName, path, pkgID)

	If IsValidFDName(pkgName) = False Then
		LogWarning MVR_FD_NAME, ARC_FEATURE_DATASET, UML_PACKAGE, pkgName, path, pkgID
	End If

End Sub


Sub ValidateFCName(elName, elStereo, path, elID)

	If IsValidFCName(elName) = False Then
		LogError MVR_FC_NAME, elStereo, UML_CLASS, elName, path, elID
	Else
		Dim prefix: prefix = Right(elName, 4)
		' There's a few prefixes we can't use either - see: http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#//002200000002000000.htm
		If Len(prefix) = 4 Then
			If prefix = "gdb_" Or prefix = "sde_" Or InStr(elName, "delta_") = 1 Then
				LogError MVR_FC_NAME_PREFIX, elStereo, UML_CLASS, elName, path, elID
			End If
		End If
	End If

	If Len(elName) > MAX_FC_NAME Then
		LogWarning MVR_FC_LONG_NAME, elStereo, UML_CLASS, elName, path, elID
	End If

End Sub


Sub ValidateRCName(conName, path, conID)

	If IsValidFCName(conName) = False Then
		If Len(conName) = 0 Then
			LogError MVR_RC_MISSING_NAME, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		Else
			LogError MVR_RC_INVALID_NAME, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
		End If
	End If

	If Len(conName) > MAX_FC_NAME Then
		LogWarning MVR_RC_LONG_NAME, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	End If

End Sub


Sub ValidateCodedDomainName(elName, path, elID)

	' There are only two illegal characters: ' and `. However backslash fails on PostgreSQL, hence warning for these and other special chars
	If IsValidFCName(elName) = False Then
		If Instr(elName, "'") > 0 Or Instr(elName, "`") > 0 Then
			LogError MVR_CVD_NAME_ILLEGAL_CHARS, ARC_CODED_VALUE_DOMAIN, UML_CLASS, elName, path, elID
		Else
			LogWarning MVR_CVD_NAME_SPEC_CHARS, ARC_CODED_VALUE_DOMAIN, UML_CLASS, elName, path, elID
		End If
	End If

	If Len(elName) > MAX_FC_NAME Then
		LogWarning MVR_CVD_LONG_NAME, ARC_CODED_VALUE_DOMAIN, UML_CLASS, elName, path, elID
	End If

End Sub



Sub ValidateSubtypeName(elName, path, elID)
	
	If Len(elName) > MAX_FC_NAME Then
		LogWarning MVR_ST_LONG_NAME, ARC_SUBTYPE, UML_CLASS, elName, path, elID
	End If

End Sub


Sub ValidateFieldName(attName, attStereo, path, attID)

	If IsValidFCName(attName) = False Then
		LogError MVR_FL_NAME, attStereo, UML_ATT, attName, path, attID
	End If

	' Max name length for attributes is assumed to be the same as for classes
	If Len(attName) > MAX_FC_NAME Then
		LogWarning MVR_FL_LONG_NAME, attStereo, UML_ATT, attName, path, attID
	End If

	If IsReservedKeyword(attName) = True Then
		LogError MVR_FL_NAME_RESERVED, attStereo, UML_ATT, attName, path, attID
	End If

End Sub


Sub ValidateUniqueFCName(elName, elStereo, el, path, elID)

	Dim lowerName : lowerName = LCase(elName)

	If classNames.Exists(lowerName) = True Then
		LogError MVR_FC_DUPLICATE_NAME, elStereo, UML_CLASS, elName, path, elID
	Else
		Set classNames(lowerName) = el
	End If

End Sub


Sub ValidateUniqueCodedValueName(elName, el, path, elID)

	If domainNames.Exists(elName) = True Then
		LogError MVR_CVD_DUPLICATE_NAME, ARC_CODED_VALUE_DOMAIN, UML_CLASS, elName, path, elID
	Else
		Set domainNames(elName) = el
	End If

End Sub


Sub ValidateUniqueRangeDomainName(elName, el, path, elID)

	If rangeNames.Exists(elName) = True Then
		LogError MVR_RD_DUPLICATE_NAME, ARC_RANGE_DOMAIN, UML_CLASS, elName, path, elID
	Else
		Set rangeNames(elName) = el
	End If

End Sub


Sub ValidateUniqueRCName(conName, con, path, conID)

	If rcNames.Exists(conName) = True Then
		LogError MVR_RC_DUPLICATE_NAME, ARC_RELATIONSHIP_CLASS, UML_CONNECTOR, conName, path, conID
	Else
		Set rcNames(conName) = con
	End If

End Sub


Sub ValidateFieldType(att, attStereo, attName, attType, path, attID)

	Dim err : err = ""
	Dim warn : warn = ""
	Dim scale, precision, length

	' Check that a Long Int specifies a valid precision value
	If attType = ARC_TYPE_LONG_INT Then
		precision = GetTagValByName(att.TaggedValues, ARC_TAG_PRECISION)
		If Len(precision) > 0 Then
			If IsNumeric(precision) = False Then
				err = MVR_FL_TYPE_LONGINT_INVALID_PRECISION
			ElseIf CLng(precision) > MAX_PRECISION_INT Or CLng(precision) < MIN_PRECISION_INT Then
				err = MVR_FL_TYPE_LONGINT_OUT_OF_BOUNDS_PRECISION
			End If
		Else
			warn = MVR_FL_TYPE_LONGINT_MISSING_PRECISION
		End If
	ElseIf attType = ARC_TYPE_STRING Then
		length = GetTagValByName(att.TaggedValues, ARC_TAG_LENGTH)
		If Len(length) > 0 Then
			If IsNumeric(length) = False Then
				err = MVR_FL_TYPE_TEXT_INVALID_LENGTH
			Else
				If CLng(length) > MAX_TEXT_LENGTH Or CLng(length) < MIN_TEXT_LENGTH Then
					warn = MVR_FL_TYPE_TEXT_OUT_OF_BOUNDS_LENGTH
				End If
			End If
		Else
			warn = MVR_FL_TYPE_TEXT_MISSING_LENGTH
		End If
	ElseIf attType = ARC_TYPE_DOUBLE Or attType = ARC_TYPE_SINGLE Then
		precision = GetTagValByName(att.TaggedValues, ARC_TAG_PRECISION)
		If Len(precision) > 0 Then
			If IsNumeric(precision) = False Then
				err = MVR_FL_TYPE_INVALID_PRECISION
			ElseIf attType = ARC_TYPE_DOUBLE And (CLng(precision) > MAX_PRECISION_DOUBLE Or CLng(precision) < MIN_PRECISION_DOUBLE) Then
				err = MVR_FL_TYPE_OUT_OF_BOUNDS_PRECISION_DOUBLE
			ElseIf attType = ARC_TYPE_SINGLE And (CLng(precision) > MAX_PRECISION_FLOAT Or CLng(precision) < MIN_PRECISION_FLOAT) Then
				err = MVR_FL_TYPE_OUT_OF_BOUNDS_PRECISION_FLOAT
			End If
		Else
			LogWarning MVR_FL_TYPE_MISSING_PRECISION, attStereo, UML_ATT, attName, path, attID
		End If

		If Len(err) > 0 Then
			LogError err, attStereo, UML_ATT, attName, path, attID
			err = ""
		End If

		scale = GetTagValByName(att.TaggedValues, ARC_TAG_SCALE)
		If Len(scale) > 0 Then
			If IsNumeric(scale) = False Then
				err = MVR_FL_TYPE_INVALID_SCALE
			ElseIf attType = ARC_TYPE_DOUBLE And (CLng(scale) > MAX_SCALE_DOUBLE Or CLng(scale) < MIN_SCALE_DOUBLE) Then
				err = MVR_FL_TYPE_OUT_OF_BOUNDS_SCALE_DOUBLE
			ElseIf attType = ARC_TYPE_SINGLE And (CLng(scale) > MAX_SCALE_FLOAT Or CLng(scale) < MIN_SCALE_FLOAT) Then
				err = MVR_FL_TYPE_OUT_OF_BOUNDS_SCALE_FLOAT
			End If
		Else
			warn = MVR_FL_TYPE_MISSING_SCALE
		End If
'	ElseIf attType = ARC_TYPE_GUID Then
' There may not be a precise rule that can be used to determine whether esriFieldTypeGUID fields should allow null values
'		Dim tagVal : tagVal = LCase(GetTagValByName(att.TaggedValues, ARC_TAG_ISNULLABLE))
'		If tagVal = "true" Then
'
'			warn = MVR_FL_TYPE_ISNULLABLE_CONFLICT
'		End If
	Else
		' Check whether the type is missing or is one of the remaining ArcGIS primitives or a CodedValueDomain or a RangeDomain
		If Len(attType) = 0 Then
			err = MVR_FL_TYPE_MISSING
		ElseIf IsPrimitiveType(attType) = False Then
			ValidateDomainReference att.ClassifierID, attType, attName, att.Default, path, attID
		End If
	End If

	If Len(err) > 0 Then
		LogError err, attStereo, UML_ATT, attName, path, attID
	End If

	If Len(warn) > 0 Then
		LogWarning warn, attStereo, UML_ATT, attName, path, attID
	End If

End Sub


Sub ValidateFieldDefaultVal(att, attStereo, attName, attType, path, attID)

	' Finally check whether the default value matches the field type.
	' Currently, we only check that integer types have a numeric value and date types have a valid date value.
	Dim defaultVal : defaultVal = att.Default
	If Len(defaultVal) > 0 Then
		If IsNumericFieldType(attType) = True Then
			If IsNumeric(defaultVal) = False Then
				LogError MVR_FL_DEFAULT_VAL_INT_TYPE_MISMATCH, attStereo, UML_ATT, attName, path, attID
			End If
		ElseIf attType = ARC_TYPE_DATE Then
			If IsDate(defaultVal) = False Then
				LogError MVR_FL_DEFAULT_VAL_DATE_TYPE_MISMATCH, attStereo, UML_ATT, attName, path, attID
			End If
		End If
	End If

End Sub


Function IsNumericFieldType(fieldType)

	Dim ret : ret = False

	If fieldType = ARC_TYPE_LONG_INT Or fieldType = ARC_TYPE_SMALL_INT Or fieldType = ARC_TYPE_DOUBLE Or fieldType = ARC_TYPE_SINGLE Or fieldType = ARC_TYPE_OID Then
		ret = True
	End If

	IsNumericFieldType = ret

End Function


Sub ValidateFieldTags(att, attStereo, attName, path, attID)

	ValidateBooleanTag att.TaggedValues, ARC_TAG_ISNULLABLE, attStereo, UML_ATT, attName, path, attID

End Sub


Sub ValidateSpatialReferenceValue(tags, elStereo, elType, elName, path, elID)

	Dim ret : ret = GetTagValByName(tags, ARC_TAG_SPATIAL_REF)
	If Len(ret) = 0 Then
		LogError MVR_SR_MISSING_VAL,elStereo, elType, elName, path, elID
	Else
		If IsValidSpatialReference(ret) = False Then
			LogError MVR_SR_INVALID_LINK, elStereo, elType, elName, path, elID
		End If
	End If

End Sub


Function IsValidSpatialReference(guid)

	Dim ret : ret = False

	Dim idx
	Dim el As EA.Element
	For idx = 0 To spatialRefEls.Count - 1
		Set el = spatialRefEls.GetAt(idx)
		If Not el Is Nothing Then
			If el.ElementGUID = guid Then 
				ret = True
				Exit For
			End If
		End If
	Next

	IsValidSpatialReference = ret

End Function


' Calls IsValidFCName to determine if 'name' breaks any ArcGIS naming conventions for fields and feature classes
' (IsValidFDName allows a special case for 'name' starting with digits, which ArcGIS seems to accept)
' Returns True, if 'name' is a valid feature dataset name
' Returns False, otherwise
Function IsValidFDName(name)

	Dim ret : ret = IsValidFCName(name)

	If ret = False And Len(name) > 0 Then
		Dim bIsFirstCharNumeric: bIsFirstCharNumeric = IsNumeric(Left(name,1))
		If bIsFirstCharNumeric = True Then
			ret = True
		End If

	End If

	IsValidFDName = ret

End Function


' Checks whether 'name' breaks any ArcGIS naming conventions for fields and feature classes
' (cannot be an empty string, first character cannot be numeric, no special chars, no spaces etc.)
' Returns True, if none of the naming rules are broken
' Returns False, otherwise
Function IsValidFCName(name)

	Dim ret : ret = False

	If Len(name) > 0 Then
		Dim bIsFirstCharNumeric, bHasSpecialChars, pos
		bIsFirstCharNumeric = IsNumeric(Left(name,1))

		If bIsFirstCharNumeric = False Then

			Dim specChars, count
			specChars = "<>,.?\/|:;'¬`~!@#$%^&*()+= "&vbtab

			For count = 1 to Len(specChars)
			  pos = Mid(specChars, count, 1)
			  If InStr(name, pos) > 0 Then
				bHasSpecialChars = True
				Exit For
			  End If
			Next

			If bHasSpecialChars = False Then
				ret = True
			End If

		End If
	End If

	IsValidFCName = ret

End Function


Function IsValidClassStereo(elStereo)

	Dim ret : ret = False
	Dim stereos : stereos = ",Point,Polyline,Polygon,MultiPatch,Multipoint,RangeDomain,CodedValueDomain,RelationshipClass,ObjectClass,Subtype,RasterBand,RasterCatalog,StorageDef,SpatialReference,"

	If InStr(stereos,","&elStereo&",") > 0 Then
		ret = True
	End If

	IsValidClassStereo = ret

End Function


Function IsFeatureClass(elStereo)

	Dim ret : ret = False
	Dim stereos : stereos = ",Point,Polyline,Polygon,MultiPatch,Multipoint,RasterCatalog,"

	If InStr(stereos,","&elStereo&",") > 0 Then
		ret = True
	End If

	IsFeatureClass = ret

End Function


Function IsShapeLengthFieldRequired(elStereo)

	IsShapeLengthFieldRequired = (elStereo = ARC_POLYGON Or elStereo = ARC_POLYLINE Or elStereo = ARC_RASTER_CATALOG)

End Function


Function IsShapeAreaFieldRequired(elStereo)

	IsShapeAreaFieldRequired = (elStereo = ARC_POLYGON Or elStereo = ARC_RASTER_CATALOG)

End Function


Function IsPrimitiveType(attType)

	Dim ret : ret = False

	If attType = ARC_TYPE_LONG_INT Or attType = ARC_TYPE_SMALL_INT Or attType = ARC_TYPE_DOUBLE Or attType = ARC_TYPE_SINGLE Or _
	attType = ARC_TYPE_STRING Or attType = ARC_TYPE_DATE Or attType = ARC_TYPE_GEOMETRY Or attType = ARC_TYPE_OID Or _
	attType = ARC_TYPE_BLOB Or attType = ARC_TYPE_GID Or attType = ARC_TYPE_RASTER Or attType = ARC_TYPE_GUID Or attType = ARC_TYPE_XML Then
		ret = True
	End If

	IsPrimitiveType = ret

End Function


Function IsReservedKeyword(name)

	Dim ret : ret = False

	If InStr(RESERVED_DBMS_WORDS, ","&LCase(name)&",") > 0 Then
		ret = True
	End If

	IsReservedKeyword = ret

End Function


Function IsDomainDefined(classID)

	Dim ret : ret = False

	If domainVals.Exists(classID) = True Or rangeVals.Exists(classID) = True Then
		ret = True
	End If

	IsDomainDefined = ret

End Function


Function IsDomainNameDefined(name)

	Dim ret : ret = False

	If domainNames.Exists(name) = True Or rangeNames.Exists(name) = True Then
		ret = True
	End If

	IsDomainNameDefined = ret

End Function


Function IsValidDomainValue(classID, val)

	Dim ret : ret = False

	Dim ary
	If domainVals.Exists(classID) = True Then
		ary = domainVals(classID)
		Dim idx
		' Skip the first entry in the array as it just contains the domain type
		For idx = 1 To  UBound(ary)
			Dim str, domainVal, pos
			str = ary(idx)
			pos = Instr(str, "=")
			If pos > 0 Then
				domainVal = Mid(str, pos+1)
				If val = domainVal Then
					ret = True
					Exit For
				End If
			End If
		Next
	Else
		If rangeVals.Exists(classID) = True Then
			ary = rangeVals(classID)
			If ary(0) <> ARC_TYPE_DATE Then
				'TODO: Currently we just check that the type matches. It would be possible to also check that the min/max values are within bounds
				' with some logic that first checks whether the RangeDomain's max value is greater than or less than min value, then compares values accordingly
				' (ie. if RangeMax < RangeMin, then val must be greater than or equal to RangeMax and less than or equal to RangeMin)
				If IsNumeric(val) = True Then
					ret = True
				End If
			Else
				'For Range Domains of type esriFieldTypeDate, check that val is a valid date format and it is within the min/max range values
				If IsDate(val) = True And IsDate(ary(1)) = True And IsDate(ary(2)) Then
					If CDate(val) >= CDate(ary(1)) And CDate(val) <= CDate(ary(2)) Then
						ret = True
					End If
				End If
			End If
		End If
	End If

	IsValidDomainValue = ret

End Function


Function GetDomainPrimitiveType(domainName)

	Dim ret : ret = ""

	Dim el As EA.Element
	If domainNames.Exists(domainName) = True Then
		Set el = domainNames(domainName)
		If Not el Is Nothing Then
			Dim classID : classID = el.ElementID
			If domainVals.Exists(classID) = True Then
				Dim ary: ary = domainVals(classID)
				' The first entry in the array is guaranteed to exist and always contains the domain type
				ret = ary(0)
			End If
		End If
	End If

	GetDomainPrimitiveType = ret

End Function


Sub LogError(errDesc, elStereo, elType, elName, elPath, elID)

	LogLine "Error", errDesc, elStereo, elType, elName, elPath, elID, numErrs

End Sub


Sub LogWarning(warnDesc, elStereo, elType, elName, elPath, elID)

	LogLine "Warning", warnDesc, elStereo, elType, elName, elPath, elID, numWarns

End Sub


Sub LogLine(severity, desc, elStereo, elType, elName, elPath, elID, ByRef counter)

	Dim code, shortDesc, details
	SplitRuleText desc, code, shortDesc, details

	' Check the user didn't request to suppress this kind of error or warning.
	If IsInArray(skipRules, code) = True Then
		Exit Sub
	End If

	Dim stereo
	If Len(elStereo) = 0 Then
		stereo = ""
	Else
		stereo = "<<"&elStereo&">>"
	End If

	' EA strips the following prefix from lines output to the ArcGIS validation window.
	' The prefix tells EA what the object type is so that it can jump to the appropriate model element/pkg/att
	' Note: Jump to connector, not currently supported by EA.
	Dim prefix
	If elType = UML_PACKAGE Then
		prefix = "Pkg:"
	ElseIf elType = UML_CLASS Then
		prefix = "Ele:"
	ElseIf elType = UML_CONNECTOR Then
		prefix = "Con:"
	ElseIf elType = UML_ATT Then
		prefix = "Att:"
	Else
		prefix = ""
	End If

	' Increment the error/warning counter
	counter = counter + 1

	WriteOutput OUTPUT_TAB_NAME, prefix&severity&" "&code&": "&shortDesc&","&elName&","&stereo&",UML "&elType&",Path="&elPath&","&details&VbCrLf & VbCrLf, elID

'	WriteOutput OUTPUT_TAB_NAME, severity&" "&code&": "&shortDesc&","&elName&","&stereo&",UML "&elType&",Path="&elPath&","&details&VbCrLf & VbCrLf, elID

End Sub


Sub SplitRuleText(ruleText, ruleCode, shortDesc, details)

	Dim ary : ary = Split(ruleText, ";")
	If UBound(ary) <> 2 Then
		OutputLine "Internal Error: Malformed Validation Rule: "&ruleText
	Else
		ruleCode = ary(0)
		shortDesc = ary(1)
		details = ary(2)
	End If

End Sub


Sub OutputLine(line)

	WriteOutput OUTPUT_TAB_NAME, line, 0

End Sub

''''''''''''''''''''''
''
'' Utility Functions
''
''''''''''''''''''''''

' Returns a comma separated string containing each Package ID in the workspace, including the workspace package ID itself
' workspacePkg is an EA.Package object that corresponds to the workspace package for the schema.
Function GetWorkspacePkgIds(workspacePkg)

	Dim idsList : idsList = ""

	If Not workspacePkg Is Nothing Then
		Dim workspacePkgIds()
		ReDim workspacePkgIds(0)
		workspacePkgIds(0) = workspacePkg.PackageID
		GetDescendantPkgs workspacePkg.PackageID, workspacePkgIds
		Dim id
		For Each id In workspacePkgIds
			idsList = idsList & id & ","
		Next
		If Len(idsList) > 1 Then
			idsList = Left(idsList, Len(idsList) - 1)
		End If
	End If

	GetWorkspacePkgIds = idsList

End Function


' Returns a zero-based array of package ids that represent descendants of the package with package id: rootID
' Recurses over sub-packages and adds their descendants to aryIds
Sub GetDescendantPkgs(rootID, ByRef aryIds)

	Dim pkgIDStr : pkgIDStr = "Package_ID"
	Dim DBMS : DBMS = Repository.RepositoryType()
	If DBMS = MODEL_DB_TYPE_POSTGRES Then
		pkgIDstr = "package_id"
	ElseIf DBMS = MODEL_DB_TYPE_ORACLE Or DBMS = MODEL_DB_TYPE_FIREBIRD Then
		pkgIDstr = "PACKAGE_ID"
	End If

	Dim sql : sql = "SELECT t_package.Package_ID FROM t_package WHERE t_package.Parent_ID=" & rootID
	Dim aryChildIds : aryChildIds = BuildArrayFromSQL(sql,pkgIDstr)

	If UBound(aryChildIds) < 0 Then
		Exit Sub
	End If

	Dim id, pos
	pos = UBound(aryIds) + 1
	For Each id In aryChildIds
		Redim Preserve aryIds(pos)
		aryIds(pos) = id
		pos = pos + 1
	Next

	For Each id In aryChildIds
		GetDescendantPkgs id, aryIds
	Next

End Sub


Function GetWorkspaceEls(workspacePkgIds)

	Dim sql : sql = "SELECT t_object.Object_ID FROM t_object, t_package WHERE t_object.Object_Type = 'Class' AND t_package.Package_ID = t_object.Package_ID AND t_object.Package_ID IN (" + workspacePkgIds + ") AND t_package.Name <> 'ESRI Interfaces' AND t_package.Name <> 'ESRI Classes' AND t_package.Name <> 'ESRI Network' AND t_package.Name <> 'Spatial References'"
	Dim els As EA.Collection
	Set els = GetElementSet(sql,2)

	Set GetWorkspaceEls = els

End Function


Sub MapPkgIds(workspacePkgIds)

	Dim aryIds
	aryIds = Split(workspacePkgIds, ",")

	Dim pkgID
	For Each pkgID In aryIds
		Dim pkg As EA.Package
		Set pkg = GetPackageByID(pkgID)
		If Not pkg Is Nothing Then
			idPkgs.Add pkgID, pkg
		End If
	Next

End Sub


Function GetCachedPkg(pkgID)

	Dim ret: Set ret = Nothing

	If idPkgs.Exists(pkgID) = True Then
		Set ret = idPkgs(pkgID)
	End If

	Set GetCachedPkg = ret

End Function


' Takes a collection of EA.Elements and populates these VBScript dictionary objects: idClasses, pkgClasses, attGuids
' idClasses is populated with EA.ElementID-EA.Element key-value pairs
' pkgClasses is populated with PackageID-EA.Element key-value pairs
' attGuids is populated with AttributeGUID-Attribute key-value pairs
Sub MapClassIds(workspaceEls)
	Dim idx
	Dim el As EA.Element
	For idx = 0 To workspaceEls.Count - 1
		Set el = workspaceEls.GetAt(idx)
		If Not el Is Nothing Then
			idClasses.Add el.ElementID, el
			Dim pkgId, aryIds, pos
			pkgId = el.PackageID
			If pkgClasses.Exists(pkgId) = True Then
				aryIds = pkgClasses(pkgId)
				pos = UBound(aryIds) + 1
				Redim Preserve aryIds(pos)
				Set aryIds(pos) = el
				pkgClasses(pkgId) = aryIds
			Else
				Redim aryIds(0)
				Set aryIds(0) = el
				pkgClasses(pkgId) = aryIds
			End If
		End If
	Next

	' Now that every class has been cached, we can safely cache inherited attributes...
	For idx = 0 To workspaceEls.Count - 1
		Set el = workspaceEls.GetAt(idx)
		If Not el Is Nothing Then
			MapAttsCache(el)
		End If
	Next

End Sub


Function GetCachedClass(classID)

	Dim ret: Set ret = Nothing

	If idClasses.Exists(classID) = True Then
		Set ret = idClasses(classID)
	End If

	Set GetCachedClass = ret

End Function


Sub MapAttsCache(el)

	Dim elStereo : elStereo = el.Stereotype
	Dim bAbstract : bAbstract = el.Abstract = "1"
	Dim elID: elID = el.ElementID

	' Only cache attributes for feature classes, object classes, relationship (UML Association) classes, unstereotyped classes and abstract classes
	Dim bCache : bCache = False
	If bAbstract = False Then
		If IsFeatureClass(elStereo) = True Then
			bCache = True
		ElseIf elStereo = ARC_OBJECT_CLASS Then
			bCache = True
		ElseIf elStereo = ARC_RELATIONSHIP_CLASS Then
			bCache = True
		ElseIf Len(elStereo) = 0 Then
			bCache = True
		End If
	Else
		bCache = True
	End If

	If bCache = True Then
		ReDim aryGUIDS(-1)
		Dim att As EA.Attribute
		Dim attCount : attCount = el.Attributes.Count
		Dim idx
		For idx = 0 To attCount - 1
			Set att = el.Attributes.GetAt(idx)
			If Not att Is Nothing Then
				Dim attGUID
				attGUID = att.AttributeGUID
				attGuids.Add attGUID, att

				Redim Preserve aryGUIDS(idx)
				aryGUIDS(idx) = attGUID
			End If
		Next

		ReDim aryParentGUIDS(-1)
		Dim parenAttNames : parenAttNames = GetInheritedAtts(el, aryParentGUIDS)

		Dim aryCache(3)
		aryCache(0) = aryGUIDS
		aryCache(1) = aryParentGUIDS
		aryCache(2) = parenAttNames
		elAtts.Add elID, aryCache
	End If

End Sub


Function GetCachedAttByGUID(guid)

	Dim ret: Set ret = Nothing

	If attGuids.Exists(guid) = True Then
		Set ret = attGuids(guid)
	End If

	Set GetCachedAttByGUID = ret

End Function


' Performs a lookup on attGUID using the elAtts Cache.
' Returns the attribute corresponding to attGUID if it is directly owned by the element with ID elID or one of its parents - otherwise returns Nothing.
Function GetCachedOwnedAttByGUID(attGUID, elID)

	Dim ret: Set ret = Nothing

	If elAtts.Exists(elID) = True Then
		Dim ary : ary = elAtts(elID)(0)
		If IsInArray(ary, attGUID) = False Then
			ary = elAtts(elID)(1)
			If IsInArray(ary, attGUID) = True Then
				Set ret = GetCachedAttByGUID(attGUID)
			End If
		Else
			Set ret = GetCachedAttByGUID(attGUID)
		End If
	Else
		OutputLine "Internal Error: GetCachedOwnedAttByGUID: Could not locate element with ID"&elID
	End If

	Set GetCachedOwnedAttByGUID = ret

End Function


' Returns zero-based array of values from a select query
Function BuildArrayFromSQL(sql, ColumnName)

    Dim xml, data(), count
    Dim doc, rows, row
	Set doc = CreateObject("MSXML2.DOMDocument")
    xml = Repository.SQLQuery(sql)
	count = 0
	ReDim data(-1)

	If doc.loadXML(xml) Then
        Set rows = doc.selectNodes("//EADATA//Dataset_0//Data//Row")
        For Each row In rows
			Redim Preserve data(count)
			data(count) = row.selectSingleNode(ColumnName).Text
			count = count + 1
        Next
    End If

	BuildArrayFromSQL = data

End Function


' Retrieves the first EA.Element with stereotype ValidationOptions and existing under Package workspacePkg
Function GetValidationOptionsElement(workspacePkgIds, workspacePkg)

	Dim ret : Set ret = Nothing

	Dim sql : sql = "SELECT t_object.Object_ID FROM t_object WHERE t_object.Object_Type='Class' AND t_object.Stereotype='"&ARC_VALIDATION_OPTIONS&"' AND t_object.Package_ID IN (" + workspacePkgIds + ")"

	Dim els : Set els = GetElementSet(sql,2)
	If Not els Is Nothing Then
		If els.Count > 0 Then
			Set ret = els.GetAt(0)
		End If
	End If

	Set GetValidationOptionsElement = ret

End Function

' Retrieves an EA.Collection containing all Spatial References under workspacePkg.
Function GetSpatialReferences(workspacePkgIds, workspacePkg)

	Dim sql : sql = "SELECT t_object.Object_ID FROM t_object WHERE t_object.Object_Type='Class' AND t_object.Stereotype='"&ARC_SPATIAL_REFERENCE&"' AND t_object.Package_ID IN (" + workspacePkgIds + ")"

	Set GetSpatialReferences = GetElementSet(sql,2)

End Function


Function GetTagByName(tags, name)

	Dim i
	Dim tag As EA.TaggedValue
	Set tag = Nothing
	If Not tags Is Nothing Then
		For i = 0 To tags.Count - 1
			If tags.GetAt(i).Name = name Then
				Set tag = tags.GetAt(i)
				Exit For
			End If
		Next
	End If
	Set GetTagByName = tag

End Function


Function GetTagValByName(tags, name)

	Dim ret : ret = ""

	If Not tags Is Nothing Then
		Dim i
		Dim tag As EA.TaggedValue
'		Set tag = tags.GetByName(name)
		Dim count : count = tags.Count - 1
		For i = 0 To count
			Set tag = tags.GetAt(i)
			If tag.Name = name Then
				ret = tag.Value
				If ret = "<memo>" Then
					ret = tags.GetAt(i).Notes
				End If
				Exit For
			End If
		Next
	End If

	GetTagValByName = ret

End Function


Function GetAttByStereotype(atts, stereo)

	Set GetAttByStereotype = Nothing

	If atts Is Nothing Or Len(stereo) = 0 Then
		OutputLine "Internal Error: GetAttByStereotype cannot accept Null parameters."
		Exit Function
	End If

	Dim idx
	Dim att As EA.Attribute
	For idx = 0 To atts.Count - 1
		Set att = atts.GetAt(idx)
		If att.Stereotype = stereo Then
			Set GetAttByStereotype = att
			Exit For
		End If
	Next

End Function


Function GetAttByName(atts, name)

	Set GetAttByName = Nothing

	If atts Is Nothing Or Len(name) = 0 Then
		OutputLine "Internal Error: GetAttByName cannot accept Null parameters."
		Exit Function
	End If

	Dim idx
	Dim att As EA.Attribute
	For idx = 0 To atts.Count - 1
		Set att = atts.GetAt(idx)
		If Not att Is Nothing Then
			If att.Name = name Then
				Set GetAttByName = att
				Exit For
			End If
		End If
	Next

End Function


Function GetAttByGUID(atts, guid)

	Set GetAttByGUID = Nothing

	If atts Is Nothing Or Len(guid) = 0 Then
		OutputLine "Internal Error: GetAttByGUID cannot accept Null parameters."
		Exit Function
	End If

	Dim idx
	Dim att As EA.Attribute
	For idx = 0 To atts.Count - 1
		Set att = atts.GetAt(idx)
		If Not att Is Nothing Then
			If att.AttributeGUID = guid Then
				Set GetAttByGUID = att
				Exit For
			End If
		End If
	Next

End Function


Function GetAttNameByRefGUIDTagName(el, elStereo, elID, tagName)

	Dim attName : attName = ""
	Dim attGuid : attGuid = GetTagValByName(el.TaggedValues, tagName)
	Dim att : Set att = GetCachedOwnedAttByGUID(attGuid, elID)
	
	If Not att Is Nothing Then
			attName = att.Name
	End If
	
	GetAttNameByRefGUIDTagName = attName
	
End Function


Function IsInArray(ary, val)

	Dim ret: ret = False

	Dim idx
	For idx=0 To UBound(ary)
		If ary(idx) = val Then
			ret = True
			Exit For
		End If
	Next

	IsInArray = ret

End Function


'Get a specific name/value pair from within a Style string
Function GetOption(styleString, delimiter, optionName)
	Dim index : index = InStr(styleString, optionName)
	Dim s : s = ""
	
	If index > 0 Then
		Dim delimiterIndex : delimiterIndex = InStr(index, styleString, delimiter)
		s = Mid(styleString, index, delimiterIndex - index)
	End If
	GetOption = s
End Function


'Get the value part from a name/value pair (i.e. anything after the equals sign)
Function GetValue(theOption)
	Dim s : s = ""
	Dim index : index = InStr(theOption, "=")
	s = Mid(theOption, index+1)

	GetValue = s
End Function


Main