diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 3cc2295..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "rust-parser" -version = "0.1.0" -authors = ["Ricardo Barros "] - -[lib] -name = "embed" -crate-type = ["dylib"] - -[dependencies] -node2object = "0.1.0" -treexml = "0.3" -serde_json = "0.9" -libc = "0.2.20" diff --git a/README.md b/README.md index fd4385b..1e2ce50 100644 --- a/README.md +++ b/README.md @@ -26,32 +26,26 @@ const xml = ` ` -parser.parseString(xml, (err, res) => { - ... -}) +const json = await parser.parseString(xml, false) -parser.parseFile('/a/xml/file/somewhere.xml', { object: true }, (err, res) => { - ... -}) +const object = await parser.parseFile('/a/xml/file/somewhere.xml') ``` ## API -### `parseString(xmlString[, options], callback)` +### `async parseString(xmlString[, parse])` - `xmlString` - A string that represents an XML - - `options` - an optional object where: - - `object` - Parse JSON string to Object. default `false` - - `callback` - a function with the signature `function (err, result)` + - `parse` - Parse JSON string to Object. default `true` + Returns `Object` or `String` dependently on the `parse` argument value. -### `parseFile(filePath[, options], callback)` -- `filePath` - Relative or absolute path to an `.xml` file -- `options` - an optional object where: - - `object` - Parse JSON string to Object. default `false` -- `callback` - a function with the signature `function (err, result)` +### `async parseFile(filePath[, parse])` + - `filePath` - Relative or absolute path to an `.xml` file + - `parse` - Parse JSON string to Object. default `true` + Returns `Object` or `String` dependently on the `parse` argument value. -## Benchmark +## Benchmark (TO BE CLARIFIED) Results from a i7 2.2 Ghz ``` diff --git a/benchmark/index.js b/benchmark/index.js index eb9955c..626eadf 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -66,12 +66,17 @@ function runSync (fn, opts) { } async.series([ - (next) => runAsync(parserXmlToJson, { object: true }, (timeTaken) => { + (next) => { + const timeTaken = runSync( + async(opts) => {await parserXmlToJson(opts)}, + {} + ) + table.cell('Package', 'parser-xml2json (rust)') table.cell('Time taken', timeTaken) table.newRow() next() - }), + }, (next) => runAsync(xml2js, {}, (timeTaken) => { table.cell('Package', 'xml2js (js)') table.cell('Time taken', timeTaken) diff --git a/lib/index.js b/lib/index.js index a2f249f..2ef62dc 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,49 +1,27 @@ -const ffi = require('ffi') +const {parser} = require('../native') const path = require('path') const fs = require('fs') -// Import rust lib -const rustEmbedLocation = path.join(__dirname, '../target/release/libembed') -const rustLib = ffi.Library(rustEmbedLocation, { - parser: ['void', ['string', 'pointer']] -}) -function parseString (content, options, done) { - if (typeof options === 'function') { - done = options - options = {} - } +async function parseString(content, parse = true) { + const start = new Date() - let callback = ffi.Callback('void', ['int', 'string'], (err, res) => { - if (err) { - return done(new Error(res)) - } + const result = parser(content) + const finish = new Date() + console.log(`Parsed in ${(finish - start) / 1000}s`) - if (options.object) { - try { - res = JSON.parse(res) - } catch (e) { - return done(e) - } - } - - done(null, res) - }) - - rustLib.parser(content, callback) + if(result[0] !== '{') + throw new Error(result) + else + return parse ? JSON.parse(result) : result } -function parseFile (filePath, options, done) { - fs.readFile(path.resolve(filePath), 'utf8', (err, content) => { - if (err) { - return done(err) - } - - parseString(content, options, done) - }) +async function parseFile (filePath, options) { + const content = fs.readFileSync(path.resolve(filePath), 'utf8') + return await parseString(content, options) } module.exports = { - parseString, - parseFile + parseString, + parseFile, } diff --git a/native/Cargo.toml b/native/Cargo.toml new file mode 100644 index 0000000..7a4b863 --- /dev/null +++ b/native/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "parser-xml2json" +version = "0.2.0" +authors = ["Ricardo Barros ", "Alexandr Priezzhev "] +license = "MIT" +build = "build.rs" + +[lib] +name = "parser_xml2json" +crate-type = ["dylib"] + +[build-dependencies] +neon-build = "0.1.18" + +[dependencies] +neon = "0.1.18" +treexml = "0.6.1" +serde_json = "1.0.2" + +[dependencies.node2object] +# version = "0.15.*" +git = "https://github.com/priezz/node2object" +rev = "553b5683307fe3278d54d7909dc68a4622880771" diff --git a/native/artifacts.json b/native/artifacts.json new file mode 100644 index 0000000..c787904 --- /dev/null +++ b/native/artifacts.json @@ -0,0 +1 @@ +{"active":"release","targets":{"release":{"rustc":"rustc 1.21.0-nightly (599be0d18 2017-07-26)","env":{"npm_config_target":null,"npm_config_arch":null,"npm_config_target_arch":null,"npm_config_disturl":null,"npm_config_runtime":null,"npm_config_build_from_source":null,"npm_config_devdir":null}}}} \ No newline at end of file diff --git a/native/build.rs b/native/build.rs new file mode 100644 index 0000000..687a661 --- /dev/null +++ b/native/build.rs @@ -0,0 +1,7 @@ +extern crate neon_build; + +fn main() { + neon_build::setup(); // must be called in build.rs + + // add project-specific build logic here... +} diff --git a/native/index.node b/native/index.node new file mode 100644 index 0000000..7d97643 Binary files /dev/null and b/native/index.node differ diff --git a/native/src/lib.rs b/native/src/lib.rs new file mode 100644 index 0000000..1c15b18 --- /dev/null +++ b/native/src/lib.rs @@ -0,0 +1,34 @@ +#[macro_use] +extern crate neon; +use neon::vm::{Call, JsResult}; +use neon::js::JsString; +use neon::mem::Handle; + +extern crate treexml; +extern crate node2object; +extern crate serde_json; + +use std::error::Error; + + +fn parser(call: Call) -> JsResult { + let scope = call.scope; + let string: Handle = try!(try!(call.arguments.require(scope, 0)).check::()); + let tree = treexml::Document::parse(string.value().as_bytes()); + + let result = match tree { + Ok(v) => { + let dom_root = v.root.unwrap(); + serde_json::Value::Object(node2object::node2object(&dom_root)).to_string() + }, + Err(e) => String::from(e.description()) + }; + + Ok(JsString::new(scope, &result).unwrap()) +} + + +register_module!(m, { + try!(m.export("parser", parser)); + Ok(()) +}); diff --git a/package.json b/package.json index 7160857..82f12d1 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,33 @@ { "name": "parser-xml2json", - "version": "0.1.1", + "version": "0.2.0", "description": "Blazing fast XML parser to JSON written in Rust", "main": "lib/index.js", - "scripts": { - "lint": "standard || snazzy", - "test": "mocha ", - "postinstall": "cargo build --release" - }, - "keywords": [ - "xml", - "json", - "parser" - ], "repository": { "type": "git", - "url": "https://github.com/ricardofbarros/parser-xml2json.git" + "url": "https://github.com/ricardofbarros/parser-xml2json" + }, + "bugs": { + "url": "https://github.com/ricardofbarros/parser-xml2json/issues" }, + "homepage": "https://github.com/ricardofbarros/parser-xml2json", "author": { "name": "Ricardo Barros", "email": "ricardofbarros@hotmail.com" }, "license": "MIT", - "bugs": { - "url": "https://github.com/ricardofbarros/parser-xml2json/issues" + "keywords": [ + "xml", + "json", + "parser" + ], + "scripts": { + "lint": "standard || snazzy", + "test": "mocha", + "install": "neon build" }, - "homepage": "https://github.com/ricardofbarros/parser-xml2json", "dependencies": { - "ffi": "^2.2.0" + "neon-cli": "^0.1.18" }, "devDependencies": { "chai": "^3.5.0", diff --git a/src/embed.rs b/src/embed.rs deleted file mode 100644 index f18d8d2..0000000 --- a/src/embed.rs +++ /dev/null @@ -1,30 +0,0 @@ -extern crate treexml; -extern crate libc; -extern crate node2object; -extern crate serde_json; - -use libc::{c_char}; -use std::ffi::{CStr, CString}; -use std::error::Error; - -#[no_mangle] -pub unsafe extern fn parser(input: *const c_char, callback: extern fn(err: i32, res: std::ffi::CString)) { - let input_str = CStr::from_ptr(input); - let rust_str = input_str.to_str().unwrap(); - let tree = treexml::Document::parse(rust_str.as_bytes()); - - match tree { - Ok(v) => { - let dom_root = v.root.unwrap(); - let json_rep = serde_json::Value::Object(node2object::node2object(&dom_root)); - let s = json_rep.to_string(); - - let res = CString::new(s).unwrap(); - callback(0, res) - }, - Err(e) => { - let err = CString::new(String::from(e.description())).unwrap(); - callback(1, err) - } - }; -}