@itwin/ecschema-metadata
Version:
ECObjects core concepts in typescript
873 lines • 58 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.XmlParser = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const ECObjects_1 = require("../ECObjects");
const Exception_1 = require("../Exception");
const Enumeration_1 = require("../Metadata/Enumeration");
const ECName_1 = require("../ECName");
const AbstractParser_1 = require("./AbstractParser");
const Helper_1 = require("./Helper");
const NON_ITEM_SCHEMA_ELEMENTS = ["ECSchemaReference", "ECCustomAttributes"];
const ECXML_URI = "http://www\\.bentley\\.com/schemas/Bentley\\.ECXML";
/** @internal */
class XmlParser extends AbstractParser_1.AbstractParser {
_rawSchema;
_schemaName;
_schemaReferenceNames;
_schemaAlias;
_schemaVersion;
_xmlNamespace;
_currentItemFullName;
_schemaItems;
_mapIsPopulated;
constructor(rawSchema) {
super();
this._rawSchema = rawSchema;
const schemaInfo = rawSchema.documentElement;
const schemaName = schemaInfo.getAttribute("schemaName");
if (schemaName)
this._schemaName = schemaName;
this._schemaAlias = "";
const schemaAlias = schemaInfo.getAttribute("alias");
if (schemaAlias)
this._schemaAlias = schemaAlias;
this._schemaReferenceNames = new Map();
const schemaVersion = schemaInfo.getAttribute("version");
if (schemaVersion)
this._schemaVersion = schemaVersion;
const xmlNamespace = schemaInfo.getAttribute("xmlns");
if (xmlNamespace) {
this._xmlNamespace = xmlNamespace;
this._ecSpecVersion = XmlParser.parseXmlNamespace(this._xmlNamespace);
}
this._schemaItems = new Map();
this._mapIsPopulated = false;
}
get getECSpecVersion() { return this._ecSpecVersion; }
parseSchema() {
const schemaMetadata = this._rawSchema.documentElement;
if ("ECSchema" !== schemaMetadata.nodeName)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, "An ECSchema is missing the required metadata.");
const schemaDefDuplicates = this.getElementChildrenByTagName(schemaMetadata, "ECSchema");
if (schemaDefDuplicates.length > 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, "An ECSchema has more than one ECSchema definition. Only one is allowed.");
if (this._schemaName === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `An ECSchema is missing a required 'schemaName' attribute`);
if (this._schemaVersion === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ECSchema ${this._schemaName} is missing a required 'version' attribute`);
if (this._xmlNamespace === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ECSchema ${this._schemaName} is missing a required 'xmlns' attribute`);
if (this._ecSpecVersion === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ECSchema ${this._schemaName} has an invalid 'xmlns' attribute`);
const alias = this.getRequiredAttribute(schemaMetadata, "alias", `The ECSchema ${this._schemaName} is missing a required 'alias' attribute`);
const description = this.getOptionalAttribute(schemaMetadata, "description");
const displayLabel = this.getOptionalAttribute(schemaMetadata, "displayLabel");
const schemaProps = {
name: this._schemaName,
$schema: this._xmlNamespace,
version: this._schemaVersion,
alias,
label: displayLabel,
description,
ecSpecMajorVersion: this._ecSpecVersion.readVersion,
ecSpecMinorVersion: this._ecSpecVersion.writeVersion,
};
return schemaProps;
}
*getReferences() {
const schemaReferences = this.getElementChildrenByTagName(this._rawSchema.documentElement, "ECSchemaReference");
for (const ref of schemaReferences) {
yield this.getSchemaReference(ref);
}
}
*getItems() {
if (!this._mapIsPopulated) {
const schemaItems = this.getSchemaChildren();
for (const item of schemaItems) {
let rawItemType = item.nodeName;
if (NON_ITEM_SCHEMA_ELEMENTS.includes(rawItemType))
continue;
// Differentiate a Mixin from an EntityClass
const customAttributesResult = this.getElementChildrenByTagName(item, "ECCustomAttributes");
if (customAttributesResult.length > 0) {
const customAttributes = customAttributesResult[0];
const isMixinResult = this.getElementChildrenByTagName(customAttributes, "IsMixin");
if (isMixinResult.length > 0)
rawItemType = "Mixin";
}
const itemType = this.getSchemaItemType(rawItemType);
if (itemType === undefined) {
if (Helper_1.SchemaReadHelper.isECSpecVersionNewer(this._ecSpecVersion))
continue;
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `A SchemaItem in ${this._schemaName} has an invalid type. '${rawItemType}' is not a valid SchemaItem type.`);
}
const itemName = this.getRequiredAttribute(item, "typeName", `A SchemaItem in ${this._schemaName} is missing the required 'typeName' attribute.`);
if (!ECName_1.ECName.validate(itemName))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECName, `A SchemaItem in ${this._schemaName} has an invalid 'typeName' attribute. '${itemName}' is not a valid ECName.`);
this._currentItemFullName = `${this._schemaName}.${itemName}`;
this._schemaItems.set(itemName, [itemType, item]);
yield [itemName, itemType, item];
}
this._mapIsPopulated = true;
}
else {
for (const [itemName, [itemType, item]] of this._schemaItems) {
this._currentItemFullName = `${this._schemaName}.${itemName}`;
yield [itemName, itemType, item];
}
}
}
findItem(itemName) {
if (!this._mapIsPopulated) {
for (const item of this.getItems()) {
if (item[0] === itemName) {
this._currentItemFullName = `${this._schemaName}.${itemName}`;
return item;
}
}
}
else {
const values = this._schemaItems.get(itemName);
if (undefined !== values) {
const [itemType, item] = values;
this._currentItemFullName = `${this._schemaName}.${itemName}`;
return [itemName, itemType, item];
}
}
return undefined;
}
parseEntityClass(xmlElement) {
const classProps = this.getClassProps(xmlElement);
const baseClasses = this.getElementChildrenByTagName(xmlElement, "BaseClass");
let mixinElements;
const mixins = new Array();
// if it has just one BaseClass we assume it is a 'true' base class not a mixin
if (baseClasses.length > 1) {
mixinElements = baseClasses.slice(1);
for (const mixin of mixinElements) {
if (mixin.textContent) {
const typeName = this.getQualifiedTypeName(mixin.textContent);
mixins.push(typeName);
}
}
}
const entityClassProps = {
...classProps,
mixins,
};
return entityClassProps;
}
parseMixin(xmlElement) {
const classProps = this.getClassProps(xmlElement);
const baseClasses = this.getElementChildrenByTagName(xmlElement, "BaseClass");
// Mixins can only have one base class
if (baseClasses.length > 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Mixin ${this._currentItemFullName} has more than one base class which is not allowed.`);
const customAttributesResult = this.getElementChildrenByTagName(xmlElement, "ECCustomAttributes");
if (customAttributesResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Mixin ${this._currentItemFullName} is missing the required 'IsMixin' tag.`);
const customAttributes = customAttributesResult[0];
const isMixinResult = this.getElementChildrenByTagName(customAttributes, "IsMixin");
if (isMixinResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Mixin ${this._currentItemFullName} is missing the required 'IsMixin' tag.`);
const mixinAttributes = isMixinResult[0];
const appliesToResult = this.getElementChildrenByTagName(mixinAttributes, "AppliesToEntityClass");
if (appliesToResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Mixin ${this._currentItemFullName} is missing the required 'AppliesToEntityClass' tag.`);
const appliesToElement = appliesToResult[0];
let appliesTo = appliesToElement.textContent;
if (appliesTo === null || appliesTo.length === 0)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Mixin ${this._currentItemFullName} is missing the required 'AppliesToEntityClass' tag.`);
appliesTo = this.getQualifiedTypeName(appliesTo);
const mixinProps = {
...classProps,
appliesTo,
};
return mixinProps;
}
parseStructClass(xmlElement) {
return this.getClassProps(xmlElement);
}
parseCustomAttributeClass(xmlElement) {
const classProps = this.getClassProps(xmlElement);
const appliesTo = this.getRequiredAttribute(xmlElement, "appliesTo", `The CustomAttributeClass ${this._currentItemFullName} is missing the required 'appliesTo' attribute.`);
const customAttributeClassProps = {
...classProps,
appliesTo,
};
return customAttributeClassProps;
}
parseRelationshipClass(xmlElement) {
const classProps = this.getClassProps(xmlElement);
const strength = this.getRequiredAttribute(xmlElement, "strength", `The RelationshipClass ${this._currentItemFullName} is missing the required 'strength' attribute.`);
let strengthDirection = this.getOptionalAttribute(xmlElement, "strengthDirection");
if (!strengthDirection)
strengthDirection = (0, ECObjects_1.strengthDirectionToString)(ECObjects_1.StrengthDirection.Forward);
const sourceResult = this.getElementChildrenByTagName(xmlElement, "Source");
if (sourceResult.length !== 1) {
if (sourceResult.length === 0)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The RelationshipClass ${this._currentItemFullName} is missing the required Source constraint tag.`);
else
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The RelationshipClass ${this._currentItemFullName} has more than one Source constraint tag. Only one is allowed.`);
}
const source = this.getRelationshipConstraintProps(sourceResult[0], true);
const targetResult = this.getElementChildrenByTagName(xmlElement, "Target");
if (targetResult.length !== 1) {
if (targetResult.length === 0)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The RelationshipClass ${this._currentItemFullName} is missing the required Target constraint tag.`);
else
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The RelationshipClass ${this._currentItemFullName} has more than one Target constraint tag. Only one is allowed.`);
}
const target = this.getRelationshipConstraintProps(targetResult[0], false);
return {
...classProps,
strength,
strengthDirection,
source,
target,
};
}
parseEnumeration(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
const enumType = this.getRequiredAttribute(xmlElement, "backingTypeName", `The Enumeration ${this._currentItemFullName} is missing the required 'backingTypeName' attribute.`);
// TODO: This shouldn't be verified here. It's for the deserialize method to handle. The only reason it's currently done here so that the xml
// value can be put in the correct type, number or string.
let tempBackingType;
if (/int/i.test(enumType)) {
tempBackingType = ECObjects_1.PrimitiveType.Integer;
}
else if (/string/i.test(enumType)) {
tempBackingType = ECObjects_1.PrimitiveType.String;
}
else {
if (Helper_1.SchemaReadHelper.isECSpecVersionNewer(this._ecSpecVersion))
tempBackingType = ECObjects_1.PrimitiveType.String;
else
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an invalid 'backingTypeName' attribute. It should be either "int" or "string".`);
}
let isStrictString = this.getOptionalAttribute(xmlElement, "isStrict");
if (isStrictString === undefined)
isStrictString = "true";
const isStrict = this.parseBoolean(isStrictString, `The Enumeration ${this._currentItemFullName} has an invalid 'isStrict' attribute. It should either be "true" or "false".`);
const enumeratorElements = this.getElementChildrenByTagName(xmlElement, "ECEnumerator");
const enumerators = new Array();
for (const element of enumeratorElements) {
const name = this.getRequiredAttribute(element, "name", `The Enumeration ${this._currentItemFullName} has an enumerator that is missing the required attribute 'name'.`);
const valueString = this.getRequiredAttribute(element, "value", `The Enumeration ${this._currentItemFullName} has an enumerator that is missing the required attribute 'value'.`);
let value = valueString;
if (ECObjects_1.PrimitiveType.Integer === tempBackingType) {
const numericValue = parseInt(valueString, 10);
if (isNaN(numericValue))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Enumeration ${this._currentItemFullName} of type "int" has an enumerator with a non-integer value.`);
value = numericValue;
}
const label = this.getOptionalAttribute(element, "displayLabel");
const description = this.getOptionalAttribute(element, "description");
enumerators.push({
name,
value,
label,
description,
});
}
return {
...itemProps,
type: enumType,
isStrict,
enumerators,
originalECSpecMajorVersion: this._ecSpecVersion?.readVersion,
originalECSpecMinorVersion: this._ecSpecVersion?.writeVersion,
};
}
parseKindOfQuantity(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
const relativeErrorString = this.getRequiredAttribute(xmlElement, "relativeError", `The KindOfQuantity ${this._currentItemFullName} is missing the required 'relativeError' attribute.`);
const relativeError = parseFloat(relativeErrorString);
if (isNaN(relativeError))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The KindOfQuantity ${this._currentItemFullName} has an invalid 'relativeError' attribute. It should be a numeric value.`);
const presentationUnitsString = this.getOptionalAttribute(xmlElement, "presentationUnits");
let presentationUnits;
if (presentationUnitsString)
presentationUnits = this.getQualifiedPresentationUnits(presentationUnitsString.split(";"));
let persistenceUnit = this.getRequiredAttribute(xmlElement, "persistenceUnit", `The KindOfQuantity ${this._currentItemFullName} is missing the required 'persistenceUnit' attribute.`);
persistenceUnit = this.getQualifiedTypeName(persistenceUnit);
return {
...itemProps,
relativeError,
presentationUnits,
persistenceUnit,
};
}
parsePropertyCategory(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
const priorityString = this.getRequiredAttribute(xmlElement, "priority", `The PropertyCategory ${this._currentItemFullName} is missing the required 'priority' attribute.`);
const priority = parseInt(priorityString, 10);
if (isNaN(priority))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The PropertyCategory ${this._currentItemFullName} has an invalid 'priority' attribute. It should be a numeric value.`);
return {
...itemProps,
priority,
};
}
parseUnit(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
let phenomenon = this.getRequiredAttribute(xmlElement, "phenomenon", `The Unit ${this._currentItemFullName} is missing the required 'phenomenon' attribute.`);
let unitSystem = this.getRequiredAttribute(xmlElement, "unitSystem", `The Unit ${this._currentItemFullName} is missing the required 'unitSystem' attribute.`);
const definition = this.getRequiredAttribute(xmlElement, "definition", `The Unit ${this._currentItemFullName} is missing the required 'definition' attribute.`);
const numerator = this.getOptionalFloatAttribute(xmlElement, "numerator", `The Unit ${this._currentItemFullName} has an invalid 'numerator' attribute. It should be a numeric value.`);
const denominator = this.getOptionalFloatAttribute(xmlElement, "denominator", `The Unit ${this._currentItemFullName} has an invalid 'denominator' attribute. It should be a numeric value.`);
const offset = this.getOptionalFloatAttribute(xmlElement, "offset", `The Unit ${this._currentItemFullName} has an invalid 'offset' attribute. It should be a numeric value.`);
phenomenon = this.getQualifiedTypeName(phenomenon);
unitSystem = this.getQualifiedTypeName(unitSystem);
return {
...itemProps,
phenomenon,
unitSystem,
definition,
numerator,
denominator,
offset,
};
}
parseInvertedUnit(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
let invertsUnit = this.getRequiredAttribute(xmlElement, "invertsUnit", `The InvertedUnit ${this._currentItemFullName} is missing the required 'invertsUnit' attribute.`);
let unitSystem = this.getRequiredAttribute(xmlElement, "unitSystem", `The InvertedUnit ${this._currentItemFullName} is missing the required 'unitSystem' attribute.`);
invertsUnit = this.getQualifiedTypeName(invertsUnit);
unitSystem = this.getQualifiedTypeName(unitSystem);
return {
...itemProps,
invertsUnit,
unitSystem,
};
}
parseConstant(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
let phenomenon = this.getRequiredAttribute(xmlElement, "phenomenon", `The Constant ${this._currentItemFullName} is missing the required 'phenomenon' attribute.`);
const definition = this.getRequiredAttribute(xmlElement, "definition", `The Constant ${this._currentItemFullName} is missing the required 'definition' attribute.`);
const numerator = this.getOptionalFloatAttribute(xmlElement, "numerator", `The Constant ${this._currentItemFullName} has an invalid 'numerator' attribute. It should be a numeric value.`);
const denominator = this.getOptionalFloatAttribute(xmlElement, "denominator", `The Constant ${this._currentItemFullName} has an invalid 'denominator' attribute. It should be a numeric value.`);
phenomenon = this.getQualifiedTypeName(phenomenon);
return {
...itemProps,
phenomenon,
definition,
numerator,
denominator,
};
}
parsePhenomenon(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
const definition = this.getRequiredAttribute(xmlElement, "definition", `The Phenomenon ${this._currentItemFullName} is missing the required 'definition' attribute.`);
return {
...itemProps,
definition,
};
}
parseFormat(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
const formatType = this.getRequiredAttribute(xmlElement, "type", `The Format ${this._currentItemFullName} is missing the required 'type' attribute.`);
const precision = this.getOptionalIntAttribute(xmlElement, "precision", `The Format ${this._currentItemFullName} has an invalid 'precision' attribute. It should be a numeric value.`);
const roundFactor = this.getOptionalFloatAttribute(xmlElement, "roundFactor", `The Format ${this._currentItemFullName} has an invalid 'roundFactor' attribute. It should be a numeric value.`);
const minWidth = this.getOptionalIntAttribute(xmlElement, "minWidth", `The Format ${this._currentItemFullName} has an invalid 'minWidth' attribute. It should be a numeric value.`);
const showSignOption = this.getOptionalAttribute(xmlElement, "showSignOption");
const formatTraitsString = this.getRequiredAttribute(xmlElement, "formatTraits", `The Format ${this._currentItemFullName} is missing the required 'formatTraits' attribute.`);
const formatTraits = formatTraitsString.split("|");
const decimalSeparator = this.getOptionalAttribute(xmlElement, "decimalSeparator");
const thousandSeparator = this.getOptionalAttribute(xmlElement, "thousandSeparator");
const uomSeparator = this.getOptionalAttribute(xmlElement, "uomSeparator");
const scientificType = this.getOptionalAttribute(xmlElement, "scientificType");
const stationOffsetSize = this.getOptionalIntAttribute(xmlElement, "stationOffsetSize", `The Format ${this._currentItemFullName} has an invalid 'stationOffsetSize' attribute. It should be a numeric value.`);
const stationSeparator = this.getOptionalAttribute(xmlElement, "stationSeparator");
let composite;
const compositeResult = this.getElementChildrenByTagName(xmlElement, "Composite");
if (compositeResult.length > 0) {
const compositeElement = compositeResult[0];
const spacer = this.getOptionalAttribute(compositeElement, "spacer");
const includeZeroString = this.getOptionalAttribute(compositeElement, "includeZero");
let includeZero;
if (includeZeroString) {
includeZero = this.parseBoolean(includeZeroString, `The Format ${this._currentItemFullName} has a Composite with an invalid 'includeZero' attribute. It should be either "true" or "false".`);
}
const units = new Array();
const unitsResult = this.getElementChildrenByTagName(compositeElement, "Unit");
if (unitsResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Format ${this._currentItemFullName} has an invalid 'Composite' element. It should have 1-4 Unit elements.`);
for (const unit of unitsResult) {
let name = unit.textContent;
if (null === name || 0 === name.length)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The Format ${this._currentItemFullName} has a Composite with an invalid Unit. One of the Units is missing the required 'name' attribute.`);
const label = this.getOptionalAttribute(unit, "label");
name = this.getQualifiedTypeName(name);
units.push({ name, label });
}
composite = {
spacer,
includeZero,
units,
};
}
return {
...itemProps,
type: formatType,
precision,
roundFactor,
minWidth,
showSignOption,
formatTraits,
decimalSeparator,
thousandSeparator,
uomSeparator,
scientificType,
stationOffsetSize,
stationSeparator,
composite,
};
}
parseUnitSystem(xmlElement) {
return this.getClassProps(xmlElement);
}
*getProperties(xmlElement, itemName) {
const propertyTagRegex = /EC((Struct(Array)?)|Array|Navigation)?Property/;
const children = this.getElementChildrenByTagName(xmlElement, propertyTagRegex);
for (const child of children) {
const childType = child.nodeName;
const propertyName = this.getRequiredAttribute(child, "propertyName", `An ECProperty in ${itemName} is missing the required 'propertyName' attribute.`);
const propertyType = this.getPropertyType(childType);
// This may not be needed, just a failsafe if the regex is faulty
if (propertyType === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ECProperty ${itemName}.${propertyName} has an invalid type. ${childType} is not a valid ECProperty type.`);
yield [propertyName, propertyType, child];
}
}
parsePrimitiveProperty(xmlElement) {
const typeName = this.getPropertyTypeName(xmlElement);
const propertyProps = this.getPrimitiveOrEnumPropertyBaseProps(xmlElement, typeName);
const primitivePropertyProps = { ...propertyProps, typeName };
return primitivePropertyProps;
}
parseStructProperty(xmlElement) {
const propertyProps = this.getPropertyProps(xmlElement);
const typeName = this.getPropertyTypeName(xmlElement);
const structPropertyProps = { ...propertyProps, typeName };
return structPropertyProps;
}
parsePrimitiveArrayProperty(xmlElement) {
const typeName = this.getPropertyTypeName(xmlElement);
const propertyProps = this.getPrimitiveOrEnumPropertyBaseProps(xmlElement, typeName);
const minAndMaxOccurs = this.getPropertyMinAndMaxOccurs(xmlElement);
return {
...propertyProps,
...minAndMaxOccurs,
typeName,
};
}
parseStructArrayProperty(xmlElement) {
const propertyProps = this.getPropertyProps(xmlElement);
const typeName = this.getPropertyTypeName(xmlElement);
const minAndMaxOccurs = this.getPropertyMinAndMaxOccurs(xmlElement);
return {
...propertyProps,
...minAndMaxOccurs,
typeName,
};
}
parseNavigationProperty(xmlElement) {
const propName = this.getPropertyName(xmlElement);
const propertyProps = this.getPropertyProps(xmlElement);
let relationshipName = this.getRequiredAttribute(xmlElement, "relationshipName", `The ECNavigationProperty ${this._currentItemFullName}.${propName} is missing the required 'relationshipName' property.`);
const direction = this.getRequiredAttribute(xmlElement, "direction", `The ECNavigationProperty ${this._currentItemFullName}.${propName} is missing the required 'direction' property.`);
relationshipName = this.getQualifiedTypeName(relationshipName);
return {
...propertyProps,
relationshipName,
direction,
};
}
getSchemaCustomAttributeProviders() {
return this.getCustomAttributeProviders(this._rawSchema.documentElement, "Schema", this._schemaName);
}
getClassCustomAttributeProviders(xmlElement) {
return this.getCustomAttributeProviders(xmlElement, "ECClass", this._currentItemFullName);
}
getPropertyCustomAttributeProviders(xmlElement) {
const propName = this.getPropertyName(xmlElement);
return this.getCustomAttributeProviders(xmlElement, "ECProperty", `${this._currentItemFullName}.${propName}`);
}
getRelationshipConstraintCustomAttributeProviders(xmlElement) {
const sourceResult = this.getElementChildrenByTagName(xmlElement, "Source");
if (sourceResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The RelationshipClass ${this._currentItemFullName} is missing the required Source constraint tag.`);
const sourceElement = sourceResult[0];
const sourceCustomAttributes = this.getCustomAttributeProviders(sourceElement, "Source Constraint of", this._currentItemFullName);
const targetResult = this.getElementChildrenByTagName(xmlElement, "Target");
if (targetResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The RelationshipClass ${this._currentItemFullName} is missing the required Target constraint tag.`);
const targetElement = targetResult[0];
const targetCustomAttributes = this.getCustomAttributeProviders(targetElement, "Source Constraint of", this._currentItemFullName);
return [sourceCustomAttributes, targetCustomAttributes];
}
getElementChildren(xmlElement) {
// NodeListOf<T> does not define [Symbol.iterator]
const children = Array.from(xmlElement.childNodes).filter((child) => {
// (node.nodeType === 1) implies instanceof Element
// https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children#Polyfill
return child.nodeType === 1;
});
return children;
}
getElementChildrenByTagName(xmlElement, tagName) {
const children = this.getElementChildren(xmlElement);
if ("*" === tagName)
return children;
let result;
if (typeof tagName === "string") {
result = children.filter((child) => {
return tagName.toLowerCase() === child.nodeName.toLowerCase();
});
}
else {
result = children.filter((child) => {
return tagName.test(child.nodeName);
});
}
return result;
}
getOptionalAttribute(xmlElement, attributeName) {
if (!xmlElement.hasAttribute(attributeName))
return undefined;
const result = xmlElement.getAttribute(attributeName);
// The typings for the return value of getAttribute do not match that of xmldom
// xmldom returns an empty string instead of null
// However Typescript will still treat result as a union type without this check
// Hence this is needed for tsc to compile
if (result === null)
return undefined;
return result;
}
getOptionalFloatAttribute(xmlElement, attributeName, parseErrorMsg) {
const resultString = this.getOptionalAttribute(xmlElement, attributeName);
let result;
if (resultString) {
result = parseFloat(resultString);
if (isNaN(result))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, parseErrorMsg);
}
return result;
}
getOptionalIntAttribute(xmlElement, attributeName, parseErrorMsg) {
const resultString = this.getOptionalAttribute(xmlElement, attributeName);
let result;
if (resultString) {
result = parseInt(resultString, 10);
if (isNaN(result))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, parseErrorMsg);
}
return result;
}
parseBoolean(text, parseErrorMsg) {
const textString = text.toLowerCase();
if ("true" === textString)
return true;
else if ("false" === textString)
return false;
else
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, parseErrorMsg);
}
getRequiredAttribute(xmlElement, attributeName, errorMsg) {
if (!xmlElement.hasAttribute(attributeName))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, errorMsg);
const result = xmlElement.getAttribute(attributeName);
if (result === null)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, errorMsg);
return result;
}
getSchemaReference(xmlElement) {
const alias = this.getRequiredAttribute(xmlElement, "alias", `The schema ${this._schemaName} has an invalid ECSchemaReference attribute. One of the references is missing the required 'alias' attribute.`);
const name = this.getRequiredAttribute(xmlElement, "name", `The schema ${this._schemaName} has an invalid ECSchemaReference attribute. One of the references is missing the required 'name' attribute.`);
const version = this.getRequiredAttribute(xmlElement, "version", `The schema ${this._schemaName} has an invalid ECSchemaReference attribute. One of the references is missing the required 'version' attribute.`);
if (!this._schemaReferenceNames.has(alias.toLowerCase()))
this._schemaReferenceNames.set(alias.toLowerCase(), name);
return {
name,
version,
};
}
getSchemaItemType(rawType) {
switch (rawType.toLowerCase()) {
case "ecentityclass": return "EntityClass";
case "mixin": return "Mixin";
case "ecstructclass": return "StructClass";
case "eccustomattributeclass": return "CustomAttributeClass";
case "ecrelationshipclass": return "RelationshipClass";
case "ecenumeration": return "Enumeration";
case "kindofquantity": return "KindOfQuantity";
case "propertycategory": return "PropertyCategory";
case "unit": return "Unit";
case "invertedunit": return "InvertedUnit";
case "constant": return "Constant";
case "phenomenon": return "Phenomenon";
case "unitsystem": return "UnitSystem";
case "format": return "Format";
}
return undefined;
}
getSchemaChildren() {
const schemaMetadata = this._rawSchema.documentElement;
return this.getElementChildren(schemaMetadata);
}
getSchemaItemProps(xmlElement) {
const displayLabel = this.getOptionalAttribute(xmlElement, "displayLabel");
const description = this.getOptionalAttribute(xmlElement, "description");
return {
description,
label: displayLabel,
};
}
getClassProps(xmlElement) {
const itemProps = this.getSchemaItemProps(xmlElement);
const modifier = this.getOptionalAttribute(xmlElement, "modifier");
let baseClass = null;
const baseClasses = this.getElementChildrenByTagName(xmlElement, "BaseClass");
if (baseClasses.length > 0) {
// We are assuming here that the first BaseClass is the 'real' one - the rest are mixins
// This is not a finalized approach as this could lead to unsupported schemas
baseClass = baseClasses[0].textContent;
}
baseClass = baseClass ? this.getQualifiedTypeName(baseClass) : undefined;
return {
...itemProps,
modifier,
baseClass,
originalECSpecMajorVersion: this._ecSpecVersion?.readVersion,
originalECSpecMinorVersion: this._ecSpecVersion?.writeVersion,
};
}
getRelationshipConstraintProps(xmlElement, isSource) {
const constraintName = `${(isSource) ? "Source" : "Target"} Constraint of ${this._currentItemFullName}`;
const multiplicity = this.getRequiredAttribute(xmlElement, "multiplicity", `The ${constraintName} is missing the required 'multiplicity' attribute.`);
const roleLabel = this.getRequiredAttribute(xmlElement, "roleLabel", `The ${constraintName} is missing the required 'roleLabel' attribute.`);
const polymorphicString = this.getRequiredAttribute(xmlElement, "polymorphic", `The ${constraintName} is missing the required 'polymorphic' attribute.`);
const polymorphic = this.parseBoolean(polymorphicString, `The ${constraintName} has an invalid 'polymorphic' attribute. It should either be "true" or "false".`);
let abstractConstraint = this.getOptionalAttribute(xmlElement, "abstractConstraint");
if (undefined !== abstractConstraint)
abstractConstraint = this.getQualifiedTypeName(abstractConstraint);
const constraintClasses = new Array();
const constraintClassesResult = this.getElementChildrenByTagName(xmlElement, "Class");
if (constraintClassesResult.length < 1)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ${constraintName} is missing the required Class tags.`);
for (const constraintClass of constraintClassesResult) {
let constraintClassId = constraintClass.getAttribute("class");
if (null === constraintClassId || 0 === constraintClassId.length)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ${constraintName} has a Class that is missing the required 'class' attribute.`);
constraintClassId = this.getQualifiedTypeName(constraintClassId);
constraintClasses.push(constraintClassId);
}
return {
multiplicity,
roleLabel,
polymorphic,
abstractConstraint,
constraintClasses,
};
}
getPropertyType(propType) {
switch (propType) {
case "ECNavigationProperty": return "navigationproperty";
case "ECStructProperty": return "structproperty";
case "ECArrayProperty": return "primitivearrayproperty";
case "ECStructArrayProperty": return "structarrayproperty";
case "ECProperty": return "primitiveproperty";
default: return undefined;
}
}
getPropertyName(xmlElement) {
return this.getRequiredAttribute(xmlElement, "propertyName", `An ECProperty in ${this._currentItemFullName} is missing the required 'propertyName' attribute.`);
}
getPropertyProps(xmlElement) {
const propName = this.getPropertyName(xmlElement);
const propType = this.getPropertyType(xmlElement.nodeName);
if (propType === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid type. ${propType} is not a valid ECProperty type.`);
const label = this.getOptionalAttribute(xmlElement, "displayLabel");
const description = this.getOptionalAttribute(xmlElement, "description");
const readOnlyString = this.getOptionalAttribute(xmlElement, "readOnly");
let isReadOnly;
if (readOnlyString) {
isReadOnly = this.parseBoolean(readOnlyString, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'readOnly' attribute. It should be either "true" or "false".`);
}
let category = this.getOptionalAttribute(xmlElement, "category");
const priority = this.getOptionalIntAttribute(xmlElement, "priority", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'priority' attribute. It should be a numeric value.`);
const inheritedString = this.getOptionalAttribute(xmlElement, "inherited");
let inherited;
if (inheritedString) {
inherited = this.parseBoolean(inheritedString, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'inherited' attribute. It should be either "true" or "false".`);
}
let kindOfQuantity = this.getOptionalAttribute(xmlElement, "kindOfQuantity");
if (kindOfQuantity)
kindOfQuantity = this.getQualifiedTypeName(kindOfQuantity);
if (category)
category = this.getQualifiedTypeName(category);
return {
name: propName,
type: propType,
description,
label,
isReadOnly,
category,
priority,
inherited,
kindOfQuantity,
};
}
getPropertyTypeName(xmlElement) {
const propName = this.getPropertyName(xmlElement);
const rawTypeName = this.getRequiredAttribute(xmlElement, "typeName", `The ECProperty ${this._currentItemFullName}.${propName} is missing the required 'typeName' attribute.`);
// If not a primitive type, we must prepend the schema name.
const primitiveType = (0, ECObjects_1.parsePrimitiveType)(rawTypeName);
if (primitiveType)
return rawTypeName;
return this.getQualifiedTypeName(rawTypeName);
}
getPrimitiveOrEnumPropertyBaseProps(xmlElement, typeName) {
const primitiveType = (0, ECObjects_1.parsePrimitiveType)(typeName);
const propertyProps = this.getPropertyProps(xmlElement);
const propName = propertyProps.name;
const extendedTypeName = this.getOptionalAttribute(xmlElement, "extendedTypeName");
const minLength = this.getOptionalIntAttribute(xmlElement, "minimumLength", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minimumLength' attribute. It should be a numeric value.`);
const maxLength = this.getOptionalIntAttribute(xmlElement, "maximumLength", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maximumLength' attribute. It should be a numeric value.`);
let minValue;
let maxValue;
if (primitiveType === ECObjects_1.PrimitiveType.Double || primitiveType === ECObjects_1.PrimitiveType.Long) {
minValue = this.getOptionalFloatAttribute(xmlElement, "minimumValue", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minimumValue' attribute. It should be a numeric value.`);
maxValue = this.getOptionalFloatAttribute(xmlElement, "maximumValue", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maximumValue' attribute. It should be a numeric value.`);
}
else {
minValue = this.getOptionalIntAttribute(xmlElement, "minimumValue", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minimumValue' attribute. It should be a numeric value.`);
maxValue = this.getOptionalIntAttribute(xmlElement, "maximumValue", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maximumValue' attribute. It should be a numeric value.`);
}
return {
...propertyProps,
extendedTypeName,
minLength,
maxLength,
minValue,
maxValue,
};
}
getPropertyMinAndMaxOccurs(xmlElement) {
const propName = this.getPropertyName(xmlElement);
const minOccurs = this.getOptionalIntAttribute(xmlElement, "minOccurs", `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minOccurs' attribute. It should be a numeric value.`);
const maxOccursStr = this.getOptionalAttribute(xmlElement, "maxOccurs");
let maxOccurs;
if ("unbounded" === maxOccursStr)
maxOccurs = 2147483647; // TODO: This should be using the INT32_MAX variable.
else if (undefined !== maxOccursStr) {
maxOccurs = parseInt(maxOccursStr, 10);
if (isNaN(maxOccurs))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maxOccurs' attribute. It should be a numeric value.`);
}
return { minOccurs, maxOccurs };
}
*getCustomAttributeProviders(xmlElement, type, _name) {
const customAttributesResult = this.getElementChildrenByTagName(xmlElement, "ECCustomAttributes");
if (customAttributesResult.length < 1)
return;
const attributes = this.getElementChildren(customAttributesResult[0]);
for (const attribute of attributes) {
if ("ECClass" === type && "IsMixin" === attribute.tagName)
continue;
yield this.getCustomAttributeProvider(attribute);
}
}
getCustomAttributeProvider(xmlCustomAttribute) {
(0, core_bentley_1.assert)(this._ecSpecVersion !== undefined);
let ns = xmlCustomAttribute.getAttribute("xmlns");
if (!ns) {
(0, core_bentley_1.assert)(this._schemaName !== undefined);
(0, core_bentley_1.assert)(this._schemaVersion !== undefined);
ns = `${this._schemaName}.${this._schemaVersion}`;
}
if (null === ns || !this.isSchemaFullNameValidForVersion(ns, this._ecSpecVersion))
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.InvalidSchemaXML, `Custom attribute namespaces must contain a valid 3.2 full schema name in the form <schemaName>.RR.ww.mm.`);
const schemaNameParts = ns.split(".");
const className = `${schemaNameParts[0]}.${xmlCustomAttribute.tagName}`;
const properties = this.getElementChildren(xmlCustomAttribute);
const provider = (caClass) => {
return this.addCAPropertyValues(caClass, properties);
};
return [className, provider];
}
addCAPropertyValues(caClass, propertyElements) {
const instance = { className: caClass.fullName };
for (const propertyElement of propertyElements) {
const value = this.readPropertyValue(propertyElement, caClass);
if (value !== undefined)
instance[propertyElement.tagName] = value;
}
return instance;
}
readPropertyValue(propElement, parentClass) {
const propertyClass = parentClass.getPropertySync(propElement.tagName);
if (!propertyClass)
return;
if (propertyClass.isArray())
return this.readArrayPropertyValue(propElement, propertyClass);
let enumeration;
if (propertyClass.isPrimitive()) {
if (propertyClass.isEnumeration() && propertyClass.enumeration) {
enumeration = propertyClass.schema.lookupItemSync(propertyClass.enumeration.fullName, Enumeration_1.Enumeration);
if (!enumeration)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.ClassNotFound, `The Enumeration class '${propertyClass.enumeration.fullName}' could not be found.`);
}
const primitiveType = enumeration && enumeration.type ? enumeration.type : (propertyClass).primitiveType;
return this.readPrimitivePropertyValue(propElement, primitiveType);
}
if (propertyClass.isStruct())
return this.readStructPropertyValue(propElement, propertyClass.structClass);
return undefined;
}
readArrayPropertyValue(propElement, propertyClass) {
if (propertyClass.isPrimitive())
return this.readPrimitiveArrayValues(propElement, propertyClass.primitiveType);
if (propertyClass.isStruct())
return this.readStructArrayValues(propElement, propertyClass);
return undefined;
}
readPrimitiveArrayValues(propElement, primitiveType) {
const typeName = (0, ECObjects_1.primitiveTypeToString)(primitiveType);
const children = this.getElementChildrenByTagName(propElement, typeName);
const values = [];
for (const child of children) {
const value = this.readPrimitivePropertyValue(child, primitiveType);
values.push(value);
}
return values;
}
readStructArrayValues(propElement, propertyClass) {
const children = this.getElementChildren(propElement);
const values = [];
for (const child of children) {
const value = this.readStructPropertyValue(child, propertyClass.structClass);
values.push(value);
}
return values;
}
readStructPropertyValue(propElement, structClass) {
const structObj = {};
const children = this.getElementChildren(propElement);
for (const child of children) {
const value = this.readPropertyValue(child, structClass);
if (value !== undefined)
structObj[child.tagName] = value;
}
return structObj;
}
readPrimitivePropertyValue(propElement, primitiveType) {
if (undefined === propElement.textContent || null === propElement.textContent)
throw new