Skip to content

Commit

Permalink
Add tests for custom delimiter.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Jun 5, 2015
1 parent 043b782 commit ebf3152
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 6 deletions.
12 changes: 10 additions & 2 deletions README.md
Expand Up @@ -2,7 +2,7 @@

A parser and formatter for delimiter-separated values, most commonly comma-separated values (CSV) or tab-separated values (TSV).

To use in the browser:
To parse CSV in the browser:

```html
<script src="dsv.js"></script>
Expand All @@ -13,12 +13,20 @@ console.log(dsv.csv.parse("foo,bar\n1,2")); // [{foo: "1", bar: "2"}]
</script>
```

To use in Node.js, `npm install d3-dsv`, and then:
To parse CSV in Node.js, `npm install d3-dsv`, and then:

```js
var dsv = require("d3-dsv");

console.log(dsv.csv.parse("foo,bar\n1,2")); // [{foo: "1", bar: "2"}]
```

To define a new delimiter, use the dsv constructor:

```js
var psv = dsv.dsv("|");

console.log(psv.parse("foo|bar\n1|2")); // [{foo: "1", bar: "2"}]
```

For more usage, see [D3’s wiki page](https://github.com/mbostock/d3/wiki/CSV).
11 changes: 8 additions & 3 deletions index.js
@@ -1,5 +1,10 @@
import dsv from "./src/dsv";

export var csv = dsv(",");
export var tsv = dsv("\t");
export default dsv;
var csv = dsv(",");
var tsv = dsv("\t");

export {
csv,
tsv,
dsv
};
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "d3-dsv",
"version": "0.1.0",
"version": "0.1.1",
"description": "A parser and formatter for DSV (CSV and TSV) files.",
"keywords": [
"d3",
Expand Down
2 changes: 2 additions & 0 deletions test/data/sample.psv
@@ -0,0 +1,2 @@
Hello|World
42|"""fish"""
171 changes: 171 additions & 0 deletions test/dsv-test.js
@@ -0,0 +1,171 @@
var tape = require("tape"),
dsv = require("../"),
fs = require("fs");

var psv = dsv.dsv("|");

tape("dsv(\"|\").parse(string) returns the expected objects", function(test) {
test.deepEqual(psv.parse("a|b|c\n1|2|3\n"), [{a: "1", b: "2", c: "3"}]);
test.deepEqual(psv.parse(fs.readFileSync("test/data/sample.psv", "utf-8")), [{Hello: "42", World: "\"fish\""}]);
test.end();
});

tape("dsv(\"|\").parse(string) does not strip whitespace", function(test) {
test.deepEqual(psv.parse("a|b|c\n 1| 2|3\n"), [{a: " 1", b: " 2", c: "3"}]);
test.end();
});

tape("dsv(\"|\").parse(string) parses quoted values", function(test) {
test.deepEqual(psv.parse("a|b|c\n\"1\"|2|3"), [{a: "1", b: "2", c: "3"}]);
test.deepEqual(psv.parse("a|b|c\n\"1\"|2|3\n"), [{a: "1", b: "2", c: "3"}]);
test.end();
});

tape("dsv(\"|\").parse(string) parses quoted values with quotes", function(test) {
test.deepEqual(psv.parse("a\n\"\"\"hello\"\"\""), [{a: "\"hello\""}]);
test.end();
});

tape("dsv(\"|\").parse(string) parses quoted values with newlines", function(test) {
test.deepEqual(psv.parse("a\n\"new\nline\""), [{a: "new\nline"}]);
test.deepEqual(psv.parse("a\n\"new\rline\""), [{a: "new\rline"}]);
test.deepEqual(psv.parse("a\n\"new\r\nline\""), [{a: "new\r\nline"}]);
test.end();
});

tape("dsv(\"|\").parse(string) observes Unix, Mac and DOS newlines", function(test) {
test.deepEqual(psv.parse("a|b|c\n1|2|3\n4|5|\"6\"\n7|8|9"), [{a: "1", b: "2", c: "3"}, {a: "4", b: "5", c: "6"}, {a: "7", b: "8", c: "9"}]);
test.deepEqual(psv.parse("a|b|c\r1|2|3\r4|5|\"6\"\r7|8|9"), [{a: "1", b: "2", c: "3"}, {a: "4", b: "5", c: "6"}, {a: "7", b: "8", c: "9"}]);
test.deepEqual(psv.parse("a|b|c\r\n1|2|3\r\n4|5|\"6\"\r\n7|8|9"), [{a: "1", b: "2", c: "3"}, {a: "4", b: "5", c: "6"}, {a: "7", b: "8", c: "9"}]);
test.end();
});

tape("dsv(\"|\").parse(string, convert) returns the expected converted objects", function(test) {
function convert(row) { row.Hello = -row.Hello; return row; }
test.deepEqual(psv.parse(fs.readFileSync("test/data/sample.psv", "utf-8"), convert), [{Hello: -42, World: "\"fish\""}]);
test.deepEqual(psv.parse("a|b|c\n1|2|3\n", function(row) { return row; }), [{a: "1", b: "2", c: "3"}]);
test.end();
});

tape("dsv(\"|\").parse(string, convert) skips rows if convert returns null or undefined", function(test) {
function convert(row, i) { return [row, null, undefined, false][i]; }
test.deepEqual(psv.parse("field\n42\n\n\n\n", convert), [{field: "42"}, false]);
test.deepEqual(psv.parse("a|b|c\n1|2|3\n2|3|4", function(row) { return row.a & 1 ? null : row; }), [{a: "2", b: "3", c: "4"}]);
test.deepEqual(psv.parse("a|b|c\n1|2|3\n2|3|4", function(row) { return row.a & 1 ? undefined : row; }), [{a: "2", b: "3", c: "4"}]);
test.end();
});

tape("dsv(\"|\").parse(string, convert) invokes convert for every row in order", function(test) {
var rows = [];
psv.parse("a\n1\n2\n3\n4", function(d, i) { rows.push({d: d, i: i}); });
test.deepEqual(rows, [{d: {a: "1"}, i: 0}, {d: {a: "2"}, i: 1}, {d: {a: "3"}, i: 2}, {d: {a: "4"}, i: 3}]);
test.end();
});

tape("dsv(\"|\").parseRows(string) returns the expected array of array of string", function(test) {
test.deepEqual(psv.parseRows("a|b|c\n"), [["a", "b", "c"]]);
test.end();
});

tape("dsv(\"|\").parseRows(string) parses quoted values", function(test) {
test.deepEqual(psv.parseRows("\"1\"|2|3\n"), [["1", "2", "3"]]);
test.deepEqual(psv.parseRows("\"hello\""), [["hello"]]);
test.end();
});

tape("dsv(\"|\").parseRows(string) parses quoted values with quotes", function(test) {
test.deepEqual(psv.parseRows("\"\"\"hello\"\"\""), [["\"hello\""]]);
test.end();
});

tape("dsv(\"|\").parseRows(string) parses quoted values with newlines", function(test) {
test.deepEqual(psv.parseRows("\"new\nline\""), [["new\nline"]]);
test.deepEqual(psv.parseRows("\"new\rline\""), [["new\rline"]]);
test.deepEqual(psv.parseRows("\"new\r\nline\""), [["new\r\nline"]]);
test.end();
});

tape("dsv(\"|\").parseRows(string) parses Unix, Mac and DOS newlines", function(test) {
test.deepEqual(psv.parseRows("a|b|c\n1|2|3\n4|5|\"6\"\n7|8|9"), [["a", "b", "c"], ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]]);
test.deepEqual(psv.parseRows("a|b|c\r1|2|3\r4|5|\"6\"\r7|8|9"), [["a", "b", "c"], ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]]);
test.deepEqual(psv.parseRows("a|b|c\r\n1|2|3\r\n4|5|\"6\"\r\n7|8|9"), [["a", "b", "c"], ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]]);
test.end();
});

tape("dsv(\"|\").parseRows(string, convert) returns the expected converted array of array of string", function(test) {
function convert(row, i) { if (i) row[0] = -row[0]; return row; }
test.deepEqual(psv.parseRows(fs.readFileSync("test/data/sample.psv", "utf-8"), convert), [["Hello", "World"], [-42, "\"fish\""]]);
test.deepEqual(psv.parseRows("a|b|c\n1|2|3\n", function(row) { return row; }), [["a", "b", "c"], ["1", "2", "3"]]);
test.end();
});

tape("dsv(\"|\").parseRows(string, convert) skips rows if convert returns null or undefined", function(test) {
function convert(row, i) { return [row, null, undefined, false][i]; }
test.deepEqual(psv.parseRows("field\n42\n\n\n", convert), [["field"], false]);
test.deepEqual(psv.parseRows("a|b|c\n1|2|3\n2|3|4", function(row, i) { return i & 1 ? null : row; }), [["a", "b", "c"], ["2", "3", "4"]]);
test.deepEqual(psv.parseRows("a|b|c\n1|2|3\n2|3|4", function(row, i) { return i & 1 ? undefined : row; }), [["a", "b", "c"], ["2", "3", "4"]]);
test.end();
});

tape("dsv(\"|\").parseRows(string, convert) invokes convert for every row in order", function(test) {
var rows = [];
psv.parseRows("a\n1\n2\n3\n4", function(d, i) { rows.push({d: d, i: i}); });
test.deepEqual(rows, [{d: ["a"], i: 0}, {d: ["1"], i: 1}, {d: ["2"], i: 2}, {d: ["3"], i: 3}, {d: ["4"], i: 4}]);
test.end();
});

tape("dsv(\"|\").format(array) takes an array of objects as input", function(test) {
test.deepEqual(psv.format([{a: 1, b: 2, c: 3}]), "a|b|c\n1|2|3");
test.end();
});

tape("dsv(\"|\").format(array) escapes field names and values containing delimiters", function(test) {
test.deepEqual(psv.format([{"foo|bar": true}]), "\"foo|bar\"\ntrue");
test.deepEqual(psv.format([{field: "foo|bar"}]), "field\n\"foo|bar\"");
test.end();
});

tape("dsv(\"|\").format(array) computes the union of all fields", function(test) {
test.deepEqual(psv.format([{a: 1}, {a: 1, b: 2}, {a: 1, b: 2, c: 3}, {b: 1, c: 2}, {c: 1}]), "a|b|c\n1||\n1|2|\n1|2|3\n|1|2\n||1");
test.end();
});

tape("dsv(\"|\").format(array) orders fields by first-seen", function(test) {
test.deepEqual(psv.format([{a: 1, b: 2}, {c: 3, b: 4}, {c: 5, a: 1, b: 2}]), "a|b|c\n1|2|\n|4|3\n1|2|5");
test.end();
});

tape("dsv(\"|\").formatRows(array) takes an array of array of string as input", function(test) {
test.deepEqual(psv.formatRows([["a", "b", "c"], ["1", "2", "3"]]), "a|b|c\n1|2|3");
test.end();
});

tape("dsv(\"|\").formatRows(array) separates lines using Unix newline", function(test) {
test.deepEqual(psv.formatRows([[], []]), "\n");
test.end();
});

tape("dsv(\"|\").formatRows(array) does not strip whitespace", function(test) {
test.deepEqual(psv.formatRows([["a ", " b", "c"], ["1", "2", "3 "]]), "a | b|c\n1|2|3 ");
test.end();
});

tape("dsv(\"|\").formatRows(array) does not quote simple values", function(test) {
test.deepEqual(psv.formatRows([["a"], [1]]), "a\n1");
test.end();
});

tape("dsv(\"|\").formatRows(array) escapes double quotes", function(test) {
test.deepEqual(psv.formatRows([["\"fish\""]]), "\"\"\"fish\"\"\"");
test.end();
});

tape("dsv(\"|\").formatRows(array) escapes Unix newlines", function(test) {
test.deepEqual(psv.formatRows([["new\nline"]]), "\"new\nline\"");
test.end();
});

tape("dsv(\"|\").formatRows(array) escapes values containing delimiters", function(test) {
test.deepEqual(psv.formatRows([["oxford|tab"]]), "\"oxford|tab\"");
test.end();
});

0 comments on commit ebf3152

Please sign in to comment.