Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track code caret rules when simplifying array indexing #247

Merged
merged 2 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
64 changes: 12 additions & 52 deletions src/optimizer/plugins/SimplifyArrayIndexingOptimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ export default {
const ruleArr: fshrules.Rule[] = def.rules;
const parsedPaths = ruleArr.map((rule: fshrules.Rule) => {
const parsedPath: { path: fhirtypes.PathPart[]; caretPath?: fhirtypes.PathPart[] } = {
path: parseFSHPath(rule.path),
path: utils.parseFSHPath(rule.path),
caretPath: []
};
// If we have a CaretValueRule, we'll need a second round of parsing for the caret path
if (rule instanceof ExportableCaretValueRule) {
parsedPath.caretPath = parseFSHPath(rule.caretPath);
parsedPath.caretPath = utils.parseFSHPath(rule.caretPath);
}
return parsedPath;
});
Expand All @@ -44,19 +44,24 @@ export default {
});

parsedPaths.forEach((parsedRule: any, ruleIndex: number) => {
// First handle the rule path
const originalRule = def.rules[ruleIndex];
parsedRule.path.forEach((element: fhirtypes.PathPart) => {
applySoftIndexing(element, pathMap);
});

// Then handle the caret rule paths
const key =
originalRule instanceof ExportableCaretValueRule && originalRule.isCodeCaretRule
? JSON.stringify(originalRule.pathArray)
: originalRule.path;
parsedRule.caretPath.forEach((element: fhirtypes.PathPart) => {
// Caret path indexes should only be resolved in the context of a specific path
// Each normal path has a separate map to keep track of the caret path indexes
if (!caretPathMap.has(originalRule.path)) {
caretPathMap.set(originalRule.path, new Map());
if (!caretPathMap.has(key)) {
caretPathMap.set(key, new Map());
}

const elementCaretPathMap = caretPathMap.get(originalRule.path);
const elementCaretPathMap = caretPathMap.get(key);
applySoftIndexing(element, elementCaretPathMap);
});
});
Expand Down Expand Up @@ -86,7 +91,7 @@ function addPrefixes(parsedPath: fhirtypes.PathPart[]) {
}

/**
* Converts numeric indeces on a PathPart into soft indexing characters
* Converts numeric indices on a PathPart into soft indexing characters
* @param {PathPart} element - A single element in a rules path
* @param {Map<string, number} pathMap - A map containing an element's name as the key and that element's updated index as the value
* @param {Map<string, PathPart[]} singletonArrayElements - A map containing a string unique to each element of type array as the key, and an array of PathParts as the value
Expand Down Expand Up @@ -116,51 +121,6 @@ function applySoftIndexing(element: fhirtypes.PathPart, pathMap: Map<string, num
}
}

// TODO: remove once sushi has been released with fixes to the parseFSHPath function
function splitOnPathPeriods(path: string): string[] {
return path.split(/\.(?![^\[]*\])/g); // match a period that isn't within square brackets
}

// TODO: remove once sushi has been released with fixes to the parseFSHPath function
export function parseFSHPath(fshPath: string): fhirtypes.PathPart[] {
const pathParts: fhirtypes.PathPart[] = [];
const seenSlices: string[] = [];
const indexRegex = /^[0-9]+$/;
const splitPath = fshPath === '.' ? [fshPath] : splitOnPathPeriods(fshPath);
for (const pathPart of splitPath) {
const splitPathPart = pathPart.split('[');
if (splitPathPart.length === 1 || pathPart.endsWith('[x]')) {
// There are no brackets, or the brackets are for a choice, so just push on the name
pathParts.push({ base: pathPart });
} else {
// We have brackets, let's save the bracket info
let fhirPathBase = splitPathPart[0];
// Get the bracket elements and slice off the trailing ']'
let brackets = splitPathPart.slice(1).map(s => s.slice(0, -1));
// Get rid of any remaining [x] elements in the brackets
if (brackets[0] === 'x') {
fhirPathBase += '[x]';
brackets = brackets.slice(1);
}
brackets.forEach(bracket => {
if (!indexRegex.test(bracket) && !(bracket === '+' || bracket === '=')) {
seenSlices.push(bracket);
}
});
if (seenSlices.length > 0) {
pathParts.push({
base: fhirPathBase,
brackets: brackets,
slices: [...seenSlices]
});
} else {
pathParts.push({ base: fhirPathBase, brackets: brackets });
}
}
}
return pathParts;
}

// Removes '[0]' and '[=]' indexing from elements with a single value in their array
function removeZeroIndices(parsedPaths: any[]) {
const regularPathElements = _.flatten(parsedPaths.map(rule => rule.path));
Expand Down
64 changes: 63 additions & 1 deletion test/optimizer/plugins/SimplifyArrayIndexingOptimizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
ExportableInstance,
ExportableProfile,
ExportableLogical,
ExportableResource
ExportableResource,
ExportableCodeSystem
} from '../../../src/exportable';
import optimizer from '../../../src/optimizer/plugins/SimplifyArrayIndexingOptimizer';

Expand Down Expand Up @@ -167,5 +168,66 @@ describe('optimizer', () => {
expect(optimizedCaretRules[2].caretPath).toBe('contact[=].email');
expect(optimizedCaretRules[3].caretPath).toBe('contact[+].phone');
});

it('should apply soft indexing on caret paths on concepts for a CodeSystem', () => {
const codeSystem = new ExportableCodeSystem('MyCS');

// const bread = new ExportableConceptRule('bread');
const breadProperty = new ExportableCaretValueRule('');
breadProperty.isCodeCaretRule = true;
breadProperty.pathArray = ['bread'];
breadProperty.caretPath = 'property[0].code';
const breadLanguage = new ExportableCaretValueRule('');
breadLanguage.isCodeCaretRule = true;
breadLanguage.pathArray = ['bread'];
breadLanguage.caretPath = 'designation[0].language';
const breadUse = new ExportableCaretValueRule('');
breadUse.isCodeCaretRule = true;
breadUse.pathArray = ['bread'];
breadUse.caretPath = 'designation[0].use';

// const cake = new ExportableConceptRule('cake');
const cakeLanguage0 = new ExportableCaretValueRule('');
cakeLanguage0.isCodeCaretRule = true;
cakeLanguage0.pathArray = ['cake'];
cakeLanguage0.caretPath = 'designation[0].language';
const cakeUse0 = new ExportableCaretValueRule('');
cakeUse0.isCodeCaretRule = true;
cakeUse0.pathArray = ['cake'];
cakeUse0.caretPath = 'designation[0].use';
const cakeLanguage1 = new ExportableCaretValueRule('');
cakeLanguage1.isCodeCaretRule = true;
cakeLanguage1.pathArray = ['cake'];
cakeLanguage1.caretPath = 'designation[1].language';
const cakeUse1 = new ExportableCaretValueRule('');
cakeUse1.isCodeCaretRule = true;
cakeUse1.pathArray = ['cake'];
cakeUse1.caretPath = 'designation[1].use';

codeSystem.rules.push(
// bread,
breadProperty,
breadLanguage,
breadUse,
// cake,
cakeLanguage0,
cakeUse0,
cakeLanguage1,
cakeUse1
);

const myPackage = new Package();
myPackage.add(codeSystem);
optimizer.optimize(myPackage);

const optimizedCaretRules = myPackage.codeSystems[0].rules as ExportableCaretValueRule[];
expect(optimizedCaretRules[0].caretPath).toBe('property.code');
expect(optimizedCaretRules[1].caretPath).toBe('designation[0].language');
expect(optimizedCaretRules[2].caretPath).toBe('designation[=].use');
expect(optimizedCaretRules[3].caretPath).toBe('designation[0].language');
expect(optimizedCaretRules[4].caretPath).toBe('designation[=].use');
expect(optimizedCaretRules[5].caretPath).toBe('designation[+].language');
expect(optimizedCaretRules[6].caretPath).toBe('designation[=].use');
});
});
});
Loading