Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/parser/parsedHedTag.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { IssueError } from '../issues/issues'
import ParsedHedSubstring from './parsedHedSubstring'
import { SchemaValueTag } from '../schema/entries'
import SchemaValueTag from '../schema/entries/valueTag'
import TagConverter from './tagConverter'
import { ReservedChecker } from './reservedChecker'

Expand Down
105 changes: 105 additions & 0 deletions src/schema/abstractLoader.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* This module holds the abstract superclass for a schema loader.
* @module schema/abstractLoader
*/
import { Schemas } from './containers'
import { SchemaSpec, SchemasSpec } from './specs'
import { type HedSchemaXMLObject } from './xmlType'
import { type IssueParameters } from '../issues/issues'
export default abstract class AbstractHedSchemaLoader {
/**
* Build a schema collection object from a schema specification.
*
* @param schemaSpecs - The description of which schemas to use.
* @returns The schema container object and any issues found.
* @throws {IssueError} If the schema specification is invalid or schemas cannot be built.
*/
buildSchemas(schemaSpecs: SchemasSpec): Promise<Schemas>
/**
* Build HED schemas from a version specification string.
*
* @param hedVersionString - The HED version specification string (can contain comma-separated versions).
* @returns A Promise that resolves to the built schemas.
* @throws {IssueError} If the schema specification is invalid or schemas cannot be built.
*/
buildSchemasFromVersion(hedVersionString?: string): Promise<Schemas>
/**
* Build a single merged schema container object from one or more XML files.
*
* @param prefix - The prefix whose schema object is being created.
* @param xmlData - The schemas' XML data.
* @returns The HED schema object.
*/
private buildSchemaObjects
/**
* Load schema XML data from a schema version or path description.
*
* @param schemaDef - The description of which schema to use.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
* @internal
*/
loadSchema(schemaDef: SchemaSpec): Promise<HedSchemaXMLObject>
/**
* Choose the schema Promise from a schema version or path description.
*
* @param schemaDef - The description of which schema to use.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
private loadPromise
/**
* Load schema XML data from a bundled file.
*
* @param schemaDef - The description of which schema to use.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
private loadBundledSchema
/**
* Determine whether this validator bundles a particular schema.
*
* @param schemaDef - The description of which schema to use.
* @returns Whether this validator bundles a particular schema.
* @throws {IssueError} If the schema could not be loaded.
*/
protected abstract hasBundledSchema(schemaDef: SchemaSpec): boolean
/**
* Retrieve the contents of a bundled schema.
*
* @param schemaDef - The description of which schema to use.
* @returns The raw schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
protected abstract getBundledSchema(schemaDef: SchemaSpec): Promise<string>
/**
* Load schema XML data from the HED GitHub repository.
*
* @param schemaDef - The standard schema version to load.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
private loadRemoteSchema
/**
* Load schema XML data from a local file.
*
* @param path - The path to the schema XML data.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
protected abstract loadLocalSchema(path: string): Promise<HedSchemaXMLObject>
/**
* Actually load the schema XML file.
*
* @param xmlDataPromise - The Promise containing the unparsed XML data.
* @param issueCode - The issue code.
* @param issueArgs - The issue arguments passed from the calling function.
* @returns The parsed schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
protected loadSchemaFile(
xmlDataPromise: Promise<string>,
issueCode: string,
issueArgs: IssueParameters,
): Promise<HedSchemaXMLObject>
}
188 changes: 188 additions & 0 deletions src/schema/abstractLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/**
* This module holds the abstract superclass for a schema loader.
* @module schema/abstractLoader
*/
import partition from 'lodash/partition'
import zip from 'lodash/zip'
import { Schema, Schemas } from './containers'
import SchemaParser from './parser/schemaParser'
import { SchemaSpec, SchemasSpec } from './specs'
import { HedSchemaXMLCollection } from './xmlType'
import { IssueError } from '../issues/issues'
import * as files from '../utils/files'
import { splitStringTrimAndRemoveBlanks } from '../utils/string'
import { parseSchemaXML } from '../utils/xml'
export default class AbstractHedSchemaLoader {
/**
* Build a schema collection object from a schema specification.
*
* @param schemaSpecs - The description of which schemas to use.
* @returns The schema container object and any issues found.
* @throws {IssueError} If the schema specification is invalid or schemas cannot be built.
*/
async buildSchemas(schemaSpecs) {
const schemaPrefixes = Array.from(schemaSpecs.data.keys())
/* Data format example:
* [[xmlData, ...], [xmlData, xmlData, ...], ...] */
const schemaXmlData = await Promise.all(
schemaPrefixes.map((prefix) => {
const specs = schemaSpecs.data.get(prefix) ?? []
return Promise.all(specs.map((spec) => this.loadSchema(spec)))
}),
)
const schemaObjects = await Promise.all(
schemaXmlData.map((schemaXmls, index) => this.buildSchemaObjects(schemaPrefixes[index], schemaXmls)),
)
const schemas = new Map(zip(schemaPrefixes, schemaObjects))
return new Schemas(schemas)
}
/**
* Build HED schemas from a version specification string.
*
* @param hedVersionString - The HED version specification string (can contain comma-separated versions).
* @returns A Promise that resolves to the built schemas.
* @throws {IssueError} If the schema specification is invalid or schemas cannot be built.
*/
async buildSchemasFromVersion(hedVersionString) {
hedVersionString ??= ''
const hedVersionSpecStrings = splitStringTrimAndRemoveBlanks(hedVersionString, ',')
const hedVersionSpecs = SchemasSpec.parseVersionSpecs(hedVersionSpecStrings)
return this.buildSchemas(hedVersionSpecs)
}
/**
* Build a single merged schema container object from one or more XML files.
*
* @param prefix - The prefix whose schema object is being created.
* @param xmlData - The schemas' XML data.
* @returns The HED schema object.
*/
async buildSchemaObjects(prefix, xmlData) {
const [standardSchemas, librarySchemas] = partition(xmlData, (xml) => xml.HED.$.library === undefined)
const [partneredLibrarySchemas, nonPartneredLibrarySchemas] = partition(
librarySchemas,
(xml) => xml.HED.$.withStandard !== undefined,
)
const [mergedLibrarySchemas, unmergedLibrarySchemas] = partition(
partneredLibrarySchemas,
(xml) => xml.HED.$.unmerged === undefined,
)
const isNonPartneredSchema = nonPartneredLibrarySchemas.length > 0
if (isNonPartneredSchema) {
if (xmlData.length > 1) {
IssueError.generateAndThrow('nonPartneredSchemaWithAnotherSchema', { prefix })
}
const schemaEntries = new SchemaParser(new HedSchemaXMLCollection(xmlData[0])).parse()
return new Schema(xmlData[0], schemaEntries, prefix)
}
const standardVersions = new Set([
...standardSchemas.map((xml) => xml.HED.$.version),
...partneredLibrarySchemas.map((xml) => xml.HED.$.withStandard),
])
if (standardVersions.size !== 1) {
IssueError.generateAndThrow('differentWithStandard', {
versions: JSON.stringify(Array.from(standardVersions).toSorted((a, b) => a.localeCompare(b))),
})
}
Comment thread
happy5214 marked this conversation as resolved.
const standardVersion = Array.from(standardVersions)[0]
const standardSchema = standardSchemas?.[0]
let baseSchemaXml = standardSchema ?? mergedLibrarySchemas.shift()
if (baseSchemaXml === undefined) {
baseSchemaXml = await this.loadSchema(new SchemaSpec(prefix, standardVersion))
}
const schemaXmls = new HedSchemaXMLCollection(
baseSchemaXml,
standardVersion,
mergedLibrarySchemas,
unmergedLibrarySchemas,
)
const schemaEntries = new SchemaParser(schemaXmls).parse()
return new Schema(baseSchemaXml, schemaEntries, prefix)
}
/**
* Load schema XML data from a schema version or path description.
*
* @param schemaDef - The description of which schema to use.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
* @internal
*/
async loadSchema(schemaDef) {
const xmlData = await this.loadPromise(schemaDef)
if (xmlData === null) {
IssueError.generateAndThrow('invalidSchemaSpecification', { spec: JSON.stringify(schemaDef) })
}
return xmlData
}
/**
* Choose the schema Promise from a schema version or path description.
*
* @param schemaDef - The description of which schema to use.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
async loadPromise(schemaDef) {
if (schemaDef.localPath) {
return this.loadLocalSchema(schemaDef.localPath)
} else if (this.hasBundledSchema(schemaDef)) {
return this.loadBundledSchema(schemaDef)
} else {
return this.loadRemoteSchema(schemaDef)
}
}
/**
* Load schema XML data from a bundled file.
*
* @param schemaDef - The description of which schema to use.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
async loadBundledSchema(schemaDef) {
try {
const bundledSchemaData = await this.getBundledSchema(schemaDef)
return parseSchemaXML(bundledSchemaData)
} catch (error) {
IssueError.generateAndRethrow(
error,
(error) => ['bundledSchemaLoadFailed', { spec: JSON.stringify(schemaDef), error: error.message }],
'Illegal error type when loading bundled schema',
)
}
}
Comment on lines +139 to +150
/**
* Load schema XML data from the HED GitHub repository.
*
* @param schemaDef - The standard schema version to load.
* @returns The schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
async loadRemoteSchema(schemaDef) {
let url
if (schemaDef.library) {
url = `https://raw.githubusercontent.com/hed-standard/hed-schemas/refs/heads/main/library_schemas/${schemaDef.library}/hedxml/HED_${schemaDef.library}_${schemaDef.version}.xml`
} else {
url = `https://raw.githubusercontent.com/hed-standard/hed-schemas/refs/heads/main/standard_schema/hedxml/HED${schemaDef.version}.xml`
}
return this.loadSchemaFile(files.readHTTPSFile(url), 'remoteSchemaLoadFailed', { spec: JSON.stringify(schemaDef) })
}
/**
* Actually load the schema XML file.
*
* @param xmlDataPromise - The Promise containing the unparsed XML data.
* @param issueCode - The issue code.
* @param issueArgs - The issue arguments passed from the calling function.
* @returns The parsed schema XML data.
* @throws {IssueError} If the schema could not be loaded.
*/
async loadSchemaFile(xmlDataPromise, issueCode, issueArgs) {
try {
const data = await xmlDataPromise
return parseSchemaXML(data)
} catch (error) {
IssueError.generateAndRethrow(
error,
(error) => [issueCode, { ...issueArgs, error: error.message }],
'Illegal error type when loading schema file',
)
}
}
Comment on lines +176 to +187
}
20 changes: 20 additions & 0 deletions src/schema/config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Bundled HED schema configuration.
* @module schema/config
*/
/**
* This list defines the base names of HED XML schema files that are considered "bundled" with the library.
* The actual loading mechanism is handled by the schema loader, which may use an application-provided loader
* for browser environments or a Node.js fs-based loader for server-side/test environments.
*/
export declare const localSchemaNames: readonly string[]
/**
* A mapping from the bundled schema names to their XML data (as strings).
*/
export declare const localSchemaMap: Readonly<Map<string, string>>
/**
* Return a copy of the bundled schema names without the "HED" prefix.
*
* @returns The list of unprefixed bundled schema names.
*/
export declare function getLocalSchemaVersions(): string[]
5 changes: 3 additions & 2 deletions src/schema/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Bundled HED schema configuration.
/**
* Bundled HED schema configuration.
* @module schema/config
* */
*/

// Static imports of bundled HED schema XML files. These are resolved at build
// time by esbuild (loader: { '.xml': 'text' }) for the published bundles, and
Expand Down
Loading
Loading