UNPKG

fhir

Version:

Library that assists in handling FHIR resources. Supports serialization between JSON and XML, validation and FhirPath evaluation.

406 lines 17.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConvertToJs = void 0; const convert = require("xml-js"); const parseConformance_1 = require("./parseConformance"); const xmlHelper_1 = require("./xmlHelper"); const constants_1 = require("./constants"); class ConvertToJs { constructor(parser) { this.parser = parser || new parseConformance_1.ParseConformance(true); } convert(xml) { const xmlObj = convert.xml2js(xml); const firstElement = xmlObj.elements.find((element) => element.type === 'element'); if (firstElement) { return this.resourceToJS(firstElement, null); } } convertToJSON(xml) { const xmlObj = convert.xml2js(xml); if (xmlObj.elements.length !== 1) { return; } const surroundDecimalsWith = {}; const jsObj = this.resourceToJS(xmlObj.elements[0], surroundDecimalsWith); const maxDLength = this.maxLengthOfDs(jsObj); let rpt = ''; for (let i = 0; i < maxDLength + 5; i++) { rpt += 'D'; } surroundDecimalsWith['str'] = rpt; const json = JSON.stringify(jsObj, null, '\t'); const replaceRegex = new RegExp('"?' + surroundDecimalsWith['str'] + '"?', 'g'); const json2 = json.replace(replaceRegex, ''); return json2; } maxLengthOfDs(obj) { function maxSubstringLengthStr(str) { const matches = str.match(/DDDD+/g); if (!matches) { return 0; } const ret = matches .map((substr) => { return substr.length; }) .reduce((p, c) => { return Math.max(p, c); }, 0); return ret; } function maxSubstringLength(currentMax, obj) { let ret; if (typeof (obj) === 'string') { ret = Math.max(currentMax, maxSubstringLengthStr(obj)); } else if (typeof (obj) === 'object') { ret = Object.keys(obj) .map((k) => { return Math.max(maxSubstringLengthStr(k), maxSubstringLength(currentMax, obj[k])); }) .reduce((p, c) => { return Math.max(p, c); }, currentMax); } else { ret = currentMax; } return ret; } return maxSubstringLength(0, obj); } resourceToJS(xmlObj, surroundDecimalsWith) { const typeDefinition = this.parser.parsedStructureDefinitions[xmlObj.name]; const resource = { resourceType: xmlObj.name }; if (!typeDefinition) { throw new Error('Unknown resource type: ' + xmlObj.name); } typeDefinition._properties.forEach((property) => { this.propertyToJS(xmlObj, resource, property, surroundDecimalsWith); }); return resource; } findReferenceType(relativeType) { if (!relativeType || !relativeType.startsWith('#')) { return; } const resourceType = relativeType.substring(1, relativeType.indexOf('.')); const path = relativeType.substring(resourceType.length + 2); const resourceDefinition = this.parser.parsedStructureDefinitions[resourceType]; const pathSplit = path.split('.'); if (!resourceDefinition) { throw new Error('Could not find resource definition for ' + resourceType); } let current = resourceDefinition; for (let i = 0; i < pathSplit.length; i++) { const nextPath = pathSplit[i]; current = current._properties.find((property) => property._name === nextPath); if (!current) { return; } } return JSON.parse(JSON.stringify(current)); } propertyToJS(xmlObj, obj, property, surroundDecimalsWith) { const xmlElements = []; if (xmlObj.elements) { for (const element of xmlObj.elements) { if (element.name === property._name) { xmlElements.push(element); } } } const xmlAttributes = []; if (xmlObj.attributes) { const attributeKeys = Object.keys(xmlObj.attributes); for (const attributeKey of attributeKeys) { if (attributeKey === property._name) { xmlAttributes.push({ name: attributeKey, type: 'attribute', attributes: { value: xmlObj.attributes[attributeKey] } }); } } } const xmlProperty = xmlElements.concat(xmlAttributes); if (!xmlProperty || xmlProperty.length === 0) { return; } if (property._type && property._type.indexOf('#') === 0) { const relativeType = this.findReferenceType(property._type); if (!relativeType) { throw new Error('Could not find reference to element definition ' + relativeType); } relativeType._name = property._name; relativeType._multiple = property._multiple; relativeType._required = property._required; property = relativeType; } const addExtra = (element, index) => { const hasId = element.attributes && element.attributes.id; const hasExtensions = !!(element.elements || []).find((next) => next.name === 'extension'); if (hasId || hasExtensions) { if (!obj['_' + property._name]) { obj['_' + property._name] = obj[property._name] instanceof Array ? [] : {}; } } const dest = obj['_' + property._name]; if (hasId || hasExtensions) { if (dest instanceof Array) { if (dest.length < index + 1) { for (let i = 0; i < index; i++) { if (!dest[i]) { dest[i] = null; } } } dest[index] = {}; } } if (hasId) { if (dest instanceof Array) { dest[index].id = element.attributes.id; } else { dest.id = element.attributes.id; } } if (hasExtensions) { const extensionProperty = { _name: 'extension', _type: 'Extension', _multiple: true, _required: false }; this.propertyToJS(element, dest instanceof Array ? dest[index] : dest, extensionProperty, surroundDecimalsWith); } }; const pushValue = (value, index) => { if (!value) return; switch (property._type) { case 'string': case 'base64Binary': case 'code': case 'id': case 'markdown': case 'uri': case 'url': case 'canonical': case 'oid': case 'date': case 'dateTime': case 'time': case 'instant': addExtra(value, index); if (value.attributes && value.attributes['value']) { if (obj[property._name] instanceof Array) { obj[property._name].push(value.attributes['value']); } else { obj[property._name] = value.attributes['value']; } } break; case 'decimal': addExtra(value, index); if (value.attributes['value']) { if (obj[property._name] instanceof Array) { obj[property._name].push(convertDecimal(value.attributes['value'], surroundDecimalsWith)); } else { obj[property._name] = convertDecimal(value.attributes['value'], surroundDecimalsWith); } } break; case 'boolean': addExtra(value, index); if (value.attributes['value']) { if (obj[property._name] instanceof Array) { obj[property._name].push(toBoolean(value.attributes['value'])); } else { obj[property._name] = toBoolean(value.attributes['value']); } } break; case 'integer': case 'unsignedInt': case 'positiveInt': addExtra(value, index); if (value.attributes && value.attributes['value']) { if (obj[property._name] instanceof Array) { obj[property._name].push(toNumber(value.attributes['value'])); } else { obj[property._name] = toNumber(value.attributes['value']); } } break; case 'xhtml': if (value.elements && value.elements.length > 0) { const div = convert.js2xml({ elements: [xmlHelper_1.XmlHelper.escapeInvalidCharacters(value)] }); if (obj[property._name] instanceof Array) { obj[property._name].push(div); } else { obj[property._name] = div; } } break; case 'Element': case 'BackboneElement': const newValue = {}; for (const x in property._properties) { const nextProperty = property._properties[x]; this.propertyToJS(value, newValue, nextProperty, surroundDecimalsWith); } if (obj[property._name] instanceof Array) { obj[property._name].push(newValue); } else { obj[property._name] = newValue; } break; case 'Resource': if (value.elements && value.elements.length > 0) { const elementIndex = value.elements.findIndex(e => e.type === 'element'); const newJS = this.resourceToJS(value.elements[elementIndex], surroundDecimalsWith); if (value.elements.length > 1) { const comments = value.elements.filter(e => e.type === 'comment'); if (comments && comments.length > 0) { if (!newJS['fhir_comments']) { newJS['fhir_comments'] = []; } newJS['fhir_comments'].push(...comments.map(e => e.comment)); } } if (obj[property._name] instanceof Array) { obj[property._name].push(newJS); } else { obj[property._name] = newJS; } } break; default: const nextType = this.parser.parsedStructureDefinitions[property._type]; if (!nextType) { console.log('do something'); } else { const newValue = {}; nextType._properties.forEach(nextProperty => { this.propertyToJS(value, newValue, nextProperty, surroundDecimalsWith); }); if (obj[property._name] instanceof Array) { obj[property._name].push(newValue); } else { obj[property._name] = newValue; } } break; } }; function toBoolean(value) { if (value === "true") { return true; } else if (value === "false") { return false; } else { throw new Error("Value should be a boolean but got: " + value); } } function toNumber(value) { if (/^-?\d+$/.test(value) == false) { throw new Error("Value should be a number but got: " + value); } return parseInt(value, 10); } function convertDecimal(value, surroundDecimalsWith) { if (/^-?([0]|([1-9][0-9]*))(\.[0-9]+)?$/.test(value) == false) { throw new Error("Value should be a decimal number but got: " + value); } if (surroundDecimalsWith) { return { value: value, toJSON: function () { return surroundDecimalsWith.str + value + surroundDecimalsWith.str; } }; } else { return value; } } if (property._multiple) { obj[property._name] = []; } for (let i = 0; i < xmlProperty.length; i++) { let xmlCommentElements; let nextXmlComment = null; const xmlPropertyIndex = (xmlObj.elements || []).indexOf(xmlProperty[i]); while (nextXmlComment != null || !xmlCommentElements) { if (!xmlCommentElements) { xmlCommentElements = []; } const nextIndex = xmlCommentElements.length + 1; if ((xmlPropertyIndex - nextIndex) < 0) break; nextXmlComment = xmlPropertyIndex > 0 && xmlObj.elements[xmlPropertyIndex - nextIndex].type === 'comment' ? xmlObj.elements[xmlPropertyIndex - nextIndex] : null; if (nextXmlComment) { xmlCommentElements.push(nextXmlComment); } } const extraPropertyName = '_' + property._name; pushValue(xmlProperty[i], i); if (xmlCommentElements && xmlCommentElements.length > 0) { if (constants_1.Constants.PrimitiveTypes.indexOf(property._type) >= 0) { if (property._multiple) { if (!obj[extraPropertyName]) { obj[extraPropertyName] = []; } if (!obj[extraPropertyName][i]) { obj[extraPropertyName][i] = {}; } obj[extraPropertyName][i].fhir_comments = xmlCommentElements.reverse().map(c => c.comment); } else { if (!obj[extraPropertyName]) { obj[extraPropertyName] = {}; } obj[extraPropertyName].fhir_comments = xmlCommentElements.reverse().map(c => c.comment); } } else { if (property._multiple) { if (!obj[property._name]) { obj[property._name] = []; } if (!obj[property._name][i]) { obj[property._name][i] = {}; } obj[property._name][i].fhir_comments = xmlCommentElements.reverse().map(c => c.comment); } else { if (!obj[property._name]) { obj[property._name] = {}; } obj[property._name].fhir_comments = xmlCommentElements.reverse().map(c => c.comment); } } } } } } exports.ConvertToJs = ConvertToJs; //# sourceMappingURL=convertToJs.js.map