From e2192da477b94b492cdceb66eb838c064e74f27e Mon Sep 17 00:00:00 2001 From: Alexander Jones Date: Sun, 2 Mar 2025 10:56:52 -0600 Subject: [PATCH] A bunch of changes in preparation for the release of v4.0.0 --- src/bids/types/tsv.js | 1 + src/bids/validator/sidecarValidator.js | 41 ++++--- src/bids/validator/tsvValidator.js | 53 +++++---- src/bids/validator/validator.js | 17 +-- src/issues/data.js | 6 + src/issues/issues.js | 26 +---- src/parser/parsedHedTag.js | 1 - src/parser/reservedChecker.js | 15 ++- src/parser/splitter.js | 5 + src/parser/tagConverter.js | 2 +- src/schema/config.js | 26 +++-- src/schema/containers.js | 78 ++++--------- src/schema/entries.js | 154 +++---------------------- src/schema/init.js | 8 +- src/schema/parser.js | 21 ++-- src/schema/schemaMerger.js | 13 ++- src/schema/specs.js | 22 ++-- src/utils/array.js | 17 --- src/utils/hedStrings.js | 65 ----------- src/utils/memoizer.js | 2 +- src/utils/string.js | 55 +-------- tests/schema.spec.js | 10 +- tests/utils/array.spec.js | 48 -------- tests/utils/hed.spec.js | 72 ------------ tests/utils/string.spec.js | 144 ----------------------- 25 files changed, 181 insertions(+), 721 deletions(-) delete mode 100644 tests/utils/array.spec.js diff --git a/src/bids/types/tsv.js b/src/bids/types/tsv.js index 58ec5d8c..b0524d2a 100644 --- a/src/bids/types/tsv.js +++ b/src/bids/types/tsv.js @@ -15,6 +15,7 @@ export class BidsTsvFile extends BidsFile { * @type {Map} */ parsedTsv + /** * HED strings in the "HED" column of the TSV data. * @type {string[]} diff --git a/src/bids/validator/sidecarValidator.js b/src/bids/validator/sidecarValidator.js index 189d3f5b..7a4b0743 100644 --- a/src/bids/validator/sidecarValidator.js +++ b/src/bids/validator/sidecarValidator.js @@ -8,6 +8,12 @@ import { BidsValidator } from './validator' * Validator for HED data in BIDS JSON sidecars. */ export class BidsHedSidecarValidator extends BidsValidator { + /** + * The BIDS sidecar being validated. + * @type {BidsSidecar} + */ + sidecar + /** * Constructor for the BidsHedSidecarValidator. * @@ -15,7 +21,8 @@ export class BidsHedSidecarValidator extends BidsValidator { * @param {Schemas} hedSchemas - The schemas used for the sidecar validation. */ constructor(sidecar, hedSchemas) { - super(sidecar, hedSchemas) + super(hedSchemas) + this.sidecar = sidecar } /** @@ -24,9 +31,9 @@ export class BidsHedSidecarValidator extends BidsValidator { */ validate() { // Allow schema to be set a validation time -- this is checked by the superclass of BIDS file - const [errorIssues, warningIssues] = this.bidsFile.parseHed(this.hedSchemas) - this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.bidsFile.file)) - this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.bidsFile.file)) + const [errorIssues, warningIssues] = this.sidecar.parseHed(this.hedSchemas) + this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.sidecar.file)) + this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.sidecar.file)) if (errorIssues.length > 0) { return } @@ -42,7 +49,7 @@ export class BidsHedSidecarValidator extends BidsValidator { _validateStrings() { const issues = [] - for (const [sidecarKeyName, hedData] of this.bidsFile.parsedHedData) { + for (const [sidecarKeyName, hedData] of this.sidecar.parsedHedData) { if (hedData instanceof ParsedHedString) { // Value options have HED as string. issues.push(...this._checkDetails(sidecarKeyName, hedData)) @@ -82,29 +89,29 @@ export class BidsHedSidecarValidator extends BidsValidator { * @private */ _checkDefs(sidecarKeyName, hedString, placeholdersAllowed) { - let issues = this.bidsFile.definitions.validateDefs(hedString, this.hedSchemas, placeholdersAllowed) + let issues = this.sidecar.definitions.validateDefs(hedString, this.hedSchemas, placeholdersAllowed) if (issues.length > 0) { - return BidsHedIssue.fromHedIssues(issues, this.bidsFile.file, { sidecarKeyName: sidecarKeyName }) + return BidsHedIssue.fromHedIssues(issues, this.sidecar.file, { sidecarKeyName: sidecarKeyName }) } - issues = this.bidsFile.definitions.validateDefExpands(hedString, this.hedSchemas, placeholdersAllowed) - return BidsHedIssue.fromHedIssues(issues, this.bidsFile.file, { sidecarKeyName: sidecarKeyName }) + issues = this.sidecar.definitions.validateDefExpands(hedString, this.hedSchemas, placeholdersAllowed) + return BidsHedIssue.fromHedIssues(issues, this.sidecar.file, { sidecarKeyName: sidecarKeyName }) } _checkPlaceholders(sidecarKeyName, hedString) { const numberPlaceholders = getCharacterCount(hedString.hedString, '#') - const sidecarKey = this.bidsFile.sidecarKeys.get(sidecarKeyName) + const sidecarKey = this.sidecar.sidecarKeys.get(sidecarKeyName) if (!sidecarKey.valueString && !sidecarKey.hasDefinitions && numberPlaceholders > 0) { return [ BidsHedIssue.fromHedIssue( generateIssue('invalidSidecarPlaceholder', { column: sidecarKeyName, string: hedString.hedString }), - this.bidsFile.file, + this.sidecar.file, ), ] } else if (sidecarKey.valueString && numberPlaceholders === 0) { return [ BidsHedIssue.fromHedIssue( generateIssue('missingPlaceholder', { column: sidecarKeyName, string: hedString.hedString }), - this.bidsFile.file, + this.sidecar.file, ), ] } @@ -112,7 +119,7 @@ export class BidsHedSidecarValidator extends BidsValidator { return [ BidsHedIssue.fromHedIssue( generateIssue('invalidSidecarPlaceholder', { column: sidecarKeyName, string: hedString.hedString }), - this.bidsFile.file, + this.sidecar.file, ), ] } @@ -126,7 +133,7 @@ export class BidsHedSidecarValidator extends BidsValidator { */ _validateCurlyBraces() { const issues = [] - const references = this.bidsFile.columnSpliceMapping + const references = this.sidecar.columnSpliceMapping for (const [key, referredKeys] of references) { for (const referredKey of referredKeys) { @@ -134,15 +141,15 @@ export class BidsHedSidecarValidator extends BidsValidator { issues.push( BidsHedIssue.fromHedIssue( generateIssue('recursiveCurlyBracesWithKey', { column: referredKey, referrer: key }), - this.bidsFile.file, + this.sidecar.file, ), ) } - if (!this.bidsFile.parsedHedData.has(referredKey) && referredKey !== 'HED') { + if (!this.sidecar.parsedHedData.has(referredKey) && referredKey !== 'HED') { issues.push( BidsHedIssue.fromHedIssue( generateIssue('undefinedCurlyBraces', { column: referredKey }), - this.bidsFile.file, + this.sidecar.file, ), ) } diff --git a/src/bids/validator/tsvValidator.js b/src/bids/validator/tsvValidator.js index 54567114..41f88829 100644 --- a/src/bids/validator/tsvValidator.js +++ b/src/bids/validator/tsvValidator.js @@ -12,6 +12,12 @@ import { EventManager } from '../../parser/eventManager' * Validator for HED data in BIDS TSV files. */ export class BidsHedTsvValidator extends BidsValidator { + /** + * The BIDS TSV file being validated. + * @type {BidsTsvFile} + */ + tsvFile + /** * The singleton instance of the checker for reserved requirements. * @type {ReservedChecker} @@ -25,7 +31,8 @@ export class BidsHedTsvValidator extends BidsValidator { * @param {Schemas} hedSchemas - The HED schemas used to validate the tsv file. */ constructor(tsvFile, hedSchemas) { - super(tsvFile, hedSchemas) + super(hedSchemas) + this.tsvFile = tsvFile this.reserved = ReservedChecker.getInstance() } @@ -35,8 +42,8 @@ export class BidsHedTsvValidator extends BidsValidator { */ validate() { // Validate the BIDS bidsFile if it exists and return if there are errors - if (this.bidsFile.mergedSidecar) { - const issues = this.bidsFile.mergedSidecar.validate(this.hedSchemas) + if (this.tsvFile.mergedSidecar) { + const issues = this.tsvFile.mergedSidecar.validate(this.hedSchemas) const splitErrors = BidsHedIssue.splitErrors(issues) this.errors.push(...(splitErrors.error ?? [])) this.warnings.push(...(splitErrors.warning ?? [])) @@ -51,7 +58,7 @@ export class BidsHedTsvValidator extends BidsValidator { return } // Now do a full validation - const bidsHedTsvParser = new BidsHedTsvParser(this.bidsFile, this.hedSchemas) + const bidsHedTsvParser = new BidsHedTsvParser(this.tsvFile, this.hedSchemas) const [bidsEvents, errorIssues, warningIssues] = bidsHedTsvParser.parse() this.errors.push(...errorIssues) this.warnings.push(...warningIssues) @@ -59,7 +66,7 @@ export class BidsHedTsvValidator extends BidsValidator { return } this.validateDataset(bidsEvents) - if (this.errors.length === 0 && this.bidsFile.mergedSidecar?.hasHedData) { + if (this.errors.length === 0 && this.tsvFile.mergedSidecar?.hasHedData) { this._checkMissingHedWarning() this._checkMissingValueWarnings() } @@ -67,8 +74,8 @@ export class BidsHedTsvValidator extends BidsValidator { _checkMissingHedWarning() { // Check for HED column used as splice but no HED column - if (this.bidsFile.mergedSidecar.columnSpliceReferences.has('HED') && !this.bidsFile.parsedTsv.has('HED')) { - this.warnings.push(BidsHedIssue.fromHedIssue(generateIssue('hedUsedAsSpliceButNoTsvHed'), this.bidsFile.file)) + if (this.tsvFile.mergedSidecar.columnSpliceReferences.has('HED') && !this.tsvFile.parsedTsv.has('HED')) { + this.warnings.push(BidsHedIssue.fromHedIssue(generateIssue('hedUsedAsSpliceButNoTsvHed'), this.tsvFile.file)) } } @@ -77,13 +84,13 @@ export class BidsHedTsvValidator extends BidsValidator { * @private */ _checkMissingValueWarnings() { - for (const columnName of this.bidsFile.parsedTsv.keys()) { - const sidecarColumn = this.bidsFile.mergedSidecar?.sidecarKeys.get(columnName) + for (const columnName of this.tsvFile.parsedTsv.keys()) { + const sidecarColumn = this.tsvFile.mergedSidecar?.sidecarKeys.get(columnName) if (!sidecarColumn || sidecarColumn.isValueKey) { continue } const toRemove = new Set(['', 'n/a', null, undefined]) - const tsvColumnValues = new Set(this.bidsFile.parsedTsv.get(columnName)) + const tsvColumnValues = new Set(this.tsvFile.parsedTsv.get(columnName)) const cleanedValues = new Set([...tsvColumnValues].filter((value) => !toRemove.has(value))) const missingValues = [...cleanedValues].filter((value) => !sidecarColumn.categoryMap.has(value)) if (missingValues.length > 0) { @@ -91,7 +98,7 @@ export class BidsHedTsvValidator extends BidsValidator { this.warnings.push( BidsHedIssue.fromHedIssue( generateIssue('sidecarKeyMissing', { column: columnName, values: values }), - this.bidsFile.file, + this.tsvFile.file, ), ) } @@ -104,8 +111,8 @@ export class BidsHedTsvValidator extends BidsValidator { * @private */ _validateHedColumn() { - if (this.bidsFile.hedColumnHedStrings.length > 0) { - this.bidsFile.hedColumnHedStrings.flatMap((hedString, rowIndexMinusTwo) => + if (this.tsvFile.hedColumnHedStrings.length > 0) { + this.tsvFile.hedColumnHedStrings.flatMap((hedString, rowIndexMinusTwo) => this._validateHedColumnString(hedString, rowIndexMinusTwo + 2), ) } @@ -125,8 +132,8 @@ export class BidsHedTsvValidator extends BidsValidator { // Find basic parsing issues and return if unable to parse the string. (Warnings are okay.) const [parsedString, errorIssues, warningIssues] = parseHedString(hedString, this.hedSchemas, false, false) - this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.bidsFile.file, { tsvLine: rowIndex })) - this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.bidsFile.file, { tsvLine: rowIndex })) + this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.tsvFile.file, { tsvLine: rowIndex })) + this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.tsvFile.file, { tsvLine: rowIndex })) if (parsedString === null) { return } @@ -139,7 +146,7 @@ export class BidsHedTsvValidator extends BidsValidator { string: parsedString.hedString, tsvLine: rowIndex.toString(), }), - this.bidsFile.file, + this.tsvFile.file, ), ) return @@ -147,10 +154,10 @@ export class BidsHedTsvValidator extends BidsValidator { // Check whether definitions used exist and are used correctly. const defIssues = [ - ...this.bidsFile.mergedSidecar.definitions.validateDefs(parsedString, this.hedSchemas, false), - ...this.bidsFile.mergedSidecar.definitions.validateDefExpands(parsedString, this.hedSchemas, false), + ...this.tsvFile.mergedSidecar.definitions.validateDefs(parsedString, this.hedSchemas, false), + ...this.tsvFile.mergedSidecar.definitions.validateDefExpands(parsedString, this.hedSchemas, false), ] - this.errors.push(...BidsHedIssue.fromHedIssues(defIssues, this.bidsFile.file, { tsvLine: rowIndex })) + this.errors.push(...BidsHedIssue.fromHedIssues(defIssues, this.tsvFile.file, { tsvLine: rowIndex })) } /** @@ -163,7 +170,7 @@ export class BidsHedTsvValidator extends BidsValidator { return } // Temporal files have to check Onset, Inset, Offset consistency. - if (this.bidsFile.isTimelineFile) { + if (this.tsvFile.isTimelineFile) { this._validateTemporal(elements) } else { // Non-temporal files cannot have temporal tags. @@ -213,8 +220,8 @@ export class BidsHedTsvValidator extends BidsValidator { const rowString = elementList.map((element) => element.hedString).join(',') const [parsedString, errorIssues, warningIssues] = parseHedString(rowString, this.hedSchemas, false, false) const tsvLines = BidsTsvElement.getTsvLines(elementList) - this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.bidsFile.file, { tsvLine: tsvLines })) - this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.bidsFile.file, { tsvLine: tsvLines })) + this.errors.push(...BidsHedIssue.fromHedIssues(errorIssues, this.tsvFile.file, { tsvLine: tsvLines })) + this.warnings.push(...BidsHedIssue.fromHedIssues(warningIssues, this.tsvFile.file, { tsvLine: tsvLines })) } } @@ -273,7 +280,7 @@ export class BidsHedTsvValidator extends BidsValidator { this.errors.push( BidsHedIssue.fromHedIssue( generateIssue('temporalTagInNonTemporalContext', { string: element.hedString, tsvLine: element.tsvLine }), - this.bidsFile.file, + this.tsvFile.file, ), ) } diff --git a/src/bids/validator/validator.js b/src/bids/validator/validator.js index 98f523ae..f3354932 100644 --- a/src/bids/validator/validator.js +++ b/src/bids/validator/validator.js @@ -1,17 +1,14 @@ /** * Validator base class for HED data in BIDS TSV files. + * @abstract */ export class BidsValidator { - /** - * The BIDS file being validated. - * @type {BidsFile} - */ - bidsFile /** * The HED schema collection being validated against. * @type {Schemas} */ hedSchemas + /** * The issues found during validation. * @type {BidsHedIssue[]} @@ -27,11 +24,9 @@ export class BidsValidator { /** * Bids validator base class. * - * @param {BidsFile} bidsFile - The BIDS TSV file being validated. * @param {Schemas} hedSchemas - The HED schemas used for validation. */ - constructor(bidsFile, hedSchemas) { - this.bidsFile = bidsFile + constructor(hedSchemas) { this.hedSchemas = hedSchemas // Will be set when the file is validated this.errors = [] this.warnings = [] @@ -39,9 +34,7 @@ export class BidsValidator { /** * Validate a BIDS file. Overridden by particular types of BIDS files. - * + * @abstract */ - validate() { - return - } + validate() {} } diff --git a/src/issues/data.js b/src/issues/data.js index 3cc11abd..799e32ac 100644 --- a/src/issues/data.js +++ b/src/issues/data.js @@ -478,9 +478,15 @@ export default { level: 'error', message: stringTemplate`The key 'HED' was illegally used within a non-HED sidecar column.`, }, + // Internal errors internalError: { hedCode: 'INTERNAL_ERROR', level: 'error', message: stringTemplate`Internal error - message: "${'message'}".`, }, + genericError: { + hedCode: 'INTERNAL_ERROR', + level: 'error', + message: stringTemplate`Unknown HED error "${'internalCode'}" - parameters: "${'parameters'}".`, + }, } diff --git a/src/issues/issues.js b/src/issues/issues.js index 45f8bf6e..afa31984 100644 --- a/src/issues/issues.js +++ b/src/issues/issues.js @@ -48,7 +48,7 @@ export class IssueError extends Error { * @throws {IssueError} Corresponding to the generated internal error {@link Issue}. */ static generateAndThrowInternalError(message = 'Unknown internal error') { - throw new IssueError(generateIssue('internalError', { message })) + IssueError.generateAndThrow('internalError', { message }) } } @@ -61,7 +61,6 @@ export class Issue { * @type {string} */ internalCode - /** * The HED 3 error code. * @type {string} @@ -98,15 +97,6 @@ export class Issue { this.generateMessage() } - /** - * Whether this issue is an error. - * - * @returns {boolean} - */ - get isError() { - return this.level === 'error' - } - /** * Override of {@link Object.prototype.toString}. * @@ -144,16 +134,6 @@ export class Issue { this.message = `${this.level.toUpperCase()}: [${this.hedCode}] ${message} (${hedSpecLink}.)` } - - /** - * Return a tuple with a boolean denoting overall validity and all issues. - * - * @param {Issue[]} issues A list of issues. - * @returns {Array} Returns [boolean, Issue[]] indicate if validation succeeded (i.e. any errors were found)and all issues (both errors and warnings). - */ - static issueListWithValidStatus(issues) { - return [!issues.some((issue) => issue.isError), issues] - } } /** @@ -164,9 +144,9 @@ export class Issue { * @returns {Issue} An object representing the issue. */ export const generateIssue = function (internalCode, parameters) { - const issueCodeData = issueData[internalCode] ?? issueData.internalError + const issueCodeData = issueData[internalCode] ?? issueData.genericError const { hedCode, level } = issueCodeData - if (issueCodeData === issueData.internalError) { + if (issueCodeData === issueData.genericError) { parameters.internalCode = internalCode parameters.parameters = 'Issue parameters: ' + JSON.stringify(parameters) } diff --git a/src/parser/parsedHedTag.js b/src/parser/parsedHedTag.js index 6d963de9..79d4fa0d 100644 --- a/src/parser/parsedHedTag.js +++ b/src/parser/parsedHedTag.js @@ -232,7 +232,6 @@ export default class ParsedHedTag extends ParsedHedSubstring { */ hasAttribute(attribute) { return this.schemaTag.hasAttribute(attribute) - //return this.schema?.tagHasAttribute(this.formattedTag, attribute) } /** diff --git a/src/parser/reservedChecker.js b/src/parser/reservedChecker.js index ba4593b9..e51a1d39 100644 --- a/src/parser/reservedChecker.js +++ b/src/parser/reservedChecker.js @@ -3,11 +3,16 @@ import { generateIssue, IssueError } from '../issues/issues' import { getTagListString } from './parseUtils' export class ReservedChecker { - static instance = null + /** + * Singleton instance of ReservedChecker. + * @type {ReservedChecker} + * @private + */ + static #instance = undefined static reservedMap = new Map(Object.entries(reservedTags)) constructor() { - if (ReservedChecker.instance) { + if (ReservedChecker.#instance) { IssueError.generateAndThrowInternalError('Use ReservedChecker.getInstance() to get an instance of this class.') } @@ -16,10 +21,10 @@ export class ReservedChecker { // Static method to control access to the singleton instance static getInstance() { - if (!ReservedChecker.instance) { - ReservedChecker.instance = new ReservedChecker() + if (!ReservedChecker.#instance) { + ReservedChecker.#instance = new ReservedChecker() } - return ReservedChecker.instance + return ReservedChecker.#instance } _initializeReservedTags() { diff --git a/src/parser/splitter.js b/src/parser/splitter.js index d04f2fc4..5844bca7 100644 --- a/src/parser/splitter.js +++ b/src/parser/splitter.js @@ -11,12 +11,17 @@ export default class HedStringSplitter { * @type {string} */ hedString + /** * The collection of HED schemas. * @type {Schemas} */ hedSchemas + /** + * Any issues found. + * @type {Issue[]} + */ issues /** diff --git a/src/parser/tagConverter.js b/src/parser/tagConverter.js index 6a36e8cc..f1442a12 100644 --- a/src/parser/tagConverter.js +++ b/src/parser/tagConverter.js @@ -38,7 +38,7 @@ export default class TagConverter { /** * The entry manager for the tags in the active schema. - * @type {SchemaTagManager} + * @type {SchemaEntryManager} */ tagMapping diff --git a/src/schema/config.js b/src/schema/config.js index e8c8b9e6..79f99e81 100644 --- a/src/schema/config.js +++ b/src/schema/config.js @@ -1,13 +1,17 @@ /** Bundled HED schema configuration. */ -export const localSchemaList = new Map([ - ['HED8.0.0', require('../data/schemas/HED8.0.0.xml')], - ['HED8.1.0', require('../data/schemas/HED8.1.0.xml')], - ['HED8.2.0', require('../data/schemas/HED8.2.0.xml')], - ['HED8.3.0', require('../data/schemas/HED8.3.0.xml')], - ['HED_lang_1.0.0', require('../data/schemas/HED_lang_1.0.0.xml')], - ['HED_score_1.2.0', require('../data/schemas/HED_score_1.2.0.xml')], - ['HED_score_2.0.0', require('../data/schemas/HED_score_2.0.0.xml')], - ['HED_testlib_1.0.2', require('../data/schemas/HED_testlib_1.0.2.xml')], - ['HED_testlib_2.0.0', require('../data/schemas/HED_testlib_2.0.0.xml')], -]) +const localSchemas = [ + 'HED8.0.0', + 'HED8.1.0', + 'HED8.2.0', + 'HED8.3.0', + 'HED_lang_1.0.0', + 'HED_score_1.2.0', + 'HED_score_2.0.0', + 'HED_testlib_1.0.2', + 'HED_testlib_2.0.0', +] + +export const localSchemaList = new Map( + localSchemas.map((localSchema) => [localSchema, require(`../data/schemas/${localSchema}.xml`)]), +) diff --git a/src/schema/containers.js b/src/schema/containers.js index 215f6e22..5d841039 100644 --- a/src/schema/containers.js +++ b/src/schema/containers.js @@ -11,21 +11,25 @@ export class Schema { * @type {string} */ version + /** * The HED library schema name. * @type {string} */ library + /** * This schema's prefix in the active schema set. * @type {string} */ prefix + /** * The collection of schema entries. * @type {SchemaEntries} */ entries + /** * The standard HED schema version this schema is linked to. * @type {string} @@ -56,17 +60,6 @@ export class Schema { } this.entries = entries } - - /** - * Determine if a HED tag has a particular attribute in this schema. - * - * @param {string} tag The HED tag to check. - * @param {string} tagAttribute The attribute to check for. - * @returns {boolean} Whether this tag has this attribute. - */ - tagHasAttribute(tag, tagAttribute) { - return this.entries.tagHasAttribute(tag, tagAttribute) - } } /** @@ -85,6 +78,9 @@ export class PartneredSchema extends Schema { * @param {Schema[]} actualSchemas The actual HED 3 schemas underlying this partnered schema. */ constructor(actualSchemas) { + if (actualSchemas.length === 0) { + IssueError.generateAndThrowInternalError('A partnered schema set must contain at least one schema.') + } super({}, actualSchemas[0].entries) this.actualSchemas = actualSchemas this.withStandard = actualSchemas[0].withStandard @@ -99,21 +95,19 @@ export class Schemas { /** * The imported HED schemas. * - * The empty string key ("") corresponds to the schema with no nickname, - * while other keys correspond to the respective nicknames. + * The empty string key ("") corresponds to the schema with no prefix, + * while other keys correspond to the respective prefixes. * - * This field is null for syntax-only validation. - * - * @type {Map|null} + * @type {Map} */ schemas /** * Constructor. - * @param {Schema|Map|null} schemas The imported HED schemas. + * @param {Schema|Map} schemas The imported HED schemas. */ constructor(schemas) { - if (schemas === null || schemas instanceof Map) { + if (schemas instanceof Map) { this.schemas = schemas } else if (schemas instanceof Schema) { this.schemas = new Map([['', schemas]]) @@ -121,64 +115,32 @@ export class Schemas { IssueError.generateAndThrowInternalError('Invalid type passed to Schemas constructor') } if (this.schemas) { - this._addNicknamesToSchemas() + this._addPrefixesToSchemas() } } - _addNicknamesToSchemas() { - for (const [nickname, schema] of this.schemas) { - schema.prefix = nickname + _addPrefixesToSchemas() { + for (const [prefix, schema] of this.schemas) { + schema.prefix = prefix } } /** - * Return the schema with the given nickname. + * Return the schema with the given prefix. * - * @param {string} schemaName A nickname in the schema set. - * @returns {Schema} The schema object corresponding to that nickname. + * @param {string} schemaName A prefix in the schema set. + * @returns {Schema} The schema object corresponding to that prefix. */ getSchema(schemaName) { return this.schemas?.get(schemaName) } /** - * The base schema, i.e. the schema with no nickname, if one is defined. + * The base schema, i.e. the schema with no prefix, if one is defined. * * @returns {Schema} */ get baseSchema() { return this.getSchema('') } - - /** - * The standard schema, i.e. primary schema implementing the HED standard, if one is defined. - * - * @returns {Schema} - */ - get standardSchema() { - if (this.schemas === null) { - return undefined - } - for (const schema of this.schemas.values()) { - if (schema.library === '') { - return schema - } - } - return undefined - } - - /** - * The library schemas, i.e. the schema with nicknames, if any are defined. - * - * @returns {Map|null} - */ - get librarySchemas() { - if (this.schemas !== null) { - const schemasCopy = new Map(this.schemas) - schemasCopy.delete('') - return schemasCopy - } else { - return null - } - } } diff --git a/src/schema/entries.js b/src/schema/entries.js index 3750e4e8..da788e48 100644 --- a/src/schema/entries.js +++ b/src/schema/entries.js @@ -1,9 +1,9 @@ import pluralize from 'pluralize' -import Memoizer from '../utils/memoizer' -import { IssueError } from '../issues/issues' - pluralize.addUncountableRule('hertz') +import { IssueError } from '../issues/issues' +import Memoizer from '../utils/memoizer' + /** * SchemaEntries class */ @@ -40,7 +40,7 @@ export class SchemaEntries extends Memoizer { /** * The schema's tags. - * @type {SchemaTagManager} + * @type {SchemaEntryManager} */ tags @@ -57,50 +57,6 @@ export class SchemaEntries extends Memoizer { this.unitModifiers = schemaParser.unitModifiers this.tags = schemaParser.tags } - - /** - * Get a map of all of this schema's units. - */ - get allUnits() { - return this._memoize('allUnits', () => { - const units = [] - for (const unitClass of this.unitClasses.values()) { - const unitClassUnits = unitClass.units - units.push(...unitClassUnits) - } - return new Map(units) - }) - } - - /** - * Get the schema's SI unit modifiers. - * @returns {Map} - string --> SchemaUnitModifier. - */ - get SIUnitModifiers() { - return this.unitModifiers.getEntriesWithBooleanAttribute('SIUnitModifier') - } - - /** - * Get the schema's SI unit symbol modifiers. - * @returns {Map} - string --> SchemaUnitSymbolModifier. - */ - get SIUnitSymbolModifiers() { - return this.unitModifiers.getEntriesWithBooleanAttribute('SIUnitSymbolModifier') - } - - /** - * Determine if a HED tag has a particular attribute in this schema. - * - * @param {string} tag The HED tag to check. - * @param {string} tagAttribute The attribute to check for. - * @returns {boolean} Whether this tag has this attribute. - */ - tagHasAttribute(tag, tagAttribute) { - if (!this.tags.hasLongNameEntry(tag)) { - return false - } - return this.tags.getLongNameEntry(tag).hasAttribute(tagAttribute) - } } /** @@ -182,7 +138,7 @@ export class SchemaEntryManager extends Memoizer { getEntriesWithBooleanAttribute(booleanAttributeName) { return this._memoize(booleanAttributeName, () => { return this.filter(([, v]) => { - return v.hasAttribute(booleanAttributeName) + return v.hasBooleanAttribute(booleanAttributeName) }) }) } @@ -221,60 +177,6 @@ export class SchemaEntryManager extends Memoizer { } } -/** - * A manager of {@link SchemaTag} objects. - * - * @extends {SchemaEntryManager} - */ -export class SchemaTagManager extends SchemaEntryManager { - /** - * The mapping of tags by long name. - * @type {Map} - */ - _definitionsByLongName - - /** - * Constructor. - * - * @param {Map} byShortName The mapping of tags by short name. - * @param {Map} byLongName The mapping of tags by long name. - */ - constructor(byShortName, byLongName) { - super(byShortName) - this._definitionsByLongName = byLongName - } - - /** - * Determine whether the tag with the given name exists. - * - * @param {string} longName - The long name of the tag. - * @returns {boolean} -True if the tag exists. - */ - hasLongNameEntry(longName) { - return this._definitionsByLongName.has(longName) - } - - /** - * Get the tag with the given name. - * - * @param {string} longName - The long name of the tag to retrieve. - * @returns {SchemaTag} - The tag with that name. - */ - getLongNameEntry(longName) { - return this._definitionsByLongName.get(longName) - } - - /** - * Filter the map underlying this manager using the long name. - * - * @param {function} fn - ([string, SchemaTag]): boolean specifying the filtering function. - * @returns {Map} - string --> SchemaTag representing the filtered map. - */ - filterByLongName(fn) { - return SchemaEntryManager._filterDefinitionMap(this._definitionsByLongName, fn) - } -} - /** * SchemaEntry class */ @@ -307,7 +209,7 @@ export class SchemaEntry extends Memoizer { * @returns {boolean} Whether this schema entry has this attribute. */ // eslint-disable-next-line no-unused-vars - hasAttributeName(attributeName) { + hasBooleanAttribute(attributeName) { return false } } @@ -373,11 +275,13 @@ export class SchemaAttribute extends SchemaEntry { * @type {Set} */ _categoryProperties + /** * The data type of this schema attribute. * @type {SchemaProperty} */ _typeProperty + /** * The set of role properties for this schema attribute. * @type {Set} @@ -485,40 +389,30 @@ export class SchemaEntryWithAttributes extends SchemaEntry { } /** - * Whether this schema entry has this attribute. - * @param {SchemaAttribute} attribute The attribute to check for. + * Whether this schema entry has this attribute (by name). + * @param {string} attributeName The attribute to check for. * @returns {boolean} Whether this schema entry has this attribute. */ - hasAttribute(attribute) { - return this.booleanAttributeNames.has(attribute) || this.valueAttributeNames.has(attribute) + hasAttribute(attributeName) { + return this.booleanAttributeNames.has(attributeName) || this.valueAttributeNames.has(attributeName) } /** - * Retrieve the value of an attribute on this schema entry. - * @param {SchemaAttribute} attribute The attribute whose value should be returned. - * @param {boolean} alwaysReturnArray Whether to return a singleton array instead of a scalar value. - * @returns {*} The value of the attribute. - */ - getAttributeValue(attribute, alwaysReturnArray = false) { - return SchemaEntryWithAttributes._getMapArrayValue(this.valueAttributes, attribute, alwaysReturnArray) - } - - /** - * Whether this schema entry has this attribute (by name). + * Whether this schema entry has this boolean attribute (by name). * @param {string} attributeName The attribute to check for. * @returns {boolean} Whether this schema entry has this attribute. */ - hasAttributeName(attributeName) { + hasBooleanAttribute(attributeName) { return this.booleanAttributeNames.has(attributeName) } /** - * Retrieve the value of an attribute (by name) on this schema entry. + * Retrieve the value of a value attribute (by name) on this schema entry. * @param {string} attributeName The attribute whose value should be returned. * @param {boolean} alwaysReturnArray Whether to return a singleton array instead of a scalar value. * @returns {*} The value of the attribute. */ - getNamedAttributeValue(attributeName, alwaysReturnArray = false) { + getAttributeValue(attributeName, alwaysReturnArray = false) { return SchemaEntryWithAttributes._getMapArrayValue(this.valueAttributeNames, attributeName, alwaysReturnArray) } @@ -669,7 +563,7 @@ export class SchemaUnitClass extends SchemaEntryWithAttributes { * @returns {SchemaUnit} */ get defaultUnit() { - return this._units.get(this.getNamedAttributeValue('defaultUnits')) + return this._units.get(this.getAttributeValue('defaultUnits')) } /** @@ -702,15 +596,13 @@ export class SchemaUnitClass extends SchemaEntryWithAttributes { } else if (!unit.isPrefixUnit) { continue } - if (!unit.validateUnit(firstPart)) { - // If it got here, can only be a prefix Unit - continue - } else { + if (unit.validateUnit(firstPart)) { actualUnit = unit actualValueString = value.substring(unit.name.length + 1) actualUnitString = unit.name break } + // If it got here, can only be a prefix Unit } return [actualUnit, actualUnitString, actualValueString] } @@ -723,14 +615,6 @@ export class SchemaUnitModifier extends SchemaEntryWithAttributes { constructor(name, booleanAttributes, valueAttributes) { super(name, booleanAttributes, valueAttributes) } - - get isSIUnitModifier() { - return this.hasAttribute('SIUnitModifier') - } - - get isSIUnitSymbolModifier() { - return this.hasAttribute('SIUnitSymbolModifier') - } } /** diff --git a/src/schema/init.js b/src/schema/init.js index 89257dce..018b7852 100644 --- a/src/schema/init.js +++ b/src/schema/init.js @@ -42,16 +42,16 @@ const buildSchemaObjects = function (xmlData) { * @returns {Promise} The schema container object and any issues found. */ export async function buildSchemas(schemaSpecs) { - const schemaKeys = Array.from(schemaSpecs.data.keys()) + const schemaPrefixes = Array.from(schemaSpecs.data.keys()) /* Data format example: * [[xmlData, ...], [xmlData, xmlData, ...], ...] */ const schemaXmlData = await Promise.all( - schemaKeys.map((k) => { - const specs = schemaSpecs.data.get(k) + schemaPrefixes.map((prefix) => { + const specs = schemaSpecs.data.get(prefix) return Promise.all(specs.map((spec) => loadSchema(spec))) }), ) const schemaObjects = schemaXmlData.map(buildSchemaObjects) - const schemas = new Map(zip(schemaKeys, schemaObjects)) + const schemas = new Map(zip(schemaPrefixes, schemaObjects)) return new Schemas(schemas) } diff --git a/src/schema/parser.js b/src/schema/parser.js index 89add5c5..fc83a9a5 100644 --- a/src/schema/parser.js +++ b/src/schema/parser.js @@ -15,7 +15,6 @@ import { SchemaEntryManager, SchemaProperty, SchemaTag, - SchemaTagManager, SchemaUnit, SchemaUnitClass, SchemaUnitModifier, @@ -34,35 +33,46 @@ export default class SchemaParser { * @type {Object} */ rootElement + /** * @type {Map} */ properties + /** * @type {Map} */ attributes + /** * The schema's value classes. * @type {SchemaEntryManager} */ valueClasses + /** * The schema's unit classes. * @type {SchemaEntryManager} */ unitClasses + /** * The schema's unit modifiers. * @type {SchemaEntryManager} */ unitModifiers + /** * The schema's tags. - * @type {SchemaTagManager} + * @type {SchemaEntryManager} */ tags + /** + * Constructor. + * + * @param {Object} rootElement The root XML element. + */ constructor(rootElement) { this.rootElement = rootElement this._versionDefinitions = { @@ -294,12 +304,7 @@ export default class SchemaParser { this._injectTagFields(tags, shortTags, tagEntries) - const longNameTagEntries = new Map() - for (const tag of tagEntries.values()) { - longNameTagEntries.set(lc(tag.longName), tag) - } - - this.tags = new SchemaTagManager(tagEntries, longNameTagEntries) + this.tags = new SchemaEntryManager(tagEntries) } /** diff --git a/src/schema/schemaMerger.js b/src/schema/schemaMerger.js index 46d7a2ce..c1352136 100644 --- a/src/schema/schemaMerger.js +++ b/src/schema/schemaMerger.js @@ -1,6 +1,6 @@ import { IssueError } from '../issues/issues' import { SchemaTag, SchemaValueTag } from './entries' -import { PartneredSchema } from './containers' +import { PartneredSchema, Schema } from './containers' export default class PartneredSchemaMerger { /** @@ -8,11 +8,13 @@ export default class PartneredSchemaMerger { * @type {Schema[]} */ sourceSchemas + /** * The current source of data to be merged. * @type {Schema} */ currentSource + /** * The destination of data to be merged. * @type {PartneredSchema} @@ -61,7 +63,7 @@ export default class PartneredSchemaMerger { /** * The source schema's tag collection. * - * @return {SchemaTagManager} + * @return {SchemaEntryManager} */ get sourceTags() { return this.currentSource.entries.tags @@ -70,7 +72,7 @@ export default class PartneredSchemaMerger { /** * The destination schema's tag collection. * - * @returns {SchemaTagManager} + * @returns {SchemaEntryManager} */ get destinationTags() { return this.destination.entries.tags @@ -101,7 +103,7 @@ export default class PartneredSchemaMerger { * @private */ _mergeTag(tag) { - if (!tag.getNamedAttributeValue('inLibrary')) { + if (!tag.getAttributeValue('inLibrary')) { return } @@ -110,7 +112,7 @@ export default class PartneredSchemaMerger { IssueError.generateAndThrow('lazyPartneredSchemasShareTag', { tag: shortName }) } - const rootedTagShortName = tag.getNamedAttributeValue('rooted') + const rootedTagShortName = tag.getAttributeValue('rooted') if (rootedTagShortName) { const parentTag = tag.parent if (parentTag?.name?.toLowerCase() !== rootedTagShortName?.toLowerCase()) { @@ -160,6 +162,5 @@ export default class PartneredSchemaMerger { } this.destinationTags._definitions.set(newTag.name.toLowerCase(), newTag) - this.destinationTags._definitionsByLongName.set(newTag.longName.toLowerCase(), newTag) } } diff --git a/src/schema/specs.js b/src/schema/specs.js index 21dfa113..a413518b 100644 --- a/src/schema/specs.js +++ b/src/schema/specs.js @@ -3,10 +3,10 @@ */ export class SchemaSpec { /** - * The nickname of this schema. + * The prefix of this schema. * @type {string} */ - nickname + prefix /** * The version of this schema. @@ -29,13 +29,13 @@ export class SchemaSpec { /** * Constructor. * - * @param {string} nickname The nickname of this schema. + * @param {string} prefix The prefix of this schema. * @param {string} version The version of this schema. * @param {string?} library The library name of this schema. * @param {string?} localPath The local path for this schema. */ - constructor(nickname, version, library = '', localPath = '') { - this.nickname = nickname + constructor(prefix, version, library = '', localPath = '') { + this.prefix = prefix this.version = version this.library = library this.localPath = localPath @@ -56,7 +56,7 @@ export class SchemaSpec { } /** - * A specification mapping schema nicknames to SchemaSpec objects. + * A specification mapping schema prefixes to SchemaSpec objects. */ export class SchemasSpec { /** @@ -78,8 +78,8 @@ export class SchemasSpec { * @yields {Iterator} - [string, SchemaSpec[]] */ *[Symbol.iterator]() { - for (const [key, value] of this.data.entries()) { - yield [key, value] + for (const [prefix, schemaSpecs] of this.data.entries()) { + yield [prefix, schemaSpecs] } } @@ -90,10 +90,10 @@ export class SchemasSpec { * @returns {SchemasSpec| map} This object. */ addSchemaSpec(schemaSpec) { - if (this.data.has(schemaSpec.nickname)) { - this.data.get(schemaSpec.nickname).push(schemaSpec) + if (this.data.has(schemaSpec.prefix)) { + this.data.get(schemaSpec.prefix).push(schemaSpec) } else { - this.data.set(schemaSpec.nickname, [schemaSpec]) + this.data.set(schemaSpec.prefix, [schemaSpec]) } return this } diff --git a/src/utils/array.js b/src/utils/array.js index cc5cfd5e..3f29489f 100644 --- a/src/utils/array.js +++ b/src/utils/array.js @@ -1,20 +1,3 @@ -/** - * Get number of instances of an element in an array. - * - * @param {Array} array The array to search. - * @param {*} elementToCount The element to search for. - * @returns {number} The number of instances of the element in the array. - */ -export const getElementCount = function (array, elementToCount) { - let count = 0 - for (let i = 0; i < array.length; i++) { - if (array[i] === elementToCount) { - count++ - } - } - return count -} - /** * Apply a function recursively to an array. * diff --git a/src/utils/hedStrings.js b/src/utils/hedStrings.js index 9f6c7875..f48673f3 100644 --- a/src/utils/hedStrings.js +++ b/src/utils/hedStrings.js @@ -1,18 +1,3 @@ -import pluralize from 'pluralize' -pluralize.addUncountableRule('hertz') - -/** - * Replace the end of a HED tag with a pound sign. - */ -export const replaceTagNameWithPound = function (formattedTag) { - const lastTagSlashIndex = formattedTag.lastIndexOf('/') - if (lastTagSlashIndex !== -1) { - return formattedTag.substring(0, lastTagSlashIndex) + '/#' - } else { - return '#' - } -} - /** * Get the indices of all slashes in a HED tag. */ @@ -24,53 +9,3 @@ export const getTagSlashIndices = function (tag) { } return indices } - -/** - * Get the last part of a HED tag. - * - * @param {string} tag A HED tag - * @param {string} character The character to use as a separator. - * @returns {string} The last part of the tag using the given separator. - */ -export const getTagName = function (tag, character = '/') { - const lastSlashIndex = tag.lastIndexOf(character) - if (lastSlashIndex === -1) { - return tag - } else { - return tag.substring(lastSlashIndex + 1) - } -} - -/** - * Get the HED tag prefix (up to the last slash). - */ -export const getParentTag = function (tag, character = '/') { - const lastSlashIndex = tag.lastIndexOf(character) - if (lastSlashIndex === -1) { - return tag - } else { - return tag.substring(0, lastSlashIndex) - } -} - -const openingGroupCharacter = '(' -const closingGroupCharacter = ')' - -/** - * Determine whether a HED string is a group (surrounded by parentheses). - * - * @param {string} hedString A HED string. - */ -export const hedStringIsAGroup = function (hedString) { - const trimmedHedString = hedString.trim() - return trimmedHedString.startsWith(openingGroupCharacter) && trimmedHedString.endsWith(closingGroupCharacter) -} - -/** - * Return a copy of a group tag with the surrounding parentheses removed. - * - * @param {string} tagGroup A tag group string. - */ -export const removeGroupParentheses = function (tagGroup) { - return tagGroup.slice(1, -1) -} diff --git a/src/utils/memoizer.js b/src/utils/memoizer.js index 19b6d70e..ba7db9d4 100644 --- a/src/utils/memoizer.js +++ b/src/utils/memoizer.js @@ -1,4 +1,4 @@ -/** Utility classes. **/ +/** Memoizer class. **/ import { IssueError } from '../issues/issues' diff --git a/src/utils/string.js b/src/utils/string.js index f034389a..48b5ca34 100644 --- a/src/utils/string.js +++ b/src/utils/string.js @@ -1,17 +1,4 @@ -import date from 'date-and-time' -import { parseISO, isValid as dateIsValid } from 'date-fns' -const rfc3339ish = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(.\d+)?$/ -const digitExpression = /^-?\d+(?:\.\d+)?(?:[Ee]-?\d+)?$/ - -/** - * Check if a string is empty or only whitespace. - * - * @param {string} string The string to check. - * @returns {boolean} Whether the string is empty. - */ -export const stringIsEmpty = function (string) { - return !string.trim() -} +/** String-related utility functions */ /** * Get number of instances of an character in a string. @@ -24,46 +11,6 @@ export const getCharacterCount = function (string, characterToCount) { return string.split(characterToCount).length - 1 } -/** - * Get a copy of a string with the first letter capitalized. - * - * @param {string} string The string to capitalize. - * @returns {string} The capitalized string. - */ -export const capitalizeString = function (string) { - return string.charAt(0).toUpperCase() + string.substring(1) -} - -/** - * Determine if a string is a valid clock face time. - * - * @param {string} timeString The string to check. - * @returns {boolean} Whether the string is a valid clock face time. - */ -export const isClockFaceTime = function (timeString) { - return date.isValid(timeString, 'HH:mm') || date.isValid(timeString, 'HH:mm:ss') -} - -/** - * Determine if a string is a valid date-time. - * - * @param {string} dateTimeString The string to check. - * @returns {boolean} Whether the string is a valid date-time. - */ -export const isDateTime = function (dateTimeString) { - return dateIsValid(parseISO(dateTimeString)) && rfc3339ish.test(dateTimeString) -} - -/** - * Determine if a string is a valid number. - * - * @param {string} numericString The string to check. - * @returns {boolean} Whether the string is a valid number. - */ -export const isNumber = function (numericString) { - return digitExpression.test(numericString) -} - /** * Parse a template literal string. * diff --git a/tests/schema.spec.js b/tests/schema.spec.js index d9e2ed4e..62ce5555 100644 --- a/tests/schema.spec.js +++ b/tests/schema.spec.js @@ -35,7 +35,7 @@ describe('HED schemas', () => { const specs = new SchemasSpec().addSchemaSpec(spec1) const hedSchemas = await buildSchemas(specs) - const schema1 = hedSchemas.getSchema(spec1.nickname) + const schema1 = hedSchemas.getSchema(spec1.prefix) assert.strictEqual(schema1.version, spec1.version, 'Schema has wrong version number') assert.strictEqual(schema1.library, spec1.library, 'Schema has wrong library name') @@ -48,9 +48,9 @@ describe('HED schemas', () => { const specs = new SchemasSpec().addSchemaSpec(spec1).addSchemaSpec(spec2).addSchemaSpec(spec3) const hedSchemas = await buildSchemas(specs) - const schema1 = hedSchemas.getSchema(spec1.nickname) - const schema2 = hedSchemas.getSchema(spec2.nickname) - const schema3 = hedSchemas.getSchema(spec3.nickname) + const schema1 = hedSchemas.getSchema(spec1.prefix) + const schema2 = hedSchemas.getSchema(spec2.prefix) + const schema3 = hedSchemas.getSchema(spec3.prefix) assert.strictEqual(schema1.version, spec1.version, 'Schema 1 has wrong version number') assert.strictEqual(schema1.library, spec1.library, 'Schema 1 has wrong library name') @@ -70,7 +70,7 @@ describe('HED schemas', () => { const specs = new SchemasSpec().addSchemaSpec(spec1) const hedSchemas = await buildSchemas(specs) - const schema1 = hedSchemas.getSchema(spec1.nickname) + const schema1 = hedSchemas.getSchema(spec1.prefix) assert.strictEqual(schema1.version, spec1.version, 'Schema has wrong version number') assert.strictEqual(schema1.library, spec1.library, 'Schema has wrong library name') diff --git a/tests/utils/array.spec.js b/tests/utils/array.spec.js deleted file mode 100644 index 6068861a..00000000 --- a/tests/utils/array.spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import chai from 'chai' -const assert = chai.assert -import { describe, it } from '@jest/globals' - -import * as arrayUtils from '../../src/utils/array' - -describe('Array utility functions', () => { - describe('Element counts', () => { - it('must be correct', () => { - const testArray = [ - 'a', - 'b', - 'c', - 'a', - 'b', - 'c', - 'a', - 'a', - 'a', - 'b', - 'c', - 'c', - 'c', - 'c', - 'd', - 'd', - 'd', - 'f', - 'd', - 'd', - 'd', - 'd', - ] - const resultA = arrayUtils.getElementCount(testArray, 'a') - const resultB = arrayUtils.getElementCount(testArray, 'b') - const resultC = arrayUtils.getElementCount(testArray, 'c') - const resultD = arrayUtils.getElementCount(testArray, 'd') - const resultE = arrayUtils.getElementCount(testArray, 'e') - const resultF = arrayUtils.getElementCount(testArray, 'f') - assert.strictEqual(resultA, 5) - assert.strictEqual(resultB, 3) - assert.strictEqual(resultC, 6) - assert.strictEqual(resultD, 7) - assert.strictEqual(resultE, 0) - assert.strictEqual(resultF, 1) - }) - }) -}) diff --git a/tests/utils/hed.spec.js b/tests/utils/hed.spec.js index c4f407a1..37f87cbe 100644 --- a/tests/utils/hed.spec.js +++ b/tests/utils/hed.spec.js @@ -24,20 +24,6 @@ describe('HED tag string utility functions', () => { } } - it('should properly replace tag values with the pound character', () => { - const testStrings = { - slash: 'Event/Duration/4 ms', - noSlash: 'Something', - } - const expectedResults = { - slash: 'Event/Duration/#', - noSlash: '#', - } - validator(testStrings, expectedResults, (string) => { - return hed.replaceTagNameWithPound(string) - }) - }) - it('should detect the locations of slashes in a tag', () => { const testStrings = { description: 'Event/Description/Something', @@ -53,63 +39,5 @@ describe('HED tag string utility functions', () => { return hed.getTagSlashIndices(string) }) }) - - it('should extract the last part of a tag', () => { - const testStrings = { - description: 'Event/Description/Something', - direction: 'Attribute/Direction/Left', - noSlash: 'Participant', - } - const expectedResults = { - description: 'Something', - direction: 'Left', - noSlash: 'Participant', - } - validator(testStrings, expectedResults, (string) => { - return hed.getTagName(string) - }) - }) - - it('should extract the parent part of a tag', () => { - const testStrings = { - description: 'Event/Description/Something', - direction: 'Attribute/Direction/Left', - noSlash: 'Participant', - } - const expectedResults = { - description: 'Event/Description', - direction: 'Attribute/Direction', - noSlash: 'Participant', - } - validator(testStrings, expectedResults, (string) => { - return hed.getParentTag(string) - }) - }) - - it('must be surrounded by parentheses', () => { - const testStrings = { - group: '(/Attribute/Object side/Left,/Participant/Effect/Body part/Arm)', - nonGroup: '/Attribute/Object side/Left,/Participant/Effect/Body part/Arm', - } - const expectedResults = { - group: true, - nonGroup: false, - } - validator(testStrings, expectedResults, (string) => { - return hed.hedStringIsAGroup(string) - }) - }) - - it('can have its parentheses removed', () => { - const testStrings = { - group: '(/Attribute/Object side/Left,/Participant/Effect/Body part/Arm)', - } - const expectedResults = { - group: '/Attribute/Object side/Left,/Participant/Effect/Body part/Arm', - } - validator(testStrings, expectedResults, (string) => { - return hed.removeGroupParentheses(string) - }) - }) }) }) diff --git a/tests/utils/string.spec.js b/tests/utils/string.spec.js index 1c466b0c..55c1a32e 100644 --- a/tests/utils/string.spec.js +++ b/tests/utils/string.spec.js @@ -5,52 +5,6 @@ import { describe, it } from '@jest/globals' import * as stringUtils from '../../src/utils/string' describe('String utility functions', () => { - describe('Blank strings', () => { - it('may be empty', () => { - const emptyString = '' - const result = stringUtils.stringIsEmpty(emptyString) - assert.strictEqual(result, true) - }) - - it('may have only whitespace', () => { - const spaceString = ' \n \t ' - const result = stringUtils.stringIsEmpty(spaceString) - assert.strictEqual(result, true) - }) - - it('may not contain letters', () => { - const aString = 'a' - const result = stringUtils.stringIsEmpty(aString) - assert.strictEqual(result, false) - }) - - it('may not contain numbers', () => { - const oneString = '1' - const result = stringUtils.stringIsEmpty(oneString) - assert.strictEqual(result, false) - }) - - it('may not contain punctuation', () => { - const slashString = '/' - const result = stringUtils.stringIsEmpty(slashString) - assert.strictEqual(result, false) - }) - }) - - describe('Capitalized strings', () => { - it('must have a capitalized first letter', () => { - const testString = 'to be' - const result = stringUtils.capitalizeString(testString) - assert.strictEqual(result, 'To be') - }) - - it('must not change letters after the first letter', () => { - const testString = 'to BE or NOT to BE' - const result = stringUtils.capitalizeString(testString) - assert.strictEqual(result, 'To BE or NOT to BE') - }) - }) - describe('Character counts', () => { it('must be correct', () => { const testString = 'abcabcaaabccccdddfdddd' @@ -68,102 +22,4 @@ describe('String utility functions', () => { assert.strictEqual(resultF, 1) }) }) - - describe('Simple string validation functions', () => { - /** - * Test a string validation function. - * @param {function (string): boolean} testFunction The validation function to test. - * @param {Object} validStrings A set of valid strings. - * @param {Object} invalidStrings A set of invalid strings. - */ - const validate = function (testFunction, validStrings, invalidStrings) { - for (const [key, string] of Object.entries(validStrings)) { - assert.isTrue(testFunction(string), key) - } - for (const [key, string] of Object.entries(invalidStrings)) { - assert.isFalse(testFunction(string), key) - } - } - - describe('Valid HED times', () => { - it('must be of the form HH:MM or HH:MM:SS', () => { - const validStrings = { - validPM: '23:52', - validMidnight: '00:55', - validHour: '11:00', - validSingleDigitHour: '08:30', - validSeconds: '19:33:47', - } - const invalidStrings = { - invalidDate: '8/8/2019', - invalidHour: '25:11', - invalidSingleDigitHour: '8:30', - invalidMinute: '12:65', - invalidSecond: '15:45:82', - invalidTimeZone: '16:25:51+00:00', - invalidMilliseconds: '17:31:05.123', - invalidMicroseconds: '09:21:16.123456', - invalidDateTime: '2000-01-01T00:55:00', - invalidString: 'not a time', - } - validate(stringUtils.isClockFaceTime, validStrings, invalidStrings) - }) - }) - - describe('Valid HED date-times', () => { - it('must be in ISO 8601 format', () => { - const validStrings = { - validPM: '2000-01-01T23:52:00', - validMidnight: '2000-01-01T00:55:00', - validHour: '2000-01-01T11:00:00', - validSingleDigitHour: '2000-01-01T08:30:00', - validSeconds: '2000-01-01T19:33:47', - validMilliseconds: '2000-01-01T17:31:05.123', - validMicroseconds: '2000-01-01T09:21:16.123456', - } - const invalidStrings = { - invalidDate: '8/8/2019', - invalidTime: '00:55:00', - invalidHour: '2000-01-01T25:11', - invalidSingleDigitHour: '2000-01-01T8:30', - invalidMinute: '2000-01-01T12:65', - invalidSecond: '2000-01-01T15:45:82', - invalidTimeZone: '2000-01-01T16:25:51+00:00', - invalidString: 'not a time', - } - validate(stringUtils.isDateTime, validStrings, invalidStrings) - }) - }) - - describe('Valid HED numbers', () => { - it('must be in scientific notation', () => { - const validStrings = { - validPositiveInteger: '21', - validNegativeInteger: '-500', - validPositiveDecimal: '8520.63', - validNegativeDecimal: '-945.61', - validPositiveFractional: '0.84', - validNegativeFractional: '-0.61', - validPositiveScientificInteger: '21e10', - validNegativeScientificInteger: '-500E-5', - validPositiveScientificDecimal: '8520.63E15', - validNegativeScientificDecimal: '-945.61e-3', - validPositiveScientificFractional: '0.84e-2', - validNegativeScientificFractional: '-0.61E5', - } - const invalidStrings = { - invalidDecimalPoint: '.', - invalidMultipleDecimalPoints: '22.88.66', - invalidMultipleEs: '888ee66', - invalidStartCharacter: 'e77e6', - invalidBlankExponent: '432e', - invalidBlankNegativeExponent: '1853e-', - invalidStartingDecimalPoint: '.852', - invalidEndingDecimalPoint: '851695.', - invalidOtherCharacter: '81468g516', - } - validate(stringUtils.isNumber, validStrings, invalidStrings) - }) - }) - }) })