From 732b1856e9b0b48025d91fa8cfe6bfd04852c721 Mon Sep 17 00:00:00 2001 From: au2 Date: Sat, 18 Oct 2014 21:23:46 -0400 Subject: [PATCH] interface and README updates --- README.md | 76 +++++++++++--------- index.js | 14 ++-- lib/README.md | 48 ------------- test/sample_runs/test-generator-ccda.js | 20 +++--- test/sample_runs/test-generator-ccda_new.js | 4 +- test/sample_runs/test-xml-vs-generatedxml.js | 2 +- 6 files changed, 62 insertions(+), 102 deletions(-) delete mode 100644 lib/README.md diff --git a/README.md b/README.md index ecbf4c9..60ae9f5 100644 --- a/README.md +++ b/README.md @@ -25,49 +25,53 @@ var record = bb.parseString(xmlString); // ... // get back xml as text -var updatedXmlString = bbg(record); +var updatedXmlString = bbg.generateCCD(record); ``` ## Implementation -blue-button-generate uses javascript template objects for implementation. As an example Reaction Observation object is shown +blue-button-generate uses javascript template objects for implementation. Each template in CCDA is represented with an object. As an example Reaction Observation object is shown ``` javascript var reactionObservation = exports.reactionObservation = { - "$": { + key: "observation", + attributes: { "classCode": "OBS", "moodCode": "EVN" }, - "templateId": common.templateId("2.16.840.1.113883.10.20.22.4.9"), - "id": fieldLevel.id, - "code": common.nullFlavor, - "text": fieldLevel.text, - "statusCode": common.completed, - "effectiveTime": fieldLevel.effectiveTime, - "value": { - "$": { - "xsi:type": "CD", - "@": attrLevel.code - }, - '#': 'reaction', - '+': condition.eitherKeyExists('code', 'name'), - '*': true - }, - "entryRelationship": { - "$": { - "typeCode": "SUBJ", - "inversionInd": "true" - }, - "observation": severityObservation, - "+": condition.keyExists('severity') - } + content: [ + fieldLevel.templateId("2.16.840.1.113883.10.20.22.4.9"), + fieldLevel.id, + fieldLevel.nullFlavor("code"), + fieldLevel.text(leafLevel.sameReference("reaction")), + fieldLevel.statusCodeCompleted, + fieldLevel.effectiveTime, { + key: "value", + attributes: [ + leafLevel.typeCD, + leafLevel.code + ], + dataKey: 'reaction', + existsWhen: condition.codeOrDisplayname, + required: true + }, { + key: "entryRelationship", + attributes: { + "typeCode": "SUBJ", + "inversionInd": "true" + }, + content: severityObservation, + existsWhen: condition.keyExists('severity') + } + ] }; ``` -this template is internally used with a call + +This template is internally used with a call ``` javascript -js2xml.fillUsingTemplate(xmlDoc, input, 'observation', reactionObservation) +js2xml.update(xmlDoc, input, context, reactionObservation); ``` -where `xmlDoc` is the parent xml document (Allergy Intolerance Observation) and `input` is the immediate parent of [bluebutton.js](https://github.com/blue-button/bluebutton.js) object that describes Reaction Observation +where `xmlDoc` is the parent xml document (Allergy Intolerance Observation) and `input` is the immediate parent of [bluebutton.js](https://github.com/blue-button/bluebutton.js) object that describes Reaction Observation. `context` is internally used for indices in text references. ### Motivation @@ -82,8 +86,12 @@ This approach is an alternative to direct programming or text based templates su ### Template Structure -Each alpha-numeric key in the template corresponds a statement in the CCDA specification. `$` key is used to specify attributes and `*` key is used to indicate an element is required. Values for `$` and `*` also directly come from the specification. `@` and other keys that start with `@` are used as place holder keys. Values for `@` are functions that provide both the keys and the values of the attributes. An additional key `_` is used to represent text value of an xml node. - -`#` and `+` keys relates the templates to [blue-button](https://github.com/amida-tech/blue-button) data model. Value for the key `#` identifies the key in the data model that is associated with the node. For example [blue-button](https://github.com/amida-tech/blue-button) data model uses `reaction` for values of Reaction Observations. `+` is used to specify a function that checks if the node should exists or not. For example in `reactionObservation` the Severity Observation `entryRelationship` is only created if `reaction` has a `severity` key. Required nodes that should not exist based on `+` are created with a `nullFlavor`. - -Each value in the template can either be an other template object or a function. +The following are the properties of the templates +* `key`: This is the name for the xml element. +* `attributes`: This describes the attributes of the element. `attributes` can be an object of with `key` and `value` pairs for each attribute or it can be an array of such objects. Each attribute object or can be a function with `input` argument that returns attributes. +* `text`: This is a function with `input` attribute that returns text value of the element. +* `content`: This is an array of other templates that describe the children of the element. For a single child an object can be used. +* `dataKey`: This is the property of `input` that serves as the date for the template. +* `required`: This identifies if template is required or not. If template is required and there is not value in the `input` a `nullFlavor` node is created. +* `dataTransform`: This is a function to transform the input. +* `existWhen`: This is a boolean function with `input` argument to describe it the elements should exists or not. diff --git a/index.js b/index.js index 0ff7b45..4c2ba25 100644 --- a/index.js +++ b/index.js @@ -27,13 +27,13 @@ var createContext = (function () { }; })(); -var generate = function (input) { - var data = input.data ? input.data : input; - data.identifiers = input.meta && input.meta.identifiers; - +var generate = exports.generate = function (template, input) { var context = createContext(); - - return js2xml.create(documentLevel.ccd, data, context); + return js2xml.create(documentLevel.ccd, input, context); }; -module.exports = generate; +exports.generateCCD = function (input) { + var data = input.data ? input.data : input; + data.identifiers = input.meta && input.meta.identifiers; + return generate(documentLevel.ccd, data); +}; diff --git a/lib/README.md b/lib/README.md deleted file mode 100644 index 5b69a93..0000000 --- a/lib/README.md +++ /dev/null @@ -1,48 +0,0 @@ -## CCDA Generation: Introduction -This module converts data in JSON format (originally parsed from a CCDA) back to CCDA/blue-button format. The module determines the -section template to which the JSON data belongs to, runs the data through the appropriate templater, and generates the corresponding CCDA/blue-button section. An entire CCDA document can be generated by iteravitely running the JSON data through the templaters for each section. - -The API exposes genWholeCCDA() for this purpose, which takes in CCDA data in JSON format as a parameter and converts it into CCDA/XML. - -The module uses libxmljs for its templaters and uses a JS XML DOM implementation (https://github.com/jindw/xmldom) to traverse the generated and expected XML documents for testing and compare them by node (tagName) and by attribute and value. - -## CCDA Generation: Testing -A suite of tests and a test class (test/test-generator and test/test-lib, respectively) help in verifying that the generated CCDA is accurate. Tests include: - - parsing ccda to json, generating back the ccda from json, and comparing the original and generated ccdas for differences. - - parsing, generating, and parsing again, comparing the first parsed JSON data with the second parsed JSON data for equality. - - Comparing a specific, single section of CCDA to the original specific, single section, achieving testing granularity. - - Testing the generator against the entire corpora of ccda documents at: https://github.com/chb/sample_ccdas using the internal ccda_explorer module - -The testing class/library provides methods to compare two XML/CCDA documents by recursively walking the XML document tree and comparing two documents node-by-node. Assertion-based or diff-based testing can be used with this library by setting the appropriate flags. The default settings ignore comments, whitespace, newlines, tab or text nodes. Here is an example of diff-based testing output after testing the CCDA Procedures Section: - -```` -PROCESSING PROCEDURES -Error: Generated number of child nodes different from expected (generated: 0 at lineNumber: 8, expected: 1 at lineNumber:12 -Error: Generated number of child nodes different from expected (generated: 11 at lineNumber: 10, expected: 10 at lineNumber:32 -Attributes mismatch. Different lengths: 1 attributes but expected 0 attributes @ lineNumber: 70, 94 -Error: Generated number of child nodes different from expected (generated: 10 at lineNumber: 71, expected: 11 at lineNumber:95 -Attributes mismatch. Different lengths: 1 attributes but expected 0 attributes @ lineNumber: 119, 149 - -Error: Attributes mismatch: Encountered: moodCode="EVN" but expected: moodCode="INT" @ lineNumber: 120, 150 -Error: Encountered different tagNames: Encountered but expected , lineNumber: 130, 161 -Error: Generated number of child nodes different from expected (generated: 4 at lineNumber: 151, expected: 5 at lineNumber:182 -ERRORS: 8 -```` - - - -**Alterable flags (in test-lib.js)**: - -PROMPT_TO_SKIP: If set to true, will prompt the user to either skip or not skip the failed test. - -DIFF (default): If set to true, will continue execution even upon failing a test and will output all of the errors/differences to the console. This is the default setting. - -**Alterable settings (testXML.error_settings in test-lib.js)**: - -"silence_cap": If set to true, will silence the output of capitalization errors. False by default. - -"silence_len": If set to true, will silence the output of attribute length errors (i.e. actual node has 2 attributes but expected node has 3 attributes). False by default. - -**Test suite settings**: - -TEST_CCDA_SAMPLES: uses ccda-explorer to test against sample_ccdas - -TEST_CCD: tests against one generic sample ccda - -TEST_SECTIONS: tests each section individually - - - diff --git a/test/sample_runs/test-generator-ccda.js b/test/sample_runs/test-generator-ccda.js index a303f4d..ac0f4cb 100644 --- a/test/sample_runs/test-generator-ccda.js +++ b/test/sample_runs/test-generator-ccda.js @@ -36,7 +36,7 @@ describe('parse generate parse generate', function () { expect(err.valid).to.be.true; // generate ccda - var xml = bbg(result).toString(); + var xml = bbg.generateCCD(result); // write ccda fs.writeFileSync(path.join(generatedDir, "CCD_1_generated.xml"), xml); @@ -49,7 +49,7 @@ describe('parse generate parse generate', function () { fs.writeFileSync(path.join(generatedDir, "CCD_1_generated_2.json"), JSON.stringify(result2, null, 4)); // re-generate - var xml2 = bbg(result2).toString(); + var xml2 = bbg.generateCCD(result2); fs.writeFileSync(path.join(generatedDir, "CCD_1_generated_2.xml"), xml2); delete result.errors; @@ -71,7 +71,7 @@ describe('parse generate parse generate', function () { var val = bb.validator.validateDocumentModel(result); // generate ccda - var xml = bbg(result).toString(); + var xml = bbg.generateCCD(result); // write ccda fs.writeFileSync(path.join(generatedDir, "Vitera_CCDA_SMART_Sample_generated.xml"), xml); @@ -81,7 +81,7 @@ describe('parse generate parse generate', function () { fs.writeFileSync(path.join(generatedDir, "Vitera_CCDA_SMART_Sample_generated_2.json"), JSON.stringify(result2, null, 4)); // re-generate - var xml2 = bbg(result2).toString(); + var xml2 = bbg.generateCCD(result2); fs.writeFileSync(path.join(generatedDir, "Vitera_CCDA_SMART_Sample_generated_2.xml"), xml2); delete result.errors; @@ -104,7 +104,7 @@ describe('parse generate parse generate', function () { var val = bb.validator.validateDocumentModel(result); // generate ccda - var xml = bbg(result).toString(); + var xml = bbg.generateCCD(result); // write ccda fs.writeFileSync(path.join(generatedDir, "VA_CCD_Sample_File_Version_12_5_1_generated.xml"), xml); @@ -116,7 +116,7 @@ describe('parse generate parse generate', function () { fs.writeFileSync(path.join(generatedDir, "VA_CCD_Sample_File_Version_12_5_1_generated_2.json"), JSON.stringify(result2, null, 4)); // re-generate - var xml2 = bbg(result2).toString(); + var xml2 = bbg.generateCCD(result2); fs.writeFileSync(path.join(generatedDir, "VA_CCD_Sample_File_Version_12_5_1_generated_2.xml"), xml2); delete result.errors; @@ -144,7 +144,7 @@ describe('parse generate parse generate', function () { var val = bb.validator.validateDocumentModel(result); // generate ccda - var xml = bbg(result).toString(); + var xml = bbg.generateCCD(result); // write ccda fs.writeFileSync(path.join(generatedDir, "SampleCCDDocument_generated.xml"), xml); @@ -156,7 +156,7 @@ describe('parse generate parse generate', function () { fs.writeFileSync(path.join(generatedDir, "SampleCCDDocument_generated_2.json"), JSON.stringify(result2, null, 4)); // re-generate - var xml2 = bbg(result2).toString(); + var xml2 = bbg.generateCCD(result2); fs.writeFileSync(path.join(generatedDir, "SampleCCDDocument_generated_2.xml"), xml2); delete result.errors; @@ -182,7 +182,7 @@ describe('parse generate parse generate', function () { var val = bb.validator.validateDocumentModel(result); // generate ccda - var xml = bbg(result).toString(); + var xml = bbg.generateCCD(result); // write ccda fs.writeFileSync(path.join(generatedDir, "cms_sample_generated.xml"), xml); @@ -192,7 +192,7 @@ describe('parse generate parse generate', function () { fs.writeFileSync(path.join(generatedDir, "cms_sample_generated_2.json"), JSON.stringify(result2, null, 4)); // re-generate - var xml2 = bbg(result2).toString(); + var xml2 = bbg.generateCCD(result2); fs.writeFileSync(path.join(generatedDir, "cms_sample_generated_2.xml"), xml2); delete result.errors; diff --git a/test/sample_runs/test-generator-ccda_new.js b/test/sample_runs/test-generator-ccda_new.js index da8bad8..019411f 100644 --- a/test/sample_runs/test-generator-ccda_new.js +++ b/test/sample_runs/test-generator-ccda_new.js @@ -33,7 +33,7 @@ describe('try skewed sample data from app.', function () { // generate ccda //console.log(result.demographics); - var xml = bbg(result).toString(); + var xml = bbg.generateCCD(result); // write ccda fs.writeFileSync(path.join(generatedDir, "CCD_1_generated_new.xml"), xml); @@ -44,7 +44,7 @@ describe('try skewed sample data from app.', function () { fs.writeFileSync(path.join(generatedDir, "CCD_1_generated_2_new.json"), JSON.stringify(result2, null, 4)); // re-generate - var xml2 = bbg(result2).toString(); + var xml2 = bbg.generateCCD(result2); fs.writeFileSync(path.join(generatedDir, "CCD_1_generated_2_new.xml"), xml2); delete result.errors; diff --git a/test/sample_runs/test-xml-vs-generatedxml.js b/test/sample_runs/test-xml-vs-generatedxml.js index ea5a719..7e0e037 100644 --- a/test/sample_runs/test-xml-vs-generatedxml.js +++ b/test/sample_runs/test-xml-vs-generatedxml.js @@ -64,7 +64,7 @@ describe('xml vs parse generate xml ', function () { } // generate ccda - var xmlGeneratedRaw = bbg(result).toString(); + var xmlGeneratedRaw = bbg.generateCCD(result); var mods = bbGeneratorMods; if (addlGeneratorMods) {