Serializing EC Schemas to XML

When working the EC Schema instances, it may be necessary to serialize the Schema to an XML file. The first option is a utility method found in the Bentley provided package @itwin/ecschema-locaters. The second option utilizes two interfaces from the @xmldom/xmldom package. Both options will be covered here.

SchemaXml in @itwin/ecschema-locaters

The SchemaXml.writeFile method takes the EC Schema instance and serializes it to a specified location on disk. Note: this method is async, requiring the caller to await it.

await SchemaXml.writeFile(schema, outDir);

@xmldom/xmldom package

If you prefer to not have a dependency on the @itwin/ecschema-locaters package, the following example demonstrates how to serialize the Schema to an XML file using the DOMParser and XMLSerializer interface from the @xmldom/xmldom package. The fs-extra package is used to work with the file system.

import * as fs from "fs-extra"; // file system api
import { DOMParser, XMLSerializer } from "@xmldom/xmldom"; // XML support
import { SchemaContext } from "@itwin/ecschema-metadata";
import { SchemaContextEditor } from "@itwin/ecschema-editing"; // For creating a small sample Schema for this tutorial

First, you must have an instance of an EC Schema to serialize. The schema may be created, loaded from an iModel, or perhaps from an xml file on the file system. In this sample we will be creating a new Schema using schema editing API available in the @itwin/ecschema-editing package.

const context = new SchemaContext();
const editor = new SchemaContextEditor(context);
const results = await editor.createSchema("sampleSchema", "sampleAlias", 1, 0, 0);
const sampleSchema = await editor.getSchema(results.schemaKey!);

Now that we have a schema to work with, create a new DOM XML document using the DOMParser. The Document can then be passed to the Schema.toXml() method which returns the Document containing the schema XML. We can then utilize the XMLSerializer interface to serialize the XML to a string.

let xmlDoc = new DOMParser().parseFromString(`<?xml version="1.0" encoding="UTF-8"?>`, "application/xml");
xmlDoc = await sampleSchema!.toXml(xmlDoc);
const serializer = new XMLSerializer();
const xml = serializer.serializeToString(xmlDoc);

We can now use the file system API to write the xml to file.

fs.writeFileSync("c:\\sample\\path\\to\\file", xml);

Schemas from an iModel

First use the SchemaLoader to create a SchemaContextEditor that knows about the schemas in the iModel

// To use the schemas already in an iModel create a SchemaLoader that pulls schemas from the iModel
// The SchemaLoader can load schemas from another location or you can create an empty context using the
// SchemaContext constructor
const loader = new SchemaLoader((name) => iModelDb.getSchemaProps(name));
const editor = new SchemaContextEditor(loader.context);

Once an editor has been created the new schema can be created with classes that reference BisCore

const createSchemaResult = await editor.createSchema("PipingSchema", "PS", 1,0,42);
if (createSchemaResult.errorMessage !== undefined) {
  throw new Error("Failed to create schema");
}

const bisSchema = loader.getSchema("BisCore");
const addRefResult = await editor.addSchemaReference(createSchemaResult.schemaKey!, bisSchema);
if(addRefResult.errorMessage !== undefined) {
  throw new Error(`failed to add reference schema: ${addRefResult.errorMessage}`);
}

const createClassResult = await editor.entities.createElement(createSchemaResult.schemaKey!, "Pipe", ECClassModifier.None, bisSchema.getSchemaItemKey("BisCore.PhysicalElement"));
if (createClassResult.errorMessage !== undefined) {
  throw new Error(`Failed to create class: ${createClassResult.errorMessage}`);
}

const createPropResult = await editor.entities.createProperty(createClassResult.itemKey!, "Diameter", PrimitiveType.Double, "cgk");
if (createPropResult.errorMessage !== undefined) {
  throw new Error(`Failed to create property: ${createPropResult.errorMessage}`);
}

Finally the new schema can be extracted from the editor, serialized to xml and imported into the iModel

const pipingSchema = await editor.getSchema(createSchemaResult.schemaKey!);
if (pipingSchema === undefined) {
  throw new Error(`Schema Key ${createSchemaResult.schemaKey!.toString(true)} not found in context`);
}
const schemaXml = await SchemaXml.writeString(pipingSchema);
await iModelDb.importSchemaStrings([schemaXml]);

Last Updated: 29 January, 2024