diff --git a/CHANGELOG.md b/CHANGELOG.md index ec77e15..e3852a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# (2020-09-03) + + +### Bug Fixes + +* always cast parameters to strings before escaping ([#10](https://github.com/softwaregroup-bg/ut-function/issues/10)) ([cb8f6e2](https://github.com/softwaregroup-bg/ut-function/commit/cb8f6e295aa443ad6369b03a30fe4bd803032ff4)) +* cbc decrypt bad value handling ([4be5b91](https://github.com/softwaregroup-bg/ut-function/commit/4be5b91ebe06a068efd956bb8d894adde9c76bb1)) +* currency path ([15d2833](https://github.com/softwaregroup-bg/ut-function/commit/15d2833688dd08a001e36aadb411434141d8984d)) +* forgotten ([293de2f](https://github.com/softwaregroup-bg/ut-function/commit/293de2f149cd5086bb1781de9ddbef524ccf24a3)) +* handle \ and special chars in template ([34d8a9e](https://github.com/softwaregroup-bg/ut-function/commit/34d8a9e890c122642e6436330c767f747f3be22c)) + + +### Features + +* add flatten ([05754d3](https://github.com/softwaregroup-bg/ut-function/commit/05754d3bee861122afcdc6fb5c50a192f5bb1125)) +* add new module - ut-function.cbc ([fc2d346](https://github.com/softwaregroup-bg/ut-function/commit/fc2d346bfef073fa2e2e157a359deb4302aefcda)) +* add ut-function.dispatch ([a06b944](https://github.com/softwaregroup-bg/ut-function/commit/a06b94438790570768ef406ead83760ae4db0451)) +* additional packages - capture-hapi, capture-request, timezone ([#3](https://github.com/softwaregroup-bg/ut-function/issues/3)) ([9214235](https://github.com/softwaregroup-bg/ut-function/commit/92142352e69cefe02f1a0e9cca69d794f662cf0d)) +* curency ([3480eef](https://github.com/softwaregroup-bg/ut-function/commit/3480eefc72a3df7f551a83ccb10b031ab9cf1e50)) +* new package ut-function.equals ([#7](https://github.com/softwaregroup-bg/ut-function/issues/7)) ([0d55ffe](https://github.com/softwaregroup-bg/ut-function/commit/0d55ffe3d9fae9a5cdc2140100e6d771b42a7231)) +* new packege ut-function.interpolate ([#8](https://github.com/softwaregroup-bg/ut-function/issues/8)) ([f7bec27](https://github.com/softwaregroup-bg/ut-function/commit/f7bec27a469fa22b6ed6fc9e3c0229672a1d518b)) +* possibility to provide function as a merge handler ([a8cf31b](https://github.com/softwaregroup-bg/ut-function/commit/a8cf31b9424afbf5de03eb25685c7931c4c97afe)) +* recursive templating over objects ([#11](https://github.com/softwaregroup-bg/ut-function/issues/11)) ([483b7e4](https://github.com/softwaregroup-bg/ut-function/commit/483b7e4cb4d50906736122daba1d89481b307733)) +* return renderer function ([e517335](https://github.com/softwaregroup-bg/ut-function/commit/e51733518beffe579bbd1151bfe56f4e52812c8b)) +* simplify package names ([684a13d](https://github.com/softwaregroup-bg/ut-function/commit/684a13dc7d48b0396047d2935540b4618060b36f)) +* unit tests launcher, templates escaping ([#9](https://github.com/softwaregroup-bg/ut-function/issues/9)) ([6a72986](https://github.com/softwaregroup-bg/ut-function/commit/6a729867794a5d9c501c450205afd70bd28ad65e)) +* ut-function.xml2json ([a02838e](https://github.com/softwaregroup-bg/ut-function/commit/a02838ec659990a7afb84246c3ea943eb16f6555)) + + + # (2020-02-25) diff --git a/packages/xml2json/index.js b/packages/xml2json/index.js index 74f70e9..b6f3aa2 100644 --- a/packages/xml2json/index.js +++ b/packages/xml2json/index.js @@ -1,28 +1,8 @@ -const flatten = require('ut-function.flatten'); -const template = require('ut-function.template'); -const set = require('lodash.set'); -const get = require('lodash.get'); -const xml2js = require('xml2js'); -const xmlParser = new xml2js.Parser({ - charkey: 'text', - mergeAttrs: true, - explicitArray: false, - tagNameProcessors: [xml2js.processors.stripPrefix] -}); +const xmlParser = require('./xmlParser').parse; module.exports = (xmlTemplate, maxDepth = 50) => { - const fn = (async() => template( - JSON.stringify( - Object - .entries(flatten(await xmlParser.parseStringPromise(xmlTemplate), maxDepth)) - .reduce((prev, [name, value]) => { - const path = value.match(/^\${(.*)}$/); - if (path) set(prev, path[1], '$' + `{ut.get(xml, '${name}')}`); - return prev; - }, {}) - ), ['xml'], {get}))(); - return async(xml, json) => { - const result = (await fn)(typeof xml === 'string' ? await xmlParser.parseStringPromise(xml) : await xml); - return json ? result : JSON.parse(result); + return async(xml, xmlTemplatePath) => { + const result = await xmlParser(xmlTemplatePath, xml); + return typeof result === 'object' ? result : JSON.parse(result); }; }; diff --git a/packages/xml2json/index.test.js b/packages/xml2json/index.test.js index fcede9d..e0c7525 100644 --- a/packages/xml2json/index.test.js +++ b/packages/xml2json/index.test.js @@ -1,22 +1,12 @@ -const sortKeys = require('sort-keys'); const path = require('path'); const tap = require('tap'); const fs = require('fs'); const parse = require('./')(fs.readFileSync(path.resolve(__dirname, 'test', 'template.xml')).toString()); +const templatePath = (path.resolve(__dirname, 'test', 'template.xml').toString()); const payload = fs.readFileSync(path.resolve(__dirname, 'test', 'payload.xml')).toString(); tap.test('parse', async assert => { - assert.matchSnapshot(sortKeys(await parse(payload)), 'parse xml string to object'); - assert.matchSnapshot(await parse(payload, true), 'parse xml string to JSON'); - assert.matchSnapshot(sortKeys(await parse({ - some: { - deep: { - nested: { - structure: 'value 1' - } - }, - veryVeryLongTagName: 'value 2' - } - })), 'extract object from parsed xml'); - assert.end(); + const wantedResult = { 'a': 'value 1', 'b': { 'c': 'value 2' }, 'd': undefined }; + assert.deepEqual(await parse(payload, templatePath), wantedResult, 'parse xml string to JSON'); + // assert.end(); }); diff --git a/packages/xml2json/package.json b/packages/xml2json/package.json index 24f03a5..3e71dfe 100644 --- a/packages/xml2json/package.json +++ b/packages/xml2json/package.json @@ -1,13 +1,14 @@ { "name": "ut-function.xml2json", - "version": "1.1.7", + "version": "1.1.8-dfa-0.0", "description": "XML to JSON converter based on template", "dependencies": { "lodash.get": "4.4.2", "lodash.set": "4.3.2", "ut-function.flatten": "^1.2.1", "ut-function.template": "^1.6.3", - "xml2js": "0.4.22" + "xml2js": "0.4.22", + "xmldom": "^0.3.0" }, "devDependencies": { "sort-keys": "2.0.0", diff --git a/packages/xml2json/xmlParser.js b/packages/xml2json/xmlParser.js new file mode 100644 index 0000000..c91ef78 --- /dev/null +++ b/packages/xml2json/xmlParser.js @@ -0,0 +1,182 @@ +const xml2js = require('xml2js'); +const fs = require('fs'); +const DOMParser = require('xmldom').DOMParser; + +function getPath(ob, p, attributes) { + if (ob.parentNode) { + p.push(ob.localName); + getPath(ob.parentNode, p, attributes); + const possibleNamesStr = ob.getAttribute('possibleNames'); + if (possibleNamesStr !== '') { + attributes.possNames[ob.localName] = possibleNamesStr.split(','); + } + const explArrAttr = ob.getAttribute('explicitArray'); + if (explArrAttr !== '' && explArrAttr === 'true') { + attributes.explicitArray = true; + } + } +} + +function removeNilValues(ob) { + if (typeof (ob) === 'object') { + const obKeys = Object.keys(ob); + for (const k in obKeys) { + if (typeof (obKeys[k]) === 'string' && ob[obKeys[k]]['i:nil'] && ob[obKeys[k]]['i:nil'] === 'true') { + return ''; + } + } + } + return ob; +} + +function getMaping(ob, fieldMapings) { + if (ob.childNodes) { + const keys = Object.keys(ob.childNodes); + keys.forEach(function(pName, i) { + if (pName !== 'length') { + let ob1 = ob.childNodes[pName]; + if (ob1.nodeName === 'include') { + const tmplPath = ob1.getAttribute('templatePath'); + if (tmplPath) { + const templString = fs.readFileSync(require.resolve(tmplPath)).toString(); + const templDoc = new DOMParser().parseFromString(templString); + ob.replaceChild(templDoc.firstChild, ob1); + ob1 = ob.childNodes[i]; + } + } + getMaping(ob1, fieldMapings); + } + }); + } else { + const nodeValue = ob.nodeValue; + const pat = /^\$\{(.*)}$/; + const res = nodeValue.match(pat); + if (res && res[1]) { + const path = []; + const attributes = { possNames: {}, explicitArray: false }; + getPath(ob.parentNode, path, attributes); + path.reverse(); + fieldMapings.push({ path: path, fldName: res[1], possblNames: attributes.possNames, explicitArray: attributes.explicitArray }); + } + } +} + +function extractValue(obbVal, path, pNames, resultObj, possNames, explicitArray) { + const lstName = pNames[(pNames.length - 1)]; + + for (let i = 1; i <= path.length; i++) { + if (obbVal && !Array.isArray(obbVal) && explicitArray && i === (path.length - 1)) { + obbVal = [obbVal]; + } + if (Array.isArray(obbVal)) { + const childName = path.length === i ? lstName : (pNames.length === 1 ? 'data' : pNames[0]); + resultObj[childName] = resultObj[childName] || []; + + if ((path.length - 1) === i || path.length === i) { + obbVal.forEach(function(ar, ix) { + // let val = ar[path[i]] ? ar[path[i]] : ar; + if (typeof (ar[path[i]]) !== 'undefined' || (typeof (path[i]) === 'undefined' && typeof (ar) === 'string')) { + // we enter here when there is such property in result object OR when result object i actualy array of string values + const val = removeNilValues(typeof (path[i]) !== 'undefined' ? ar[path[i]] : ar); + if (path.length === i) { + resultObj[childName].push(val); + } else { + if (resultObj[childName][ix]) { + resultObj[childName][ix][lstName] = val; + } else { + const vl = {}; + vl[lstName] = val; + resultObj[childName].push(vl); + } + } + } + }); + } else { + // todo: ...extract array sub objects + // let chOb = extractValue(ar[path[i]], path, pName); + // res.push(chOb); + } + + return resultObj; + } else if (typeof (obbVal) === 'object') { + let foundPn = false; + if (possNames[path[i]]) { + const posNames = possNames[path[i]]; + for (const p in posNames) { + if (typeof (posNames[p]) === 'string') { + if (posNames[p] === '*') { + obbVal = obbVal[Object.keys(obbVal)[0]]; + foundPn = true; + break; + } + if (obbVal[posNames[p]]) { + obbVal = obbVal[posNames[p]]; + foundPn = true; + break; + } + } + } + } + if (!foundPn) { + if (i === path.length) { + resultObj[lstName] = obbVal[path[i]]; + } else { + obbVal = obbVal[path[i]]; + } + } + } else { + // text + if (pNames.length === 1) { + resultObj[lstName] = obbVal; + } else { + if (!obbVal && explicitArray) { + resultObj[pNames[0]] = resultObj[pNames[0]] || []; + } else { + resultObj[pNames[0]] = resultObj[pNames[0]] || {}; + resultObj[pNames[0]][lstName] = obbVal; + } + } + } + } +} + +function tagNameProcessor(name) { + const r = name.split(':'); + return r[r.length - 1]; +} + +// ---------------------------------------------- +module.exports = { + parse: function(templName, data) { + const templFile = fs.readFileSync(require.resolve(templName)).toString(); + const doc = new DOMParser().parseFromString(templFile); + const fieldMapings = []; + getMaping(doc, fieldMapings); + + return new Promise(function(resolve, reject) { + xml2js.parseString(data, { + trim: true, + ignoreAttrs: false, + explicitArray: false, + tagNameProcessors: [tagNameProcessor] + }, + function(err, reslt) { + if (err) { + reject(err); + return; + } + const prsRes = {}; + for (const k in fieldMapings) { + if (typeof (fieldMapings[k]) === 'object') { + const path = fieldMapings[k].path; + const propNames = fieldMapings[k].fldName.split('.'); + const obbVal = reslt[path[0]]; + extractValue(obbVal, path, propNames, prsRes, fieldMapings[k].possblNames, fieldMapings[k].explicitArray); + } + } + // console.log(reslt); + resolve(prsRes); + }); + }); + } +};