UNPKG

bluebutton

Version:

BlueButton.js helps developers navigate complex health data with ease.

316 lines (270 loc) 8.52 kB
/* * ... */ Core.XML = (function () { /* * A function used to wrap DOM elements in an object so methods can be added * to the element object. IE8 does not allow methods to be added directly to * DOM objects. */ var wrapElement = function (el) { function wrapElementHelper(currentEl) { return { el: currentEl, template: template, content: content, tag: tag, immediateChildTag: immediateChildTag, elsByTag: elsByTag, attr: attr, boolAttr: boolAttr, val: val, isEmpty: isEmpty }; } // el is an array of elements if (el.length) { var els = []; for (var i = 0; i < el.length; i++) { els.push(wrapElementHelper(el[i])); } return els; // el is a single element } else { return wrapElementHelper(el); } }; /* * Find element by tag name, then attribute value. */ var tagAttrVal = function (el, tag, attr, value) { el = el.getElementsByTagName(tag); for (var i = 0; i < el.length; i++) { if (el[i].getAttribute(attr) === value) { return el[i]; } } }; /* * Search for a template ID, and return its parent element. * Example: * <templateId root="2.16.840.1.113883.10.20.22.2.17"/> * Can be found using: * el = dom.template('2.16.840.1.113883.10.20.22.2.17'); */ var template = function (templateId) { var el = tagAttrVal(this.el, 'templateId', 'root', templateId); if (!el) { return emptyEl(); } else { return wrapElement(el.parentNode); } }; /* * Search for a content tag by "ID", and return it as an element. * These are used in the unstructured versions of each section but * referenced from the structured version sometimes. * Example: * <content ID="UniqueNameReferencedElsewhere"/> * Can be found using: * el = dom.content('UniqueNameReferencedElsewhere'); * * We can't use `getElementById` because `ID` (the standard attribute name * in this context) is not the same attribute as `id` in XML, so there are no matches */ var content = function (contentId) { var el = tagAttrVal(this.el, 'content', 'ID', contentId); if (!el) { // check the <td> tag too, which isn't really correct but // will inevitably be used sometimes because it looks like very // normal HTML to put the data directly in a <td> el = tagAttrVal(this.el, 'td', 'ID', contentId); } if (!el) { return emptyEl(); } else { return wrapElement(el); } }; /* * Search for the first occurrence of an element by tag name. */ var tag = function (tag) { var el = this.el.getElementsByTagName(tag)[0]; if (!el) { return emptyEl(); } else { return wrapElement(el); } }; /* * Like `tag`, except it will only count a tag that is an immediate child of `this`. * This is useful for tags like "text" which A. may not be present for a given location * in every document and B. have a very different meaning depending on their positioning * * <parent> * <target></target> * </parent> * vs. * <parent> * <intermediate> * <target></target> * </intermediate> * </parent> * parent.immediateChildTag('target') will have a result in the first case but not in the second. */ var immediateChildTag = function (tag) { var els = this.el.getElementsByTagName(tag); if (!els) { return null; } for (var i = 0; i < els.length; i++) { if (els[i].parentNode === this.el) { return wrapElement(els[i]); } } return emptyEl(); }; /* * Search for all elements by tag name. */ var elsByTag = function (tag) { return wrapElement(this.el.getElementsByTagName(tag)); }; /* * Retrieve the element's attribute value. Example: * value = el.attr('displayName'); * * The browser and jsdom return "null" for empty attributes; * xmldom (which we now use because it's faster / can be explicitly * told to parse malformed XML as XML anyways), return the empty * string instead, so we fix that here. */ var attr = function (attrName) { if (!this.el) { return null; } var attrVal = this.el.getAttribute(attrName); return attrVal || null; }; /* * Wrapper for attr() for retrieving boolean attributes; * a raw call attr() will return Strings, which can be unexpected, * since the string 'false' will by truthy */ var boolAttr = function (attrName) { var rawAttr = this.attr(attrName); if (rawAttr === 'true' || rawAttr === '1') { return true; } return false; }; /* * Retrieve the element's value. For example, if the element is: * <city>Madison</city> * Use: * value = el.tag('city').val(); * * This function also knows how to retrieve the value of <reference> tags, * which can store their content in a <content> tag in a totally different * part of the document. */ var val = function () { if (!this.el) { return null; } if (!this.el.childNodes || !this.el.childNodes.length) { return null; } var textContent = this.el.textContent; // if there's no text value here and the only thing inside is a // <reference> tag, see if there's a linked <content> tag we can // get something out of if (!Core.stripWhitespace(textContent)) { var contentId; // "no text value" might mean there's just a reference tag if (this.el.childNodes.length === 1 && this.el.childNodes[0].tagName === 'reference') { contentId = this.el.childNodes[0].getAttribute('value'); // or maybe a newlines on top/above the reference tag } else if (this.el.childNodes.length === 3 && this.el.childNodes[1].tagName === 'reference') { contentId = this.el.childNodes[1].getAttribute('value'); } else { return textContent; } if (contentId && contentId[0] === '#') { contentId = contentId.slice(1); // get rid of the '#' var docRoot = wrapElement(this.el.ownerDocument); var contentTag = docRoot.content(contentId); return contentTag.val(); } } return textContent; }; /* * Creates and returns an empty DOM element with tag name "empty": * <empty></empty> */ var emptyEl = function () { var el = doc.createElement('empty'); return wrapElement(el); }; /* * Determines if the element is empty, i.e.: * <empty></empty> * This element is created by function `emptyEL`. */ var isEmpty = function () { if (this.el.tagName.toLowerCase() === 'empty') { return true; } else { return false; } }; /* * Cross-browser XML parsing supporting IE8+ and Node.js. */ var parse = function (data) { // XML data must be a string if (!data || typeof data !== "string") { console.log("BB Error: XML data is not a string"); return null; } var xml, parser; // Node if (isNode) { parser = new (xmldom.DOMParser)(); xml = parser.parseFromString(data, "text/xml"); // Browser } else { // Standard parser if (window.DOMParser) { parser = new DOMParser(); xml = parser.parseFromString(data, "text/xml"); // IE } else { try { xml = new ActiveXObject("Microsoft.XMLDOM"); xml.async = "false"; xml.loadXML(data); } catch (e) { console.log("BB ActiveX Exception: Could not parse XML"); } } } if (!xml || !xml.documentElement || xml.getElementsByTagName("parsererror").length) { console.log("BB Error: Could not parse XML"); return null; } return wrapElement(xml); }; // Establish the root object, `window` in the browser, or `global` in Node. var root = this, xmldom, isNode = false, doc = root.document; // Will be `undefined` if we're in Node // Check if we're in Node. If so, pull in `xmldom` so we can simulate the DOM. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { isNode = true; xmldom = require("xmldom"); doc = new xmldom.DOMImplementation().createDocument(); } } return { parse: parse }; })();