Skip to content

Commit

Permalink
Re-organize the library
Browse files Browse the repository at this point in the history
  • Loading branch information
matteodelabre committed Sep 4, 2016
1 parent 8c6db98 commit 945c4e6
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 153 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
.nyc_output
coverage
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ node_js:
- "6"
- "5"
- "4"
- "0.12"
after_success:
- npm run coveralls
- npm run coverage && npm run coveralls
19 changes: 10 additions & 9 deletions benchmark/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
/* eslint-disable no-unused-vars */
'use strict';

require('babel-register');

// benchmarking libraries
const Benchmark = require('benchmark');
const fs = require('fs');
const path = require('path');
const failsafe = require('./require-failsafe')();

// benchmarked libraries:
// benchmarked libraries
const Saxophone = require('../dist');
const failSafe = require('./require-failsafe')();

const EasySax = failSafe.require('easysax');
const expat = failSafe.require('node-expat');
const libxmljs = failSafe.require('libxmljs');
const sax = failSafe.require('sax');
const EasySax = failsafe.require('easysax');
const expat = failsafe.require('node-expat');
const libxmljs = failsafe.require('libxmljs');
const sax = failsafe.require('sax');

failSafe.commit();
failsafe.commit();

// test file:
const xml = fs.readFileSync(path.join(__dirname, 'fixture.xml')).toString();
Expand Down
27 changes: 27 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @module Jour
*/

/**
* Create a new Saxophone object, ready
* to parse XML data
*
* @return {Saxophone} A Saxophone object
*/
const Saxophone = () => {
return Object.create(saxophonePrototype);
};

// export the factory before importing the submodules
// for circular dependencies
module.exports = Saxophone;

// load the prototype from the submodules
const saxophonePrototype = Object.assign(
{},
require('events').prototype,
require('./prototype/parse')
);

// load the static properties and methods
require('./static/attrs')(Saxophone);
82 changes: 1 addition & 81 deletions index.js → lib/prototype/parse.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
'use strict';

const EventEmitter = require('events');

/**
* Check if a character is a whitespace character according
* to the XML spec (space, carriage return, line feed or tab)
*
* @param {string} character Character to check
* @return {bool} Whether the character is whitespace or not
*/
const isWhitespace = character => character === ' ' ||
character === '\r' || character === '\n' || character === '\t';

/**
* Parse a XML stream and emit events corresponding
* to the different tokens encountered
Expand Down Expand Up @@ -213,70 +199,4 @@ const parse = function (input) {
this.emit('end');
};

/**
* Parse a string of XML attributes to a map of attribute names
* to their values
*
* @memberof Saxophone
* @param {string} input A string of XML attributes
* @throws {Error} If the string is malformed
* @return {Object} A map of attribute names to their values
*/
const parseAttrs = input => {
const attrs = {}, end = input.length;
let position = 0;

while (position < end) {
// skip all whitespace
if (isWhitespace(input[position])) {
position += 1;
continue;
}

// check that the attribute name contains valid chars
const startName = position;

while (input[position] !== '=' && position < end) {
if (isWhitespace(input[position])) {
throw new Error('Attribute names may not contain whitespace');
}

position += 1;
}

// this is XML so we need a value for the attribute
if (position === end) {
throw new Error('Expected a value for the attribute');
}

const attrName = input.slice(startName, position);
position += 1;
const startQuote = input[position];
position += 1;

if (startQuote !== '"' && startQuote !== "'") {
throw new Error('Attribute values should be quoted');
}

const endQuote = input.indexOf(startQuote, position);

if (endQuote === -1) {
throw new Error('Unclosed attribute value');
}

const attrValue = input.slice(position, endQuote);

attrs[attrName] = attrValue;
position = endQuote + 1;
}

return attrs;
};

const proto = {...EventEmitter.prototype, parse};
const Saxophone = () => {
return Object.create(proto);
};

Saxophone.parseAttrs = parseAttrs;
module.exports = Saxophone;
exports.parse = parse;
45 changes: 1 addition & 44 deletions test/index.js → lib/prototype/parse.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
'use strict';

const test = require('tape');
const uniq = require('lodash.uniq');
const tags = require('common-tags');
const Saxophone = require('..');
const Saxophone = require('../');

const expectEvents = (assert, xml, events) => {
let eventsIndex = 0;
Expand Down Expand Up @@ -134,47 +132,6 @@ test('should parse tags containing attributes', assert => {
);
});

test('should parse tag attributes', assert => {
assert.deepEqual(
Saxophone.parseAttrs(' first="one" second="two" third="three " '),
{
first: 'one',
second: 'two',
third: 'three '
}
);

assert.end();
});

test('should not parse attributes without a value', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' first');
}, /Expected a value for the attribute/);
assert.end();
});

test('should not parse invalid attribute names', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' this is an attribute="value"');
}, /Attribute names may not contain whitespace/);
assert.end();
});

test('should not parse unquoted attribute values', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' attribute=value value=invalid');
}, /Attribute values should be quoted/);
assert.end();
});

test('should not parse misquoted attribute values', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' attribute="value\'');
}, /Unclosed attribute value/);
assert.end();
});

test('should parse text nodes', assert => {
expectEvents(assert,
'<textarea> this\nis\na\r\n\ttextual\ncontent </textarea>',
Expand Down
73 changes: 73 additions & 0 deletions lib/static/attrs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Check if a character is a whitespace character according
* to the XML spec (space, carriage return, line feed or tab)
*
* @param {string} character Character to check
* @return {bool} Whether the character is whitespace or not
*/
const isWhitespace = character => character === ' ' ||
character === '\r' || character === '\n' || character === '\t';

/**
* Parse a string of XML attributes to a map of attribute names
* to their values
*
* @memberof Saxophone
* @param {string} input A string of XML attributes
* @throws {Error} If the string is malformed
* @return {Object} A map of attribute names to their values
*/
const parseAttrs = input => {
const attrs = {}, end = input.length;
let position = 0;

while (position < end) {
// skip all whitespace
if (isWhitespace(input[position])) {
position += 1;
continue;
}

// check that the attribute name contains valid chars
const startName = position;

while (input[position] !== '=' && position < end) {
if (isWhitespace(input[position])) {
throw new Error('Attribute names may not contain whitespace');
}

position += 1;
}

// this is XML so we need a value for the attribute
if (position === end) {
throw new Error('Expected a value for the attribute');
}

const attrName = input.slice(startName, position);
position += 1;
const startQuote = input[position];
position += 1;

if (startQuote !== '"' && startQuote !== "'") {
throw new Error('Attribute values should be quoted');
}

const endQuote = input.indexOf(startQuote, position);

if (endQuote === -1) {
throw new Error('Unclosed attribute value');
}

const attrValue = input.slice(position, endQuote);

attrs[attrName] = attrValue;
position = endQuote + 1;
}

return attrs;
};

module.exports = Saxophone => {
Saxophone.parseAttrs = parseAttrs;
};
43 changes: 43 additions & 0 deletions lib/static/attrs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const test = require('tape');
const Saxophone = require('../');

test('should parse tag attributes', assert => {
assert.deepEqual(
Saxophone.parseAttrs(' first="one" second="two" third="three " '),
{
first: 'one',
second: 'two',
third: 'three '
}
);

assert.end();
});

test('should not parse attributes without a value', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' first');
}, /Expected a value for the attribute/);
assert.end();
});

test('should not parse invalid attribute names', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' this is an attribute="value"');
}, /Attribute names may not contain whitespace/);
assert.end();
});

test('should not parse unquoted attribute values', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' attribute=value value=invalid');
}, /Attribute values should be quoted/);
assert.end();
});

test('should not parse misquoted attribute values', assert => {
assert.throws(() => {
Saxophone.parseAttrs(' attribute="value\'');
}, /Unclosed attribute value/);
assert.end();
});
38 changes: 20 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@
"url": "https://github.com/matteodelabre/saxophone.git"
},
"scripts": {
"build": "babel index.js test/index.js --out-dir dist",
"build": "webpack -p --progress --colors",
"prepublish": "npm run build",
"lint": "eslint .",
"pretest": "npm run build",
"test": "tape dist/test | tap-spec && npm run lint",
"precoverage": "npm run build",
"coverage": "istanbul cover -- tape dist/test",
"coveralls": "npm run coverage && cat ./coverage/lcov.info | coveralls",
"prebenchmark": "npm run build",
"benchmark": "node benchmark",
"prepublish": "npm run build"
"tape": "tape -r babel-register -r babel-polyfill 'lib/**/*.test.js'",
"test": "npm run --silent tape | faucet",
"coverage": "nyc --reporter=html --reporter=text npm --silent run tape",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"benchmark": "node benchmark"
},
"files": [
"dist"
],
"engines": {
"node": ">=4.4.0"
"node": "0.12 || 4 || 5 || 6"
},
"keywords": [
"xml",
Expand All @@ -38,16 +36,20 @@
"url": "https://github.com/matteodelabre"
},
"devDependencies": {
"babel-cli": "^6.11.4",
"babel-core": "^6.13.2",
"babel-loader": "^6.2.5",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-preset-es2015": "^6.9.0",
"benchmark": "^2.1.1",
"common-tags": "^1.3.1",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.13.2",
"babel-register": "^6.11.6",
"coveralls": "^2.11.12",
"eslint": "^3.2.2",
"istanbul": "^0.4.4",
"lodash.uniq": "^4.4.0",
"eslint": "^3.3.1",
"faucet": "0.0.1",
"nyc": "^8.1.0",
"tap-spec": "^4.1.1",
"tape": "^4.6.0"
"tape": "^4.6.0",
"webpack": "^1.13.2",
"common-tags": "^1.3.1",
"lodash.uniq": "^4.4.0"
}
}
Loading

0 comments on commit 945c4e6

Please sign in to comment.