import { Bundle } from "./model/bundle" ; import { StructureDefinition } from "./model/structure-definition" ; import { ElementDefinition } from "./model/element-definition" ; import { ParseConformance } from "./parseConformance" ; /**
 * Responsible for creating snapshots on StructureDefinition resources based on the differential of the profile.
 */ export class SnapshotGenerator { /**
     * A string that represents all options possible in STU3 and R4 for choice elements, ex: value[x], effective[x], etc.)
     * It is used to create a regex that tests that value[x] matches valueQuantity in an ElementDefinition.path.
     */ private readonly choiceRegexString = '(Instant|Time|Date|DateTime|Decimal|Boolean|Integer|String|Uri|Base64Binary|Code|Id|Oid|UnsignedInt|PositiveInt|Markdown|Url|Canonical|Uuid|Identifier|HumanName|Address|ContactPoint|Timing|Quantity|SimpleQuantity|Attachment|Range|Period|Ratio|CodeableConcept|Coding|SampledData|Age|Distance|Duration|Count|Money|MoneyQuantity|Annotation|Signature|ContactDetail|Contributor|DataRequirement|ParameterDefinition|RelatedArtifact|TriggerDefinition|UsageContext|Expression|Reference|Narrative|Extension|Meta|ElementDefinition|Dosage|Xhtml)' ; /**
     * The parser containing FHIR versioning and base profile information
     */ private readonly parser : ParseConformance ; /**
     * The bundle passed into the constructor whose structure definitions should have snapshots generated
     */ private readonly bundle : Bundle ; /**
     * A field that tracks what profiles have been processed during the .generate() operation, so that profiles are not processed multiple times in a single run.
     */ private processedUrls : string [ ] = [ ] ; param parser param bundle constructor ( parser , bundle ) { this . parser = parser ; this . bundle = bundle ; } param structureDefinitions static createBundle ( ... structureDefinitions ) { const entries = structureDefinitions . map ( ( sd ) => { return { resource : sd } ; } ) ; const bundle = { resourceType : 'Bundle' , total : structureDefinitions . length , entry : entries } ; return bundle ; } param url param type private getStructureDefinition ( url , type ) { const isBaseProfile = this . parser . isBaseProfile ( url ) ; const fhirBase = isBaseProfile ? this . parser . structureDefinitions . find ( sd => sd . url . toLowerCase ( ) === ( 'http://hl7.org/fhir/StructureDefinition/' + type ) . toLowerCase ( ) ) : null ; if ( isBaseProfile && ! fhirBase ) { throw new Error ( `Base profile for ${ url } not found. Perhaps the structures have not been loaded?` ) ; } if ( fhirBase ) { return fhirBase ; } const parentEntry = this . bundle . entry . find ( entry => entry . resource . url === url ) ; if ( ! parentEntry ) { throw new Error ( `Cannot find base definition "${ url }" in bundle or core FHIR specification.` ) ; } this . process ( parentEntry . resource ) ; return parentEntry . resource ; } private merge ( diff , snapshot ) { const dest = JSON . parse ( JSON . stringify ( snapshot ) ) ; const explicitOverwrites = [ 'id' , 'representation' , 'sliceName' , 'sliceIsConstraining' , 'label' , 'code' , 'short' , 'definition' , 'comment' , 'requirements' , 'alias' , 'min' , 'max' , 'contentReference' , 'meaningWhenMissing' , 'orderMeaning' , 'maxLength' , 'condition' , 'mustSupport' , 'isModifier' , 'isModifierReason' , 'isSummary' , 'example' ] ; for ( let eo of explicitOverwrites ) { if ( diff . hasOwnProperty ( eo ) ) dest [ eo ] = diff [ eo ] ; } if ( diff . slicing && dest . slicing ) { if ( diff . slicing . hasOwnProperty ( 'discriminator' ) ) dest . slicing . discriminator = diff . slicing . discriminator ; if ( diff . slicing . hasOwnProperty ( 'description' ) ) dest . slicing . description = diff . slicing . description ; if ( diff . slicing . hasOwnProperty ( 'ordered' ) ) dest . slicing . ordered = diff . slicing . ordered ; if ( diff . slicing . hasOwnProperty ( 'rules' ) ) dest . slicing . rules = diff . slicing . rules ; } else if ( diff . slicing ) { dest . slicing = diff . slicing ; } if ( diff . base && dest . base ) { if ( diff . base . hasOwnProperty ( 'path' ) ) dest . base . path = diff . base . path ; if ( diff . base . hasOwnProperty ( 'min' ) ) dest . base . min = diff . base . min ; if ( diff . base . hasOwnProperty ( 'max' ) ) dest . base . max = diff . base . max ; } else if ( diff . base ) { dest . base = diff . base ; } if ( diff . type && dest . type ) { for ( let dt of dest . type ) { const diffType = diff . type . find ( t => t . code === dt . code ) ; if ( diffType ) { if ( diffType . hasOwnProperty ( 'profile' ) ) dt . profile = diffType . profile ; if ( diffType . hasOwnProperty ( 'targetProfile' ) ) dt . targetProfile = diffType . targetProfile ; if ( diffType . hasOwnProperty ( 'aggregation' ) ) dt . aggregation = diffType . aggregation ; if ( diffType . hasOwnProperty ( 'versioning' ) ) dt . versioning = diffType . versioning ; } } for ( let diffType of diff . type ) { if ( ! dest . type . find ( t => t . code === diffType . code ) ) { dest . type . push ( JSON . parse ( JSON . stringify ( diffType ) ) ) ; } } } else if ( diff . type ) { dest . type = diff . type ; } if ( diff . constraint && dest . constraint ) { for ( let dc of dest . constraint ) { const diffConstraint = diff . constraint . find ( c => c . key === dc . key ) ; if ( diffConstraint ) { if ( diffConstraint . hasOwnProperty ( 'requirements' ) ) dc . requirements = diffConstraint . requirements ; if ( diffConstraint . hasOwnProperty ( 'severity' ) ) dc . severity = diffConstraint . severity ; if ( diffConstraint . hasOwnProperty ( 'human' ) ) dc . human = diffConstraint . human ; if ( diffConstraint . hasOwnProperty ( 'expression' ) ) dc . expression = diffConstraint . expression ; if ( diffConstraint . hasOwnProperty ( 'xpath' ) ) dc . xpath = diffConstraint . xpath ; if ( diffConstraint . hasOwnProperty ( 'source' ) ) dc . source = diffConstraint . source ; } } for ( let diffConstraint of diff . constraint ) { if ( ! dest . constraint . find ( c => c . key === diffConstraint . key ) ) { dest . constraint . push ( JSON . parse ( JSON . stringify ( diffConstraint ) ) ) ; } } } else if ( diff . constraint ) { dest . constraint = diff . constraint ; } const diffKeys = Object . keys ( diff ) ; const destKeys = Object . keys ( dest ) ; const diffDefaultValueKey = diffKeys . find ( k => k . startsWith ( 'defaultValue' ) ) ; const diffMinValueKey = diffKeys . find ( k => k . startsWith ( 'minValue' ) ) ; const diffMaxValueKey = diffKeys . find ( k => k . startsWith ( 'maxValue' ) ) ; const diffFixedKey = diffKeys . find ( k => k . startsWith ( 'fixed' ) ) ; const diffPatternKey = diffKeys . find ( k => k . startsWith ( 'pattern' ) ) ; const destDefaultValueKey = destKeys . find ( k => k . startsWith ( 'defaultValue' ) ) ; const destMinValueKey = destKeys . find ( k => k . startsWith ( 'minValue' ) ) ; const destMaxValueKey = destKeys . find ( k => k . startsWith ( 'maxValue' ) ) ; const destFixedKey = destKeys . find ( k => k . startsWith ( 'fixed' ) ) ; const destPatternKey = destKeys . find ( k => k . startsWith ( 'pattern' ) ) ; if ( diffDefaultValueKey ) { if ( destDefaultValueKey ) delete dest [ destDefaultValueKey ] ; dest [ diffDefaultValueKey ] = diff [ diffDefaultValueKey ] ; } if ( diffMinValueKey ) { if ( destMinValueKey ) delete dest [ destMinValueKey ] ; dest [ diffMinValueKey ] = diff [ diffMinValueKey ] ; } if ( diffMaxValueKey ) { if ( destMaxValueKey ) delete dest [ destMaxValueKey ] ; dest [ diffMaxValueKey ] = diff [ diffMaxValueKey ] ; } if ( diffFixedKey ) { if ( destFixedKey ) delete dest [ destFixedKey ] ; dest [ diffFixedKey ] = diff [ diffFixedKey ] ; } if ( diffPatternKey ) { if ( destPatternKey ) delete dest [ destPatternKey ] ; dest [ diffPatternKey ] = diff [ diffPatternKey ] ; } return dest ; } param structureDefinition private process ( structureDefinition ) { if ( this . parser . isBaseProfile ( structureDefinition . url ) || this . processedUrls . indexOf ( structureDefinition . url ) >= 0 ) { return ; } if ( ! structureDefinition . differential || ! structureDefinition . differential . element || structureDefinition . differential . element . length === 0 ) { throw new Error ( `Structure ${ structureDefinition . url } does not have a differential.` ) ; } const base = this . getStructureDefinition ( structureDefinition . baseDefinition , structureDefinition . type ) ; const newElements = JSON . parse ( JSON . stringify ( base . snapshot . element ) ) ; const matched = newElements . filter ( newElement => { if ( newElement . path === structureDefinition . type ) { return false ; } const choiceName = newElement . path . match ( /^(.*\.)?(.+)\[x\]/ ) ; const matching = structureDefinition . differential . element . filter ( ( element ) => { const regexString = newElement . path . replace ( /\[x\]/g , this . choiceRegexString ) . replace ( /\./g , '\\.' ) ; const regex = new RegExp ( regexString , 'gm' ) ; const isMatch = regex . test ( element . path ) ; return isMatch ; } ) ; return matching . length > 0 ; } ) ; matched . forEach ( ( snapshotElement ) => { const snapshotIndex = newElements . indexOf ( snapshotElement ) ; const differentialElements = structureDefinition . differential . element . filter ( element => { const regexString = snapshotElement . path . replace ( /\[x\]/g , this . choiceRegexString ) . replace ( /\./g , '\\.' ) + '(\\..*)?' ; const regex = new RegExp ( regexString , 'gm' ) ; return regex . test ( element . path ) ; } ) ; const removeElements = newElements . filter ( ( next ) => next === snapshotElement || next . path . indexOf ( snapshotElement . path + '.' ) === 0 ) ; removeElements . forEach ( removeElement => { const index = newElements . indexOf ( removeElement ) ; newElements . splice ( index , 1 ) ; } ) ; for ( let i = differentialElements . length - 1 ; i >= 0 ; i -- ) { const found = ( base . snapshot && base . snapshot . element ? base . snapshot . element : [ ] ) . find ( e => e . path === differentialElements [ i ] . path ) ; const diff = found ? this . merge ( differentialElements [ i ] , found ) : differentialElements [ i ] ; newElements . splice ( snapshotIndex , 0 , diff ) ; } } ) ; structureDefinition . snapshot = { element : newElements } ; this . processedUrls . push ( structureDefinition . url ) ; } /**
     * Generates a snapshot for all structure definitions in the bundle. If a structure definition in the bundle is a base FHIR structure definition, it is
     * assumed the structure definition already has a snapshot, and it is ignored.
     */ generate ( ) { this . processedUrls = [ ] ; if ( this . bundle && this . bundle . entry ) { this . bundle . entry . forEach ( ( entry ) => { if ( ! entry . resource || entry . resource . resourceType !== 'StructureDefinition' ) { return ; } this . process ( entry . resource ) ; } ) ; } } }