UNPKG

aft-web-services

Version:

Automated Functional Testing (AFT) module for testing web services over HTTP and HTTPS

187 lines (181 loc) 6.67 kB
import { JsonObject } from "aft-core"; import { ICanHaveKeyValue } from "./ican-have-key-value"; import { XmlDocument, XmlNode, XmlCDataNode, XmlElement, XmlTextNode } from "xmldoc"; export class XML { /** * converts a passed in XML object into a Javascript object * * XML to object deserialisation will use the following rules: * - element names become property names * - attributes become properties preceeded by an `@` symbol inside the element object * - element text content is rendered in a special property named `keyValue` * * *Ex:* * ```xml * <html> * <image src="./foo/bar/baz.jpg" /> * <hr /> * <span style="color:#808080" class="hidden rounded"> * This is coloured * </span> * </html> * ``` * will become: * ```json * { * "html": { * "image": { * "@src": "./foo/bar/baz.jpg", * }, * "hr": {}, * "span": { * "@style": "color:#808080", * "@class": "hidden rounded", * "keyValue": "This is coloured" * } * } * } * ``` * @param oXMLParent an XML Document, Element or DocumentFragment to be converted * into a Javascript Object * @returns a Javascript Object representation of the passed in XML object */ static toObject<T extends JsonObject>(oXMLParent: Document | Element | DocumentFragment): T { const vResult: ICanHaveKeyValue = {}; let nLength: number = 0; let sCollectedTxt: string = ""; let oXMLParentElement: Element = oXMLParent as Element; if (oXMLParentElement?.hasAttributes && oXMLParentElement?.attributes && oXMLParentElement?.hasAttributes()) { for (nLength; nLength < oXMLParentElement.attributes.length; nLength++) { const oAttrib = oXMLParentElement.attributes.item(nLength); vResult["@" + oAttrib.name.toLowerCase()] = XML._parseText(oAttrib.value.trim()); } } if (oXMLParent.hasChildNodes()) { for (let oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) { oNode = oXMLParent.childNodes.item(nItem); if (oNode.nodeType === 4) { /* nodeType is "CDATASection" (4) */ sCollectedTxt += oNode.nodeValue; } else if (oNode.nodeType === 3) { /* nodeType is "Text" (3) */ sCollectedTxt += oNode.nodeValue.trim(); } else if (oNode.nodeType === 1 && !oNode.prefix) { /* nodeType is "Element" (1) */ sProp = oNode.nodeName.toLowerCase(); vContent = XML.toObject(oNode); if (vResult.hasOwnProperty(sProp)) { if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; } vResult[sProp].push(vContent); } else { vResult[sProp] = vContent; nLength++; } } } } if (sCollectedTxt) { vResult.keyValue = XML._parseText(sCollectedTxt); } return vResult as T; } /** * converts the passed in XML string into a Javascript object * * XML to object deserialisation will use the following rules: * - element names become property names * - attributes become properties preceeded by an `@` symbol inside the element object * - element text content is rendered in a special property named `keyValue` * * *Ex:* * ```xml * <html> * <image src="./foo/bar/baz.jpg" /> * <hr /> * <span style="color:#808080" class="hidden rounded"> * This is coloured * </span> * </html> * ``` * will become: * ```json * { * "html": { * "image": { * "@src": "./foo/bar/baz.jpg", * }, * "hr": {}, * "span": { * "@style": "color:#808080", * "@class": "hidden rounded", * "keyValue": "This is coloured" * } * } * } * ``` * @param xml an XML string to be converted into a Javascript object * @returns a Javascript object representing the passed in XML string */ static fromString<T extends JsonObject>(xml: string): T { const xmlDoc = new XmlDocument(xml); const obj = {}; XML._fromXmlNode(xmlDoc, obj); return obj as T; } private static _parseText(sValue: any) { if (/^\s*$/.test(sValue)) { return null; } if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === "true"; } if (isFinite(sValue)) { return parseFloat(sValue); } if (isFinite(Date.parse(sValue))) { return new Date(sValue); } return sValue; } private static _fromXmlNode(xml: XmlNode, current: JsonObject): void { switch (xml.type) { case 'element': XML._fromElement(xml, current); break; case 'cdata': current['keyValue'] = XML._parseText((xml as XmlCDataNode).cdata); break; case 'text': current['keyValue'] = XML._parseText((xml as XmlTextNode).text); break; case 'comment': default: /* ignore */ break; } } private static _fromElement(element: XmlElement, parent: JsonObject): void { if (element.name) { const n = element.name; parent[n] = {}; if (element.attr) { const attributeKeys = Object.keys(element.attr) if (attributeKeys.length) { for (const aKey of attributeKeys) { parent[n][`@${aKey}`] = XML._parseText(element.attr[aKey].trim()); } } } if (element.val) { const v = element.val; parent[n]['keyValue'] = XML._parseText(v); } if (element.children?.length) { for (const child of element.children) { XML._fromXmlNode(child, parent[n]); } } } } }