├── .gitignore ├── .npmignore ├── .travis.yml ├── History.md ├── Makefile ├── Readme.md ├── index.js ├── lib └── express-csv.js ├── package.json ├── support └── benchmark.js └── test ├── index.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - "0.10" 6 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.6.0 / 2013-03-14 3 | ================== 4 | 5 | * Added support for Node 0.10 6 | * Added Node 0.10 to travis 7 | 8 | 0.5.0 / 2012-11-01 9 | ================== 10 | 11 | * Support serializing an array of objects 12 | * Added travis.yml 13 | 14 | 0.4.1 / 2012-06-26 15 | ================== 16 | 17 | * Added support for Node 0.8.0 18 | 19 | 0.4.0 / 2012-06-17 20 | ================== 21 | 22 | * Avoid extending `http.ServerResponse.prototype` with Express 3.x 23 | 24 | 0.3.1 / 2012-04-20 25 | ================== 26 | 27 | * Fixed broken csv output when use `preventCast` option. 28 | 29 | 0.3.0 / 2012-03-28 30 | ================== 31 | 32 | * Added option `ignoreNullOrUndefined`. 33 | * Added test. 34 | 35 | 0.2.1 / 2012-03-28 36 | ================== 37 | 38 | * Adding clause setting undefined columns to an empty string. [mblackshaw] 39 | * Added `Makefile`. 40 | * Added test. 41 | * Added benchmark. 42 | 43 | 0.2.0 / 2012-03-21 44 | ================== 45 | 46 | * Add option `preventCast`. 47 | 48 | 0.1.1 / 2012-03-18 49 | ================= 50 | 51 | * Fixed `repository` in package.json 52 | 53 | 0.1.0 / 2012-03-18 54 | ================== 55 | 56 | * Added test 57 | 58 | 0.0.2 / 2012-03-17 59 | ================== 60 | 61 | * Add option `separator`. Default is `,`. 62 | 63 | 0.0.1 / 2012-03-17 64 | ================== 65 | 66 | * Initial release. 67 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | npm test 3 | 4 | benchmark: 5 | support/benchmark.js 6 | 7 | publish: 8 | npm test && npm publish 9 | 10 | .PHONY: test benchmark publish 11 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [![build status](https://secure.travis-ci.org/nulltask/express-csv.png)](http://travis-ci.org/nulltask/express-csv) 2 | # Express CSV 3 | 4 | ``` 5 | _____ ____ ______ __ 6 | | ____|_ ___ __ _ __ ___ ___ ___ / ___/ ___\ \ / / 7 | | _| \ \/ / '_ \| '__/ _ \/ __/ __| | | \___ \\ \ / / 8 | | |___ > <| |_) | | | __/\__ \__ \ | |___ ___) |\ V / 9 | |_____/_/\_\ .__/|_| \___||___/___/ \____|____/ \_/ 10 | |_| 11 | ``` 12 | 13 | Express CSV provides response CSV easily to [Express](http://expressjs.com/). 14 | 15 | ## Installation 16 | 17 | npm: 18 | 19 | $ npm install express-csv 20 | 21 | ## Usage 22 | 23 | Example: 24 | 25 | ```js 26 | var express = require('express') 27 | , csv = require('express-csv') 28 | , app = module.exports = express.createServer(); 29 | 30 | app.get('/', function(req, res) { 31 | res.csv([ 32 | ["a", "b", "c"] 33 | , ["d", "e", "f"] 34 | ]); 35 | }); 36 | 37 | app.listen(3000); 38 | ``` 39 | 40 | Response: 41 | 42 | ``` 43 | $ curl --verbose http://127.0.0.1:3000/ 44 | * About to connect() to 127.0.0.1 port 3000 (#0) 45 | * Trying 127.0.0.1... connected 46 | * Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0) 47 | > GET / HTTP/1.1 48 | > User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5 49 | > Host: 127.0.0.1:3000 50 | > Accept: */* 51 | > 52 | < HTTP/1.1 200 OK 53 | < X-Powered-By: Express 54 | < Content-Type: text/csv; charset=utf-8 55 | < Content-Length: 26 56 | < Connection: keep-alive 57 | < 58 | "a","b","c" 59 | "d","e","f" 60 | * Connection #0 to host 127.0.0.1 left intact 61 | * Closing connection #0 62 | ``` 63 | 64 | Alternatively, you can also pass an array of objects to be serialized, in which case the object's 65 | properties will be iterated over. E.g.: 66 | 67 | ```js 68 | res.csv([ { name: "joe", id: 1 }] 69 | //=> "joe", 1 70 | ``` 71 | 72 | ## License 73 | 74 | The MIT License 75 | 76 | Copyright (c) 2012 Seiya Konno 77 | 78 | Permission is hereby granted, free of charge, to any person obtaining 79 | a copy of this software and associated documentation files (the 80 | 'Software'), to deal in the Software without restriction, including 81 | without limitation the rights to use, copy, modify, merge, publish, 82 | distribute, sublicense, and/or sell copies of the Software, and to 83 | permit persons to whom the Software is furnished to do so, subject to 84 | the following conditions: 85 | 86 | The above copyright notice and this permission notice shall be 87 | included in all copies or substantial portions of the Software. 88 | 89 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 90 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 91 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 92 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 93 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 94 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 95 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/express-csv'); 2 | -------------------------------------------------------------------------------- /lib/express-csv.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * express-csv 3 | * Copyright 2011 Seiya Konno 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var http = require('http') 12 | , express = require('express') 13 | , res = express.response || http.ServerResponse.prototype; 14 | 15 | /** 16 | * Import package information. 17 | */ 18 | 19 | var package = require('../package'); 20 | 21 | /** 22 | * Library version. 23 | */ 24 | 25 | exports.version = package.version; 26 | 27 | /** 28 | * CSV separator 29 | */ 30 | 31 | exports.separator = ','; 32 | 33 | /** 34 | * Prevent Excel's casting. 35 | */ 36 | 37 | exports.preventCast = false; 38 | 39 | /** 40 | * Ignore `null` or `undefined` 41 | */ 42 | 43 | exports.ignoreNullOrUndefined = true; 44 | 45 | /** 46 | * Escape CSV field 47 | * 48 | * @param {Mixed} field 49 | * @return {String} 50 | * @api private 51 | */ 52 | 53 | function escape(field) { 54 | if (exports.ignoreNullOrUndefined && field == undefined) { 55 | return ''; 56 | } 57 | if (exports.preventCast) { 58 | return '="' + String(field).replace(/\"/g, '""') + '"'; 59 | } 60 | return '"' + String(field).replace(/\"/g, '""') + '"'; 61 | } 62 | 63 | /** 64 | * Convert an object to an array of property values. 65 | * 66 | * Example: 67 | * objToArray({ name: "john", id: 1 }) 68 | * // => [ "john", 1 ] 69 | * 70 | * @param {Object} obj The object to convert. 71 | * @return {Array} The array of object properties. 72 | * @api private 73 | */ 74 | 75 | function objToArray(obj) { 76 | var result = []; 77 | for (var prop in obj) { 78 | if (obj.hasOwnProperty(prop)) { 79 | result.push(obj[prop]); 80 | } 81 | } 82 | return result; 83 | } 84 | 85 | /** 86 | * Send CSV response with `obj`, optional `headers`, and optional `status`. 87 | * 88 | * @param {Array} obj 89 | * @param {Object|Number} headers or status 90 | * @param {Number} status 91 | * @return {ServerResponse} 92 | * @api public 93 | */ 94 | 95 | res.csv = function(obj, headers, status) { 96 | var body = ''; 97 | 98 | this.charset = this.charset || 'utf-8'; 99 | this.header('Content-Type', 'text/csv'); 100 | 101 | obj.forEach(function(item) { 102 | if (!(item instanceof Array)) item = objToArray(item); 103 | body += item.map(escape).join(exports.separator) + '\r\n'; 104 | }); 105 | 106 | return this.send(body, headers, status); 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-csv" 3 | , "description": "express-csv provides response csv easily to express." 4 | , "version": "0.6.0" 5 | , "author": "Seiya Konno " 6 | , "contributors": [ 7 | { "name": "Matthew Blackshaw", "web": "http://mattblackshaw.com" } 8 | , { "name": "Craig McDonald", "web": "http://thrackle.com" } 9 | ] 10 | , "devDependencies": { 11 | "mocha": "*" 12 | , "should": "*" 13 | , "superagent": "*" 14 | , "express": "2" 15 | } 16 | , "keywords": ["express", "csv"] 17 | , "repository": "git://github.com/nulltask/express-csv.git" 18 | , "main": "index" 19 | , "engines": { 20 | "node": "0.6 || 0.8 || 0.10" 21 | } 22 | , "scripts": { 23 | "test": "mocha" 24 | , "prepublish": "make test" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /support/benchmark.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var express = require('express') 4 | , request = require('superagent') 5 | , csv = require('../') 6 | , app = express.createServer() 7 | , data = []; 8 | 9 | for (var i = 0; i < 1000; ++i) { 10 | var record = []; 11 | for (var j = 0; j < 100; ++j) { 12 | record.push(j); 13 | } 14 | data.push(record); 15 | } 16 | 17 | app.get('/', function(req, res) { 18 | res.csv(data); 19 | }); 20 | 21 | app.listen(8383); 22 | 23 | function benchmark(time) { 24 | var url = 'http://127.0.0.1:8383/' 25 | , label = 'benchmark - ' + time + 'times' 26 | , fn = function() { 27 | --time || !function() { 28 | console.timeEnd(label); 29 | process.exit(0); 30 | }(); 31 | }; 32 | 33 | console.time(label); 34 | for (var i = 0, len = time; i < len; ++i) { 35 | request(url).end(fn); 36 | } 37 | } 38 | 39 | benchmark(100); 40 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , request = require('superagent') 3 | , csv = require('../') 4 | , app = express.createServer(); 5 | 6 | app.get('/test/1', function(req, res) { 7 | res.csv([ 8 | ['a', 'b', 'c'] 9 | , ['d', 'e', 'f'] 10 | ]); 11 | }); 12 | 13 | app.get('/test/2', function(req, res) { 14 | res.csv([ 15 | [ 'a', 'b', null ] 16 | ]); 17 | }); 18 | 19 | app.get('/test/3', function(req, res) { 20 | res.csv([ 21 | [ 'a', 'b', undefined ] 22 | ]); 23 | }); 24 | 25 | app.get('/test/objectArray', function(req, res) { 26 | res.csv([ 27 | { stringProp: "a", nullProp: null, undefinedProp: undefined }, 28 | { stringProp: "b", nullProp: null, undefinedProp: undefined } 29 | ]); 30 | }); 31 | 32 | app.listen(8383); 33 | 34 | describe('express-csv', function() { 35 | it('should expose .version', function() { 36 | csv.version.should.be.match(/[0-9]+\.[0-9]+\.[0-9]+/); 37 | }); 38 | 39 | it('should expose .separator', function() { 40 | csv.separator.should.be.a('string'); 41 | }); 42 | 43 | it('should expose .preventCast', function() { 44 | csv.preventCast.should.be.a('boolean'); 45 | }); 46 | 47 | it('should expose .ignoreNullOrUndefined', function() { 48 | csv.ignoreNullOrUndefined.should.be.a('boolean'); 49 | }); 50 | 51 | it('should extend res.csv', function() { 52 | if (express.version.match(/^2\.[0-9]+\.[0-9]+$/)) { 53 | // express 2.x 54 | require('http').ServerResponse.prototype.csv.should.be.a('function'); 55 | } else { 56 | // express 3.x 57 | require('express').response.csv.should.be.a('function'); 58 | } 59 | }); 60 | }); 61 | 62 | describe('res.csv()', function() { 63 | 64 | describe('when given an array of objects', function() { 65 | describe('and ignoreNullOrUndefined is true', function() { 66 | var rows; 67 | 68 | beforeEach(function(done) { 69 | csv.ignoreNullOrUndefined = true; 70 | request 71 | .get('http://127.0.0.1:8383/test/objectArray') 72 | .end(function(res) { 73 | rows = res.text.split("\r\n"); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should include values that exist', function() { 79 | rows[0].split(",")[0].should.equal('"a"'); 80 | rows[1].split(",")[0].should.equal('"b"'); 81 | }); 82 | 83 | it('should exclude null', function() { 84 | rows[0].split(",")[1].should.equal(''); 85 | rows[0].split(",")[2].should.equal(''); 86 | }); 87 | 88 | it('should exclude undefined', function() { 89 | rows[0].split(",")[2].should.equal(''); 90 | rows[1].split(",")[2].should.equal(''); 91 | }); 92 | }); 93 | 94 | describe('and ignoreNullOrUndefined is false', function() { 95 | var rows; 96 | 97 | beforeEach(function(done) { 98 | csv.ignoreNullOrUndefined = false; 99 | request 100 | .get('http://127.0.0.1:8383/test/objectArray') 101 | .end(function(res) { 102 | rows = res.text.split("\r\n"); 103 | done(); 104 | }); 105 | }); 106 | 107 | it('should include values that exist', function() { 108 | rows[0].split(",")[0].should.equal('"a"'); 109 | rows[1].split(",")[0].should.equal('"b"'); 110 | }); 111 | 112 | it('should include null', function() { 113 | rows[0].split(",")[1].should.equal('"null"'); 114 | rows[1].split(",")[1].should.equal('"null"'); 115 | }); 116 | 117 | it('should include undefined', function() { 118 | rows[0].split(",")[2].should.equal('"undefined"'); 119 | rows[1].split(",")[2].should.equal('"undefined"'); 120 | }); 121 | }); 122 | }); 123 | 124 | it('should response csv', function(done) { 125 | request 126 | .get('http://127.0.0.1:8383/test/1') 127 | .end(function(res) { 128 | res.text.should.equal('"a","b","c"\r\n"d","e","f"\r\n'); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('should response valid content-type', function(done) { 134 | request 135 | .get('http://127.0.0.1:8383/test/1') 136 | .end(function(res) { 137 | res.headers['content-type'].should.match(/^text\/csv/); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('should response csv includes ignored null', function(done) { 143 | request 144 | .get('http://127.0.0.1:8383/test/2') 145 | .end(function(res) { 146 | res.text.should.equal('"a","b",\r\n'); 147 | done(); 148 | }); 149 | }); 150 | 151 | it('should response csv includes ignored undefined', function(done) { 152 | request 153 | .get('http://127.0.0.1:8383/test/3') 154 | .end(function(res) { 155 | res.text.should.equal('"a","b",\r\n'); 156 | done(); 157 | }); 158 | }); 159 | 160 | it('should response csv includes null', function(done) { 161 | var prevOption = csv.ignoreNullOrUndefined; 162 | csv.ignoreNullOrUndefined = false; 163 | request 164 | .get('http://127.0.0.1:8383/test/2') 165 | .end(function(res) { 166 | csv.ignoreNullOrUndefined = prevOption; 167 | res.text.should.equal('"a","b","null"\r\n'); 168 | done(); 169 | }); 170 | }); 171 | 172 | it('should response csv includes undefined', function(done) { 173 | var prevOption = csv.ignoreNullOrUndefined; 174 | csv.ignoreNullOrUndefined = false; 175 | request 176 | .get('http://127.0.0.1:8383/test/3') 177 | .end(function(res) { 178 | csv.ignoreNullOrUndefined = prevOption; 179 | res.text.should.equal('"a","b","undefined"\r\n'); 180 | done(); 181 | }); 182 | }); 183 | 184 | it('should response tsv', function(done) { 185 | var prevSeparator = csv.separator; 186 | csv.separator = '\t'; 187 | request 188 | .get('http://127.0.0.1:8383/test/1') 189 | .end(function(res) { 190 | csv.separator = prevSeparator; 191 | res.text.should.equal('"a"\t"b"\t"c"\r\n"d"\t"e"\t"f"\r\n'); 192 | done(); 193 | }); 194 | }); 195 | 196 | it('should response quoted csv', function(done) { 197 | var prevSetting = csv.preventCast; 198 | csv.preventCast = true; 199 | request 200 | .get('http://127.0.0.1:8383/test/1') 201 | .end(function(res) { 202 | csv.preventCast = prevSetting; 203 | res.text.should.equal('="a",="b",="c"\r\n="d",="e",="f"\r\n'); 204 | done(); 205 | }); 206 | }); 207 | }); 208 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --slow 20 3 | --reporter spec 4 | --------------------------------------------------------------------------------