From f0a0e85d41bf5f6521e9f7a708c496bbe497ebfd Mon Sep 17 00:00:00 2001 From: Doug Martin Date: Fri, 13 Mar 2015 09:22:02 -0500 Subject: [PATCH] v0.6.0 * Removed try catch from emit to allow bubbling up of errors to process, if one is thrown #93 * This also fixed issue #92 where a loop was entered when `this.emit("error")` was called. * Added new tests --- History.md | 6 + docs-md/coverage.html | 622 - docs/History.html | 8 + lib/parser/parser_stream.js | 32 +- package.json | 2 +- .../{issue68.csv => issue68-invalid.tsv} | 2 +- test/assets/issue68.tsv | 20001 ++++++++++++++++ test/assets/issue93.csv | 20001 ++++++++++++++++ test/issues.test.js | 89 +- 9 files changed, 40105 insertions(+), 658 deletions(-) delete mode 100644 docs-md/coverage.html rename test/assets/{issue68.csv => issue68-invalid.tsv} (99%) create mode 100644 test/assets/issue68.tsv create mode 100644 test/assets/issue93.csv diff --git a/History.md b/History.md index 243f48a3..f1dff75d 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +# v0.6.0 + +* Removed try catch from emit to allow bubbling up of errors to process, if one is thrown [#93](https://github.com/C2FO/fast-csv/issues/93) + * This also fixed issue [#92](https://github.com/C2FO/fast-csv/issues/92) where a loop was entered when `this.emit("error")` was called. +* Added new tests + # v0.5.7 * Strict record handling [#53](https://github.com/C2FO/fast-csv/pull/53) - [@derjust](https://github.com/derjust) diff --git a/docs-md/coverage.html b/docs-md/coverage.html deleted file mode 100644 index d3bfc6eb..00000000 --- a/docs-md/coverage.html +++ /dev/null @@ -1,622 +0,0 @@ - - - - - -Test Coverage - - - -
- - - - - -
-
Overview
-
-
- Coverage98.90 - SLOC323 - LOC91 - Missed1 -
-
-
-
- -
- - - - - -
-
index.js
-
-
- Coverage98.90 - SLOC323 - LOC91 - Missed1 -
-
-
  1. /**
  2. -
  3. * @projectName fast-csv
  4. -
  5. * @github https://github.com/C2FO/fast-csv
  6. -
  7. * @includeDoc [Test Coverage] [../docs-md/coverage.html]
  8. -
  9. * @header
  10. -
  11. *
  12. -
  13. * [![build status](https://secure.travis-ci.org/C2FO/fast-csv.png)](http://travis-ci.org/C2FO/fast-csv)
  14. -
  15. * # Fast-csv
  16. -
  17. *
  18. -
  19. * This is a library is aimed at providing fast CSV parsing. It accomplishes this by not handling some of the more complex
  20. -
  21. * edge cases such as multi line rows. However it does support escaped values, embedded commas, double and single quotes.
  22. -
  23. *
  24. -
  25. * ## Installation
  26. -
  27. *
  28. -
  29. * `npm install fast-csv`
  30. -
  31. *
  32. -
  33. * ## Usage
  34. -
  35. *
  36. -
  37. * To parse a file.
  38. -
  39. *
  40. -
  41. * ```javascript
  42. -
  43. * var csv = require("fast-csv");
  44. -
  45. *
  46. -
  47. * csv("my.csv")
  48. -
  49. * .on("data", function(data){
  50. -
  51. * console.log(data):
  52. -
  53. * })
  54. -
  55. * .on("end", function(){
  56. -
  57. * console.log("done");
  58. -
  59. * })
  60. -
  61. * .parse();
  62. -
  63. * ```
  64. -
  65. *
  66. -
  67. * You may also parse a stream.
  68. -
  69. *
  70. -
  71. * ```javascript
  72. -
  73. * var stream = fs.createReadStream("my.csv");
  74. -
  75. *
  76. -
  77. * csv(stream)
  78. -
  79. * .on("data", function(data){
  80. -
  81. * console.log(data):
  82. -
  83. * })
  84. -
  85. * .on("end", function(){
  86. -
  87. * console.log("done");
  88. -
  89. * })
  90. -
  91. * .parse();
  92. -
  93. *
  94. -
  95. * ```
  96. -
  97. *
  98. -
  99. * If you expect the first line your csv to headers you may pass a headers option in. Setting the headers option will
  100. -
  101. * cause change each row to an object rather than an array.
  102. -
  103. *
  104. -
  105. * ```javascript
  106. -
  107. * var stream = fs.createReadStream("my.csv");
  108. -
  109. *
  110. -
  111. * csv(stream, {headers : true})
  112. -
  113. * .on("data", function(data){
  114. -
  115. * console.log(data):
  116. -
  117. * })
  118. -
  119. * .on("end", function(){
  120. -
  121. * console.log("done");
  122. -
  123. * })
  124. -
  125. * .parse();
  126. -
  127. *
  128. -
  129. * ```
  130. -
  131. *
  132. -
  133. * You may alternatively pass an array of header names which must match the order of each column in the csv, otherwise
  134. -
  135. * the data columns will not match.
  136. -
  137. *
  138. -
  139. * ```javascript
  140. -
  141. * var stream = fs.createReadStream("my.csv");
  142. -
  143. *
  144. -
  145. * csv(stream, {headers : ["firstName", "lastName", "address"]})
  146. -
  147. * .on("data", function(data){
  148. -
  149. * console.log(data):
  150. -
  151. * })
  152. -
  153. * .on("end", function(){
  154. -
  155. * console.log("done");
  156. -
  157. * })
  158. -
  159. * .parse();
  160. -
  161. *
  162. -
  163. * ```
  164. -
  165. *
  166. -
  167. * If your data may include empty rows, the sort Excel might include at the end of the file for instance, you can ignore
  168. -
  169. * these by including the `ignoreEmpty` option.
  170. -
  171. *
  172. -
  173. * Any rows consisting of nothing but empty strings and/or commas will be skipped, without emitting a 'data' or 'error' event.
  174. -
  175. *
  176. -
  177. * ```javascript
  178. -
  179. * var stream = fs.createReadStream("my.csv");
  180. -
  181. *
  182. -
  183. * csv(stream, {ignoreEmpty: true})
  184. -
  185. * .on("data", function(data){
  186. -
  187. * console.log(data):
  188. -
  189. * })
  190. -
  191. * .on("end", function(){
  192. -
  193. * console.log("done");
  194. -
  195. * })
  196. -
  197. * .parse();
  198. -
  199. *
  200. -
  201. * ```
  202. -
  203. *
  204. -
  205. * ### Validating
  206. -
  207. *
  208. -
  209. * You can validate each row in the csv by providing a validate handler. If a row is invalid then a `data-invalid` event
  210. -
  211. * will be emitted with the row and the index.
  212. -
  213. *
  214. -
  215. * ```javascript
  216. -
  217. * var stream = fs.createReadStream("my.csv");
  218. -
  219. *
  220. -
  221. * csv(stream, {headers : true})
  222. -
  223. * .validate(function(data){
  224. -
  225. * return data.age < 50; //all persons must be under the age of 50
  226. -
  227. * })
  228. -
  229. * .on("data-invalid", function(data){
  230. -
  231. * //do something with invalid row
  232. -
  233. * })
  234. -
  235. * .on("data", function(data){
  236. -
  237. * console.log(data):
  238. -
  239. * })
  240. -
  241. * .on("end", function(){
  242. -
  243. * console.log("done");
  244. -
  245. * })
  246. -
  247. * .parse();
  248. -
  249. *
  250. -
  251. * ```
  252. -
  253. *
  254. -
  255. * ### Transforming
  256. -
  257. *
  258. -
  259. * You can transform data by providing in a transform function. What is returned from the transform function will
  260. -
  261. * be provided to validate and emitted as a row.
  262. -
  263. *
  264. -
  265. * ```javascript
  266. -
  267. * var stream = fs.createReadStream("my.csv");
  268. -
  269. *
  270. -
  271. * csv(stream)
  272. -
  273. * .transform(function(data){
  274. -
  275. * return data.reverse(); //reverse each row.
  276. -
  277. * })
  278. -
  279. * .on("data", function(data){
  280. -
  281. * console.log(data):
  282. -
  283. * })
  284. -
  285. * .on("end", function(){
  286. -
  287. * console.log("done");
  288. -
  289. * })
  290. -
  291. * .parse();
  292. -
  293. *
  294. -
  295. * ```
  296. -
  297. * @footer
  298. -
  299. * ## License
  300. -
  301. *
  302. -
  303. * MIT <https://github.com/C2FO/fast-csv/raw/master/LICENSE>
  304. -
  305. *
  306. -
  307. * ##Meta
  308. -
  309. * * Code: `git clone git://github.com/C2FO/fast-csv.git`
  310. -
  311. * * Website: <http://c2fo.com>
  312. -
  313. * * Twitter: [http://twitter.com/c2fo](http://twitter.com/c2fo) - 877.465.4045
  314. -
  315. */
  316. -
  317. -
  318. 1var fs = require("fs"),
  319. -
  320. _ = require("extended")().register(require("is-extended")).register(require("object-extended")),
  321. -
  322. EventEmitter = require("events").EventEmitter,
  323. -
  324. util = require("util"),
  325. -
  326. out = process.stdout,
  327. -
  328. Stream = require("stream").Stream;
  329. -
  330. -
  331. -
  332. 1var VALIDATE = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
  333. -
  334. 1var EMPTY = /^\s*(?:''|"")?\s*(?:,\s*(?:''|"")?\s*)*$/;
  335. -
  336. 1var VALUE = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
  337. -
  338. 1var LINE_SPLIT = /[\r\n|\r|\n]/;
  339. -
  340. -
  341. 1function Parser(options) {
  342. -
  343. 14 EventEmitter.call(this);
  344. -
  345. 14 this._parsedHeaders = false;
  346. -
  347. 14 this._rowCount = 0;
  348. -
  349. 14 options = options || {};
  350. -
  351. 14 this._headers = options.headers;
  352. -
  353. 14 this._ignoreEmpty = options.ignoreEmpty;
  354. -
  355. }
  356. -
  357. 1util.inherits(Parser, EventEmitter);
  358. -
  359. -
  360. 1_(Parser).extend({
  361. -
  362. __parseLine: function __parseLineData(data, index, ignore) {
  363. -
  364. // Return NULL if input string is not well formed CSV string.
  365. -
  366. //regexp based on http://stackoverflow.com/a/8497474
  367. -
  368. 108 var ignoreEmpty = this._ignoreEmpty;
  369. -
  370. 108 if (!VALIDATE.test(data)) {
  371. -
  372. 1 this.emit("error", new Error("Invalid row " + data));
  373. -
  374. 1 return null;
  375. -
  376. 107 } else if (_.isBoolean(ignoreEmpty) && ignoreEmpty && EMPTY.test(data)) {
  377. -
  378. 5 return null;
  379. -
  380. }
  381. -
  382. 102 var a = [];
  383. -
  384. 102 data.replace(VALUE, function lineReplace(m0, m1, m2, m3) {
  385. -
  386. 353 var item;
  387. -
  388. 353 if (m1 !== undefined) {
  389. -
  390. 9 item = m1.replace(/\\'/g, "'");
  391. -
  392. 344 } else if (m2 !== undefined) {
  393. -
  394. 35 item = m2.replace(/\\"/g, '"');
  395. -
  396. 309 } else if (m3 !== undefined) {
  397. -
  398. 309 item = m3;
  399. -
  400. }
  401. -
  402. 353 a.push(item);
  403. -
  404. 353 return ''; // Return empty string.
  405. -
  406. }.bind(this));
  407. -
  408. // Handle special case of empty last value.
  409. -
  410. 102 if (/,\s*$/.test(data)) {
  411. -
  412. 10 a.push('');
  413. -
  414. }
  415. -
  416. 102 if (!ignore) {
  417. -
  418. 93 a = this._transform(a, index);
  419. -
  420. 93 if (this._validate(a, index)) {
  421. -
  422. 89 return a;
  423. -
  424. } else {
  425. -
  426. 4 this.emit("data-invalid", a, index);
  427. -
  428. }
  429. -
  430. } else {
  431. -
  432. 9 return a;
  433. -
  434. }
  435. -
  436. },
  437. -
  438. -
  439. _parse: function _parseLine(data) {
  440. -
  441. 22 var row, parseLine = this.__parseLine.bind(this), emitRow = this.emit.bind(this, "data"), count = 0;
  442. -
  443. 22 if (!this._parsedHeaders) {
  444. -
  445. 11 var headers = this._headers;
  446. -
  447. 11 if (_.isBoolean(headers) && headers) {
  448. -
  449. 9 headers = parseLine(data.shift(), 0, true);
  450. -
  451. }
  452. -
  453. 11 if (_.isArray(headers)) {
  454. -
  455. 10 var headersLength = headers.length,
  456. -
  457. orig = this._transform.bind(this);
  458. -
  459. 10 this._transform = function (data, index) {
  460. -
  461. 84 var ret = {};
  462. -
  463. 84 for (var i = 0; i < headersLength; i++) {
  464. -
  465. 296 ret[headers[i]] = data[i];
  466. -
  467. }
  468. -
  469. 84 return orig(ret, index);
  470. -
  471. };
  472. -
  473. }
  474. -
  475. 11 this._parsedHeaders = true;
  476. -
  477. }
  478. -
  479. 22 for (var i = 0, l = data.length; i < l; i++) {
  480. -
  481. 99 row = data[i];
  482. -
  483. 99 if (row) {
  484. -
  485. 99 var dataRow = parseLine(row, count);
  486. -
  487. 99 if (dataRow) {
  488. -
  489. 89 count = this._rowCount++;
  490. -
  491. 89 emitRow(dataRow, count);
  492. -
  493. }
  494. -
  495. }
  496. -
  497. }
  498. -
  499. },
  500. -
  501. from: function _from(from) {
  502. -
  503. 14 this.__from = from;
  504. -
  505. 14 return this;
  506. -
  507. },
  508. -
  509. parse: function _parse(from) {
  510. -
  511. 12 from = from || this.__from;
  512. -
  513. 12 if (_.isString(from)) {
  514. -
  515. 10 from = fs.createReadStream(from);
  516. -
  517. 10 from.on("end", function () {
  518. -
  519. 10 from.destroy();
  520. -
  521. });
  522. -
  523. }
  524. -
  525. 12 if (_.isObject(from) && from instanceof Stream) {
  526. -
  527. 11 var lines = "", parse = this._parse.bind(this), end = this.emit.bind(this, "end");
  528. -
  529. 11 from.on("data", function streamOnData(data) {
  530. -
  531. 11 var lineData = (lines + data).trim().split(LINE_SPLIT);
  532. -
  533. 11 if (lineData.length > 1) {
  534. -
  535. 11 lines = lineData.pop();
  536. -
  537. 11 parse(lineData);
  538. -
  539. } else {
  540. -
  541. 0 lines += data;
  542. -
  543. }
  544. -
  545. });
  546. -
  547. 11 from.on("end", function streamOnEnd() {
  548. -
  549. 11 parse(lines.split(LINE_SPLIT));
  550. -
  551. 11 end(this._rowCount);
  552. -
  553. }.bind(this));
  554. -
  555. } else {
  556. -
  557. 1 throw new TypeError("fast-csv.Parser#parse from must be a path or ReadableStream");
  558. -
  559. }
  560. -
  561. 11 return this;
  562. -
  563. },
  564. -
  565. -
  566. _validate: function (data, index) {
  567. -
  568. 84 return true;
  569. -
  570. },
  571. -
  572. _transform: function (data, index) {
  573. -
  574. 84 return data;
  575. -
  576. },
  577. -
  578. validate: function (cb) {
  579. -
  580. 2 if (!_.isFunction(cb)) {
  581. -
  582. 1 throw new TypeError("fast-csv.Parser#validate requires a function");
  583. -
  584. }
  585. -
  586. 1 this._validate = cb;
  587. -
  588. 1 return this;
  589. -
  590. },
  591. -
  592. transform: function (cb) {
  593. -
  594. 2 if (!_.isFunction(cb)) {
  595. -
  596. 1 throw new TypeError("fast-csv.Parser#transform requires a function");
  597. -
  598. }
  599. -
  600. 1 this._transform = cb;
  601. -
  602. 1 return this;
  603. -
  604. }
  605. -
  606. });
  607. -
  608. -
  609. /**
  610. -
  611. * Entry point to `fast-csv`. `fast-csv` does not store rows its proccesses each row and emits it. If you wish to save
  612. -
  613. * every row into an array you must store them yourself by using the `data` event. Once all rows are done processing
  614. -
  615. * the `end` event is emitted.
  616. -
  617. *
  618. -
  619. * Invoke to parse a csv file.
  620. -
  621. *
  622. -
  623. * @name fast-csv
  624. -
  625. * @param location
  626. -
  627. * @param options
  628. -
  629. * @return {*}
  630. -
  631. */
  632. -
  633. 1module.exports = function parse(location, options) {
  634. -
  635. 14 return new Parser(options).from(location);
  636. -
  637. };
-
- - - - - \ No newline at end of file diff --git a/docs/History.html b/docs/History.html index 3d023426..b5913a3d 100644 --- a/docs/History.html +++ b/docs/History.html @@ -176,6 +176,14 @@ +

v0.6.0

+

v0.5.7