Skip to content

Commit

Permalink
Adding new schema xml string locater
Browse files Browse the repository at this point in the history
  • Loading branch information
RohitPtnkr1996 committed Nov 14, 2024
1 parent 6976e06 commit 163b457
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 43 deletions.
7 changes: 7 additions & 0 deletions common/api/ecschema-locaters.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export class BackendSchemasXmlFileLocater extends SchemaXmlFileLocater implement
addSchemaSearchPaths(schemaPaths: string[]): void;
}

// @beta
export class BackendSchemaXmlStringLocater extends SchemaXmlStringLocater implements ISchemaLocater {
constructor(assetsDir: string);
addSchemaString(schemaString: string): void;
addSchemaStrings(schemaStrings: string[]): void;
}

// @beta
export class FileSchemaKey extends SchemaKey {
constructor(key: SchemaKey, fileName: string, schemaJson?: string);
Expand Down
1 change: 1 addition & 0 deletions common/api/summary/ecschema-locaters.exports.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
sep=;
Release Tag;API Item Type;API Item Name
beta;class;BackendSchemasXmlFileLocater
beta;class;BackendSchemaXmlStringLocater
beta;class;FileSchemaKey
beta;class;SchemaFileLocater
beta;class;SchemaJsonFileLocater
Expand Down
1 change: 1 addition & 0 deletions core/ecschema-locaters/src/SchemaStringLocater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class StringSchemaKey extends SchemaKey {
*/
export abstract class SchemaStringLocater {
public schemaStrings: string[];
protected searchPathPrecedence = new Map<string, number>();

constructor() {
this.schemaStrings = [];
Expand Down
96 changes: 92 additions & 4 deletions core/ecschema-locaters/src/SchemaXmlStringLocater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* @module Locaters
*/

import * as path from "path";
import * as fs from "fs";
import { DOMParser } from "@xmldom/xmldom";
import {
ECObjectsError, ECObjectsStatus, ECVersion, ISchemaLocater, Schema, SchemaContext, SchemaInfo, SchemaKey, SchemaMatchType, SchemaReadHelper, XmlParser,
Expand Down Expand Up @@ -65,8 +67,8 @@ export class SchemaXmlStringLocater extends SchemaStringLocater implements ISche
if (!candidates || candidates.length === 0)
return undefined;

const maxCandidate = candidates.sort(this.compareSchemaKeyByVersion)[candidates.length - 1];
const schemaText = maxCandidate.schemaText;
const preferredCandidate = candidates.find((candidate) => this.searchPathPrecedence.get(candidate.toString()) === 0);
const schemaText = preferredCandidate ? preferredCandidate.schemaText : candidates.sort(this.compareSchemaKeyByVersion)[candidates.length - 1].schemaText;

const parser = new DOMParser();
const document = parser.parseFromString(schemaText);
Expand All @@ -90,8 +92,8 @@ export class SchemaXmlStringLocater extends SchemaStringLocater implements ISche
if (!candidates || candidates.length === 0)
return undefined;

const maxCandidate = candidates.sort(this.compareSchemaKeyByVersion)[candidates.length - 1];
const schemaText = maxCandidate.schemaText;
const preferredCandidate = candidates.find((candidate) => this.searchPathPrecedence.get(candidate.toString()) === 0);
const schemaText = preferredCandidate ? preferredCandidate.schemaText : candidates.sort(this.compareSchemaKeyByVersion)[candidates.length - 1].schemaText;

const parser = new DOMParser();
const document = parser.parseFromString(schemaText);
Expand Down Expand Up @@ -121,3 +123,89 @@ export class SchemaXmlStringLocater extends SchemaStringLocater implements ISche
}

}

/**
* A SchemaLocator implementation for locating and deserializing EC Schemas from XML strings
* loaded in memory.
* This locater is responsible for locating standard schema files
* that are released in the core-backend package and loading the schemas.
* @beta This is a workaround the current lack of a full xml parser.
*/
export class BackendSchemaXmlStringLocater extends SchemaXmlStringLocater implements ISchemaLocater {
private _standardSchemaSearchPaths: Set<string>;
private _schemasToIgnore: Set<string>;

public constructor(assetsDir: string) {
super();

// Few standard schemas are still using ECXml version 2.0.0 which is not supported by the current implementation.
// Set the locater to ignore those schemas.
this._schemasToIgnore = new Set<string>([
path.join(assetsDir, "ECSchemas", "Standard", "Bentley_Common_Classes.01.01.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "Bentley_ECSchemaMap.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "Bentley_Standard_Classes.01.01.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "Bentley_Standard_CustomAttributes.01.14.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "Dimension_Schema.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "ECDbMap.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "ECv3ConversionAttributes.01.01.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "EditorCustomAttributes.01.03.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "iip_mdb_customAttributes.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "KindOfQuantity_Schema.01.01.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "rdl_customAttributes.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "SIUnitSystemDefaults.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "Units_Schema.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "Unit_Attributes.01.00.ecschema.xml"),
path.join(assetsDir, "ECSchemas", "Standard", "USCustomaryUnitSystemDefaults.01.00.ecschema.xml"),
]);

this._standardSchemaSearchPaths = new Set<string>([
path.join(assetsDir, "ECSchemas", "Dgn"),
path.join(assetsDir, "ECSchemas", "Domain"),
path.join(assetsDir, "ECSchemas", "ECDb"),
path.join(assetsDir, "ECSchemas", "Standard"),
]);

// Load all the standard schemas
this._standardSchemaSearchPaths.forEach((searchPath) => {
if (!fs.existsSync(searchPath)) return;

fs.readdirSync(searchPath)
.map((file) => path.join(searchPath, file))
.filter((filePath) => !this._schemasToIgnore.has(filePath) && fs.statSync(filePath).isFile())
.forEach((filePath) => {
this.schemaStrings.push(fs.readFileSync(filePath).toString());
});
});

// Set all default schemas to have a lower precedence value of 1
this.schemaStrings.forEach((schema) => {
this.searchPathPrecedence.set(this.getSchemaKey(schema).toString(), 1);
});
}

/**
* Adds schema strings used by this locator to find the
* Schemas.
* @param schemaStrings An array of Schema strings to add
*/
public override addSchemaStrings(schemaStrings: string[]) {
schemaStrings.forEach((schemaString) => this.addSchemaString(schemaString));
}

/**
* Adds a schema string used by this locator to locate and load Schemas.
* @param schemaString The text of the Schema
*/
public override addSchemaString(schemaString: string) {
const schemaKey = this.getSchemaKey(schemaString);

// Check if an entry for the same schema and version already exists. If so, remove it as the user-defined latest schema should take precedence
const existingIndex = this.schemaStrings.findIndex((entry) => this.getSchemaKey(entry).matches(schemaKey, SchemaMatchType.LatestWriteCompatible));
if (existingIndex !== -1) {
this.schemaStrings.splice(existingIndex, 1);
}

this.schemaStrings.push(schemaString);
this.searchPathPrecedence.set(schemaKey.toString(), 0);
}
}
89 changes: 52 additions & 37 deletions example-code/snippets/src/backend/SchemaXmlFileLocater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { Schema, SchemaContext, SchemaKey, SchemaMatchType } from "@itwin/ecschema-metadata";
import { ECVersion, Schema, SchemaContext, SchemaKey, SchemaMatchType } from "@itwin/ecschema-metadata";
import { BackendSchemasXmlFileLocater, SchemaXmlFileLocater } from "@itwin/ecschema-locaters";
import { KnownLocations } from "@itwin/core-backend";
import path from "path";
Expand Down Expand Up @@ -55,25 +55,23 @@ describe("SchemaXmlFileLocater - locate standard schema", () => {
});

describe("BackendSchemasXmlFileLocater - locate standard schemas", () => {
it("BackendSchemasXmlFileLocater general use", async () => {
const lrSchemaKey = new SchemaKey("LinearReferencing");
const unitsSchemaKey = new SchemaKey("Units");

async function checkSchema(context: SchemaContext, schemaKey: SchemaKey) {
const schema = await context.getSchema(schemaKey);
assert.isDefined(schema);
assert.strictEqual(schema?.name, schemaKey.name);
}

it("BackendSchemasXmlFileLocater - check default schema paths", async () => {
const context = new SchemaContext();
const loc = new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir);
context.addLocater(loc);

// Get a Dgn schema
const dgnSchema = await context.getSchema(new SchemaKey("Generic", 1, 0, 5), SchemaMatchType.Latest);
assert.isDefined(dgnSchema);
assert.strictEqual(dgnSchema?.name, "Generic");

// Get a Domain schema
const domainSchema = await context.getSchema(new SchemaKey("LinearReferencing", 2, 0, 3), SchemaMatchType.Latest);
assert.isDefined(domainSchema);
assert.strictEqual(domainSchema?.name, "LinearReferencing");

// Get a Standard schema
const standardSchema = await context.getSchema(new SchemaKey("Units", 1, 0, 8), SchemaMatchType.Latest);
assert.isDefined(standardSchema);
assert.strictEqual(standardSchema?.name, "Units");
context.addLocater(new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir));

await checkSchema(context, new SchemaKey("BisCore")); // Get BisCore
await checkSchema(context, new SchemaKey("Generic")); // Get a Dgn schema
await checkSchema(context, lrSchemaKey); // Get a Domain schema
await checkSchema(context, unitsSchemaKey); // Get a Standard schema
});

function testLocatedSchema(locatedSchema: Schema | undefined, schemaName: string, schemaItemName: string, isSchemaItemDefined: boolean) {
Expand All @@ -82,18 +80,18 @@ describe("BackendSchemasXmlFileLocater - locate standard schemas", () => {
assert.equal(locatedSchema?.getItemSync(schemaItemName) !== undefined, isSchemaItemDefined);
}

it("BackendSchemasXmlFileLocater search path precedence check - sync", () => {
it("BackendSchemasXmlFileLocater - check search path precedence - sync", () => {
let context = new SchemaContext();
const locater = new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir);

// The locater has been setup to use the default standard schemas released by core-backend package.
context.addLocater(locater);

let linearReferencingSchema = context.getSchemaSync(new SchemaKey("LinearReferencing", 2, 0, 3));
let linearReferencingSchema = context.getSchemaSync(lrSchemaKey);
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "DummyTestClass", false); // should not be loaded
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "LinearLocationElement", true); // should be loaded

let unitsSchema = context.getSchemaSync(new SchemaKey("Units", 1, 0, 8));
let unitsSchema = context.getSchemaSync(unitsSchemaKey);
testLocatedSchema(unitsSchema, "Units", "DummyUnit", false); // should not be loaded
testLocatedSchema(unitsSchema, "Units", "FAHRENHEIT", true); // should be loaded

Expand All @@ -102,46 +100,47 @@ describe("BackendSchemasXmlFileLocater - locate standard schemas", () => {
context.addLocater(locater);

// Now give the locater specific search paths to the dummy "LinearReferencing" and "Units" schemas
locater.addSchemaSearchPath(path.join(__dirname, "assets", "DummyTestSchemas", "Dgn"));
locater.addSchemaSearchPath(path.join(__dirname, "assets", "DummyTestSchemas", "Domain"));
locater.addSchemaSearchPath(path.join(__dirname, "assets", "DummyTestSchemas", "Standard"));

linearReferencingSchema = context.getSchemaSync(new SchemaKey("LinearReferencing", 2, 0, 3));
linearReferencingSchema = context.getSchemaSync(lrSchemaKey);
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "DummyTestClass", true); // should be loaded
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "LinearLocationElement", true); // should be loaded

unitsSchema = context.getSchemaSync(new SchemaKey("Units", 1, 0, 8));
unitsSchema = context.getSchemaSync(unitsSchemaKey);
testLocatedSchema(unitsSchema, "Units", "DummyUnit", true); // should be loaded
testLocatedSchema(unitsSchema, "Units", "FAHRENHEIT", true); // should be loaded

// Test case 2: Register multiple search path at a time
// Test case 2: Register multiple search paths at a time
context = new SchemaContext();
const newLocater = new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir);
context.addLocater(newLocater);

// Now give the locater specific search paths to the dummy "LinearReferencing" and "Units" schemas
newLocater.addSchemaSearchPaths([path.join(__dirname, "assets", "DummyTestSchemas", "Domain"), path.join(__dirname, "assets", "DummyTestSchemas", "Standard")]);
newLocater.addSchemaSearchPaths([path.join(__dirname, "assets", "DummyTestSchemas", "Dgn"), path.join(__dirname, "assets", "DummyTestSchemas", "Domain"), path.join(__dirname, "assets", "DummyTestSchemas", "Standard")]);

linearReferencingSchema = context.getSchemaSync(new SchemaKey("LinearReferencing", 2, 0, 3));
linearReferencingSchema = context.getSchemaSync(lrSchemaKey);
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "DummyTestClass", true); // should be loaded
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "LinearLocationElement", true); // should be loaded

unitsSchema = context.getSchemaSync(new SchemaKey("Units", 1, 0, 8));
unitsSchema = context.getSchemaSync(unitsSchemaKey);
testLocatedSchema(unitsSchema, "Units", "DummyUnit", true); // should be loaded
testLocatedSchema(unitsSchema, "Units", "FAHRENHEIT", true); // should be loaded
});

it("BackendSchemasXmlFileLocater search path precedence check - async", async () => {
it("BackendSchemasXmlFileLocater - check search path precedence - async", async () => {
let context = new SchemaContext();
const locater = new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir);

// The locater has been setup to use the default standard schemas released by core-backend package.
context.addLocater(locater);

let linearReferencingSchema = await context.getSchema(new SchemaKey("LinearReferencing", 2, 0, 3));
let linearReferencingSchema = await context.getSchema(lrSchemaKey);
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "DummyTestClass", false); // should not be loaded
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "LinearLocationElement", true); // should be loaded

let unitsSchema = await context.getSchema(new SchemaKey("Units", 1, 0, 8));
let unitsSchema = await context.getSchema(unitsSchemaKey);
testLocatedSchema(unitsSchema, "Units", "DummyUnit", false); // should not be loaded
testLocatedSchema(unitsSchema, "Units", "FAHRENHEIT", true); // should be loaded

Expand All @@ -150,31 +149,47 @@ describe("BackendSchemasXmlFileLocater - locate standard schemas", () => {
context.addLocater(locater);

// Now give the locater specific search paths to the dummy "LinearReferencing" and "Units" schemas
locater.addSchemaSearchPath(path.join(__dirname, "assets", "DummyTestSchemas", "Dgn"));
locater.addSchemaSearchPath(path.join(__dirname, "assets", "DummyTestSchemas", "Domain"));
locater.addSchemaSearchPath(path.join(__dirname, "assets", "DummyTestSchemas", "Standard"));

linearReferencingSchema = await context.getSchema(new SchemaKey("LinearReferencing", 2, 0, 3));
linearReferencingSchema = await context.getSchema(lrSchemaKey);
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "DummyTestClass", true); // should be loaded
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "LinearLocationElement", true); // should be loaded

unitsSchema = await context.getSchema(new SchemaKey("Units", 1, 0, 8));
unitsSchema = await context.getSchema(unitsSchemaKey);
testLocatedSchema(unitsSchema, "Units", "DummyUnit", true); // should be loaded
testLocatedSchema(unitsSchema, "Units", "FAHRENHEIT", true); // should be loaded

// Test case 2: Register multiple search path at a time
// Test case 2: Register multiple search paths at a time
context = new SchemaContext();
const newLocater = new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir);
context.addLocater(newLocater);

// Now give the locater specific search paths to the dummy "LinearReferencing" and "Units" schemas
newLocater.addSchemaSearchPaths([path.join(__dirname, "assets", "DummyTestSchemas", "Domain"), path.join(__dirname, "assets", "DummyTestSchemas", "Standard")]);
newLocater.addSchemaSearchPaths([path.join(__dirname, "assets", "DummyTestSchemas", "Dgn"), path.join(__dirname, "assets", "DummyTestSchemas", "Domain"), path.join(__dirname, "assets", "DummyTestSchemas", "Standard")]);

linearReferencingSchema = await context.getSchema(new SchemaKey("LinearReferencing", 2, 0, 3));
linearReferencingSchema = await context.getSchema(lrSchemaKey);
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "DummyTestClass", true); // should be loaded
testLocatedSchema(linearReferencingSchema, "LinearReferencing", "LinearLocationElement", true); // should be loaded

unitsSchema = await context.getSchema(new SchemaKey("Units", 1, 0, 8));
unitsSchema = await context.getSchema(unitsSchemaKey);
testLocatedSchema(unitsSchema, "Units", "DummyUnit", true); // should be loaded
testLocatedSchema(unitsSchema, "Units", "FAHRENHEIT", true); // should be loaded
});

it("BackendSchemasXmlFileLocater - schema version check", () => {
for (const schemaVersion of [new ECVersion(2, 0, 0), new ECVersion(2, 5, 0), new ECVersion(5, 0, 0)]) {
const context = new SchemaContext();
// The locater has been setup to use the default standard schemas released by core-backend package.
context.addLocater(new BackendSchemasXmlFileLocater(KnownLocations.nativeAssetsDir));

const linearReferencingSchema = context.getSchemaSync(new SchemaKey("LinearReferencing", schemaVersion));
assert.isDefined(linearReferencingSchema);
assert.strictEqual(linearReferencingSchema?.name, "LinearReferencing");
assert.strictEqual(linearReferencingSchema?.schemaKey.version.read, 2);
assert.strictEqual(linearReferencingSchema?.schemaKey.version.write, 0);
assert.isAbove(linearReferencingSchema?.schemaKey.version?.minor ?? -1, 0);
}
});
});
Loading

0 comments on commit 163b457

Please sign in to comment.