diff --git a/API.md b/API.md index d8157a5..0ab5b24 100644 --- a/API.md +++ b/API.md @@ -7,6 +7,10 @@ ## camaro +### `ready()` + +Initialize module. Needed to be called once before calling other methods. + ### `transform(xml, template)` Transform xml string to JSON using the given template powered by XPath where: @@ -44,6 +48,8 @@ const template = { ### `toJson(xml)` +**Not yet implemented** + Transform xml string to JSON where: - `xml` - the input xml string diff --git a/README.md b/README.md index 345a876..a2b61a5 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ The rest are pretty much vanilla XPath 1.0. For complete API documentation, please see [API.md](API.md) ```js -const { transform, prettyPrint } = require('camaro') +const { ready, transform, prettyPrint } = require('camaro') const fs = require('fs') const xml = fs.readFileSync('examples/ean.xml', 'utf-8') @@ -99,6 +99,7 @@ const template = { } ;(async function () { + await ready() const result = await transform(xml, template) console.log(result) diff --git a/index.js b/index.js index 8f844e0..ca36c3b 100644 --- a/index.js +++ b/index.js @@ -9,22 +9,22 @@ function isEmptyObject(obj) { } let cachedInstance -const instance = Module() -instance.onRuntimeInitialized = () => { - cachedInstance = instance -} function callWasmBinding(methodName, ...args) { - return new Promise((resolve) => { + if (!cachedInstance) throw new Error('camaro is not yet initialized. You need to call `ready()` first.') + return cachedInstance[methodName](...args) +} + +const ready = () => { + return new Promise((resolve, reject) => { if (!cachedInstance) { + const instance = Module() instance.onRuntimeInitialized = () => { cachedInstance = instance - const result = instance[methodName](...args) - resolve(result) + resolve() } - } else { - const result = cachedInstance[methodName](...args) - resolve(result) + } else { + resolve() } }) } @@ -76,4 +76,4 @@ async function prettyPrint(xml, opts={indentSize: 2}) { return callWasmBinding('prettyPrint', xml, opts) } -module.exports = { transform, toJson, prettyPrint } \ No newline at end of file +module.exports = { ready, transform, toJson, prettyPrint } \ No newline at end of file diff --git a/package.json b/package.json index 5129344..09490b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "camaro", - "version": "4.2.0", + "version": "5.0.0", "description": "Transforming XML to JSON using Node.js binding to native pugixml parser library", "homepage": "https://github.com/tuananh/camaro", "bugs": "https://github.com/tuananh/camaro/issues", diff --git a/test/array-in-array.test.js b/test/array-in-array.test.js index d2b7fd3..872fb88 100644 --- a/test/array-in-array.test.js +++ b/test/array-in-array.test.js @@ -1,5 +1,5 @@ const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') const xml = ` @@ -30,7 +30,7 @@ t.test('array-in-array test .// should only match nodes inside current node', as items: ['.//item', '.'] }] } - + await ready() const result = await transform(xml, template) t.equal(result.elements[0].items.length, 3, 'elements[0].items should have only 3 elements') diff --git a/test/array.test.js b/test/array.test.js index b8e8bfe..be40879 100644 --- a/test/array.test.js +++ b/test/array.test.js @@ -1,6 +1,6 @@ const fs = require('fs') const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test('array test', async t => { const xml = fs.readFileSync('examples/recipe.xml', 'utf-8') @@ -15,7 +15,7 @@ t.test('array test', async t => { } ] } - + await ready() const result = await transform(xml, recipeTemplate) t.equal(typeof result, 'object', 'result is expected to be object') t.equal(result.id, 'moco09596c01s001r002') diff --git a/test/basic.test.js b/test/basic.test.js index 2710195..3b81112 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -1,6 +1,6 @@ const fs = require('fs') const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test('basic test', async (t) => { const xml = fs.readFileSync('examples/ean.xml', 'utf-8') @@ -23,7 +23,7 @@ t.test('basic test', async (t) => { path_not_exist: '/HotelListResponse/nonExistenPath', empty_array: [] } - + await ready() const result = await transform(xml, template) t.equal(typeof result, 'object', 'result is expected to be object') t.equal(result.cache_key, '-48a4e19f:15bec159775:50eb', 'parse cache_key ok') diff --git a/test/empty-path.test.js b/test/empty-path.test.js index 110eb95..964b490 100644 --- a/test/empty-path.test.js +++ b/test/empty-path.test.js @@ -1,13 +1,12 @@ -const fs = require('fs') const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test('empty path test', async t => { const xml = 'world' const template = { empty: '' } - + await ready() const result = await transform(xml, template) t.equal(result.empty,'','empty path => empty string') t.end() diff --git a/test/function.test.js b/test/function.test.js index 22d2cfb..006ab5b 100644 --- a/test/function.test.js +++ b/test/function.test.js @@ -1,5 +1,5 @@ const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') const isWin = process.platform === 'win32' const xml = ` @@ -28,6 +28,7 @@ const xml = ` ` t.test('test function upper-case()', async (t) => { + await ready() const result = await transform(xml, { upperCase: ['//items/item', 'upper-case(.)'] }) @@ -38,6 +39,7 @@ t.test('test function upper-case()', async (t) => { }) t.test('test function lower-case()', async (t) => { + await ready() const result = await transform(xml, { lowerCase: ['//items/item', 'lower-case(.)'] }) @@ -48,6 +50,7 @@ t.test('test function lower-case()', async (t) => { }) t.test('test function title-case()', async (t) => { + await ready() const result = await transform(xml, { titleCase: ['//items/item', 'title-case(.)'] }) @@ -58,6 +61,7 @@ t.test('test function title-case()', async (t) => { }) t.test('test function title-case() unicode', async (t) => { + await ready() const result = await transform(xml, { titleCase: ['//unicode/item', 'title-case(.)'] }) @@ -68,6 +72,7 @@ t.test('test function title-case() unicode', async (t) => { }) t.test('test function title-case() upper after symbols', async (t) => { + await ready() const result = await transform(xml, { titleCase: ['//special/item', 'title-case(.)'] }) @@ -78,6 +83,7 @@ t.test('test function title-case() upper after symbols', async (t) => { }) t.test('test function camel-case()', async (t) => { + await ready() const result = await transform(xml, { camelCase: ['//items/item', 'camel-case(.)'] }) @@ -88,6 +94,7 @@ t.test('test function camel-case()', async (t) => { }) t.test('test function snake-case()', async (t) => { + await ready() const result = await transform(xml, { snakeCase: ['//items/item', 'snake-case(.)'] }) @@ -98,6 +105,7 @@ t.test('test function snake-case()', async (t) => { }) t.test('test nested function calls', async (t) => { + await ready() const result = await transform(xml, { snakeCase: ['//items/item', 'snake-case(lower-case(.))'] }) @@ -108,36 +116,42 @@ t.test('test nested function calls', async (t) => { }) t.test('test function round()', async (t) => { + await ready() const result = await transform(xml, { round: 'round(root/single)' }) t.equal(result.round, 20) t.end() }) t.test('test function floor()', async (t) => { + await ready() const result = await transform(xml, { floor: 'floor(root/single)' }) t.equal(result.floor, 20) t.end() }) t.test('test function ceiling()', async (t) => { + await ready() const result = await transform(xml, { ceiling: 'ceiling(root/single)' }) t.equal(result.ceiling, 21) t.end() }) t.test('test function sum()', async (t) => { + await ready() const result = await transform(xml, { sum: 'sum(root/number)' }) t.equal(result.sum, 30.5) t.end() }) t.test('test function count()', async (t) => { + await ready() const result = await transform(xml, { count: 'count(root/number)' }) t.equal(result.count, 2) t.end() }) t.test('test function boolean()', async (t) => { + await ready() const result = await transform(xml, { boolean: 'boolean(root/boolean = "TrUe")', boolean_false: 'boolean(root/boolean = "true")' diff --git a/test/input.test.js b/test/input.test.js index e36bdbc..c2692f3 100644 --- a/test/input.test.js +++ b/test/input.test.js @@ -1,9 +1,10 @@ const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test('test invalid xml string',async (t) => { try { - const result = await transform('', {}) + await ready() + await transform('', {}) } catch (err) { t.equal(err instanceof TypeError, true, 'should throw TypeError invalid xml string') } @@ -13,13 +14,15 @@ t.test('test invalid xml string',async (t) => { t.test('test invalid template argument',async (t) => { try { - const result = await transform('', null) + await ready() + await transform('', null) } catch (err) { t.equal(err instanceof TypeError, true, 'should throw TypeError invalid template') } try { - const result = await transform('', {}) + await ready() + await transform('', {}) } catch (err) { t.equal(err instanceof TypeError, true, 'should throw TypeError invalid template') } diff --git a/test/invalid-xpath.test.js b/test/invalid-xpath.test.js index 28d3f21..f06e55f 100644 --- a/test/invalid-xpath.test.js +++ b/test/invalid-xpath.test.js @@ -1,13 +1,13 @@ const fs = require('fs') const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test('invalid xpath test', async t => { const xml = fs.readFileSync('examples/ean.xml', 'utf-8') const template = { invalidXPath: 'concat()' } - + await ready() const result = await transform(xml, template) t.equal(result.invalidXPath, '') t.end() diff --git a/test/output.test.js b/test/output.test.js index 8f1f3d3..85f8455 100644 --- a/test/output.test.js +++ b/test/output.test.js @@ -1,15 +1,17 @@ const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test('test output is empty', async (t) => { try { - const result = await transform('Too Many Requests', { check: 'valid_xml' }) + await ready() + await transform('Too Many Requests', { check: 'valid_xml' }) } catch (err) { t.equal(err instanceof TypeError, true, 'should throw TypeError malformed xml') } try { - const result = await transform('invalid xml', { check: 'valid_xml' }) + await ready() + await transform('invalid xml', { check: 'valid_xml' }) } catch (err) { t.equal(err instanceof TypeError, true, 'should throw TypeError malformed xml') } diff --git a/test/parallel.test.js b/test/parallel.test.js new file mode 100644 index 0000000..849d4fb --- /dev/null +++ b/test/parallel.test.js @@ -0,0 +1,13 @@ +const t = require('tape') +const { ready, transform } = require('../') + +t.test('parallel test', async t => { + await ready() + const doit = () => transform('bar', { foo: 'foo' }) + let called = 0 + await doit().then(_ => { called += 1 }) + await doit().then(_ => { called += 1 }) + + t.equal(called, 2, '`called` should equal to 2') + t.end() +}) \ No newline at end of file diff --git a/test/pretty-print.test.js b/test/pretty-print.test.js index c388880..1dc92ec 100644 --- a/test/pretty-print.test.js +++ b/test/pretty-print.test.js @@ -1,9 +1,11 @@ const fs = require('fs') const t = require('tape') -const { prettyPrint } = require('../') +const { ready, prettyPrint } = require('../') + +const xml = fs.readFileSync('examples/simple.xml', 'utf-8') t.test('pretty print default indentSize', async (t) => { - const xml = fs.readFileSync('examples/simple.xml', 'utf-8') + await ready() const prettyStr = await prettyPrint(xml) t.equal(prettyStr, ` @@ -17,7 +19,7 @@ t.test('pretty print default indentSize', async (t) => { }) t.test('pretty print indentSize=4', async (t) => { - const xml = fs.readFileSync('examples/simple.xml', 'utf-8') + await ready() const prettyStr = await prettyPrint(xml, { indentSize: 4}) t.equal(prettyStr, ` diff --git a/test/raw.test.js b/test/raw.test.js index 27972a0..27945e3 100644 --- a/test/raw.test.js +++ b/test/raw.test.js @@ -1,6 +1,6 @@ const fs = require('fs') const t = require('tape') -const { transform } = require('..') +const { ready, transform } = require('..') t.test('raw() test', async t => { const xml = fs.readFileSync('examples/simple.xml', 'utf-8') @@ -10,6 +10,7 @@ t.test('raw() test', async t => { raw1: 'raw(root/items)', raw1NodeSet: 'raw(//item)' } + await ready() const result = await transform(xml, template) const rawXml = '\n\t1\n\t2\n\n' diff --git a/test/ready.test.js b/test/ready.test.js new file mode 100644 index 0000000..798e8e2 --- /dev/null +++ b/test/ready.test.js @@ -0,0 +1,16 @@ +const t = require('tape') + +// delete require cache to force module to reinitialize +delete require.cache[require.resolve('../')] + +const { transform } = require('../') + +t.test('throw not ready error if `ready()` is not called', async t => { + try { + await transform('bar', { foo: 'foo' }) + t.end() + } catch (err) { + t.equal(err.message, 'camaro is not yet initialized. You need to call `ready()` first.') + t.end() + } +}) \ No newline at end of file diff --git a/test/template-is-array.test.js b/test/template-is-array.test.js index 6c298ad..f64c4d8 100644 --- a/test/template-is-array.test.js +++ b/test/template-is-array.test.js @@ -1,8 +1,8 @@ const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') t.test( - 'camaro should be able to parse an array template too (1)', + 'camaro should be able to parse an array template', async (t) => { const xml = ` @@ -11,6 +11,7 @@ t.test( 3 ` + await ready() const result = await transform(xml, ['root/item', 'number(.)']) t.deepEqual(result, [1, 2, 3]) diff --git a/test/template-key-order.test.js b/test/template-key-order.test.js index f10d743..f53072e 100644 --- a/test/template-key-order.test.js +++ b/test/template-key-order.test.js @@ -1,5 +1,5 @@ const t = require('tape') -const { transform } = require('../') +const { ready, transform } = require('../') const xml = ` @@ -14,6 +14,7 @@ t.test('template key order test', async (t) => { 'a': 'root/last_name', 'c': 'root/middle_name' } + await ready() const output = await transform(xml, template) t.deepEqual(Object.keys(output), Object.keys(template), 'keys are in original order')