diff --git a/parser/columnSplicer.js b/parser/columnSplicer.js index 03ff8362..bc27e02e 100644 --- a/parser/columnSplicer.js +++ b/parser/columnSplicer.js @@ -115,6 +115,9 @@ export class ColumnSplicer { if (replacementString === null) { return null } + if (columnName === 'HED') { + return this._spliceHedColumnTemplate() + } if (replacementString === undefined) { this.issues.push(generateIssue('undefinedCurlyBraces', { column: columnName })) return [] @@ -130,6 +133,18 @@ export class ColumnSplicer { return replacementString.parseTree } + /** + * Splice a "HED" column value string in place of a column template. + * + * @returns {ParsedHedSubstring[]} The spliced column substitution. + * @private + */ + _spliceHedColumnTemplate() { + const columnName = 'HED' + const replacementString = this.columnValues.get(columnName) + return this._reparseAndSpliceString(replacementString) + } + /** * Splice a value-taking replacement string in place of a column template. * @@ -141,7 +156,18 @@ export class ColumnSplicer { const columnName = columnTemplate.originalTag const replacementString = this.columnReplacements.get(columnName) const replacedString = replacementString.hedString.replace('#', this.columnValues.get(columnName)) - const [newParsedString, parsingIssues] = parseHedString(replacedString, this.hedSchemas) + return this._reparseAndSpliceString(replacedString) + } + + /** + * Re-parse a string to use in splicing. + * + * @param {string} replacementString A new string to parse. + * @returns {ParsedHedSubstring[]} The new string's parse tree. + * @private + */ + _reparseAndSpliceString(replacementString) { + const [newParsedString, parsingIssues] = parseHedString(replacementString, this.hedSchemas) const flatParsingIssues = Object.values(parsingIssues).flat() if (flatParsingIssues.length > 0) { this.issues.push(...flatParsingIssues) diff --git a/tests/bids.spec.data.js b/tests/bids.spec.data.js index d365dc6b..f494da5b 100644 --- a/tests/bids.spec.data.js +++ b/tests/bids.spec.data.js @@ -647,6 +647,29 @@ const tsvFiles = [ '5.5\t0\tface\tn/a', ], ], + // sub11 - 'n/a' curly brace tests + [ + [ + Object.assign({}, sidecars[6][0], sidecars[6][2]), + 'onset\tduration\tevent_code\tresponse_time\n' + '5.0\t0\tball\tn/a\n', + ], + [ + Object.assign({}, sidecars[6][0], sidecars[6][4]), + 'onset\tduration\tevent_code\tHED\tresponse_time\n' + '5.0\t0\tball\tGreen,Def/MyColor\tn/a\n', + ], + [ + Object.assign({}, sidecars[6][0], sidecars[6][4]), + 'onset\tduration\tevent_code\tHED\tresponse_time\n' + '5.0\t0\tball\tn/a\t1\n', + ], + [ + Object.assign({}, sidecars[6][0], sidecars[6][4]), + 'onset\tduration\tevent_code\tHED\tresponse_time\n' + '5.0\t0\tball\tn/a\tn/a\n', + ], + [ + Object.assign({}, sidecars[6][0], sidecars[6][5]), + 'onset\tduration\tevent_code\tresponse_time\tresponse_code\n' + '5.0\t0\tface\t1\tn/a\n', + ], + ], ] const datasetDescriptions = [ diff --git a/tests/bids.spec.js b/tests/bids.spec.js index 3152d865..883dec14 100644 --- a/tests/bids.spec.js +++ b/tests/bids.spec.js @@ -3,9 +3,11 @@ const assert = chai.assert import converterGenerateIssue from '../converter/issues' import { generateIssue } from '../common/issues/issues' import { SchemaSpec, SchemasSpec } from '../common/schema/types' -import { parseSchemasSpec } from '../bids/schema' +import { buildBidsSchemas, parseSchemasSpec } from '../bids/schema' import { BidsDataset, BidsHedIssue, BidsIssue, validateBidsDataset } from '../bids' import { bidsDatasetDescriptions, bidsSidecars, bidsTsvFiles } from './bids.spec.data' +import { parseHedString } from '../parser/main' +import { BidsHedTsvValidator } from '../bids/validator/bidsHedTsvValidator' describe('BIDS datasets', () => { /** @@ -675,5 +677,40 @@ describe('BIDS datasets', () => { } return validator(testDatasets, expectedIssues, specs) }, 10000) + + it('should delete substrings corresponding to "n/a" TSV values', () => { + const tsvFiles = bidsTsvFiles[10] + const expectedStrings = [ + '(Def/Acc/3.5 m-per-s^2)', + '(Def/Acc/3.5 m-per-s^2), (Green, Def/MyColor)', + 'Label/1, (Def/Acc/3.5 m-per-s^2)', + '(Def/Acc/3.5 m-per-s^2)', + '(Red, Blue), (Green, (Yellow))', + ] + const dataset = new BidsDataset(tsvFiles, []) + return buildBidsSchemas(dataset, specs).then(([hedSchemas, schemaIssues]) => { + assert.isEmpty(schemaIssues, 'Schema failed to load') + const parsedExpectedStrings = [] + for (const expectedString of expectedStrings) { + const [parsedExpectedString, parsingIssues] = parseHedString(expectedString, hedSchemas) + assert.isEmpty(Object.values(parsingIssues).flat(), `String "${expectedString}" failed to parse`) + parsedExpectedStrings.push(parsedExpectedString) + } + const tsvHedStrings = [] + for (const tsvFile of tsvFiles) { + tsvFile.mergedSidecar.parseHedStrings(hedSchemas) + const tsvValidator = new BidsHedTsvValidator(tsvFile, hedSchemas) + const tsvHed = tsvValidator.parseHed() + assert.isEmpty(tsvValidator.issues, 'TSV file failed to parse') + tsvHedStrings.push(...tsvHed) + } + const formatMap = (hedString) => hedString.format() + assert.deepStrictEqual( + tsvHedStrings.map(formatMap), + parsedExpectedStrings.map(formatMap), + 'Mismatch in parsed strings', + ) + }) + }, 10000) }) })