Repository: mholt/PapaParse (js)

Issue:
Either skipEmptyLines is broken--or is not used for unparse()
**Expected**
Calling unparse with skipEmptyLines: 'greedy' results in no basically empty rows.

**Actual**
Calling unparse with skipEmptyLines: 'greedy' (or true) will still have rows with no data (except commas). 

**Sample Code**
```
let csv = Papa.unparse(
      {
        fields: ['a', 'b'],
        data: [[null, ' '], [], ['1', '2']]
      },
      {
        skipEmptyLines: 'greedy',
        worker: true
      }
 );
console.log(csv);
```

**Discussion**
Am I misunderstanding something or not setting the config correctly? Is `skipEmptyLines` only for parse, perhaps? In the mean time, I suppose I can do some edge checking so I don't pass "empty" rows to unparse.... :(

Keep up the good work! I love the project. Glad I found it. Fast, clean, modern, reliable.

Pull request:
Use 'greedy' skipEmptyLines in unparse
Added support for skipEmptyLines in unparsing using both `true` and `'greedy'` values.

Resolves #553 

Patch:
diff --git a/papaparse.js b/papaparse.js
index 67f5e2e..059ccbb 100755
--- a/papaparse.js
+++ b/papaparse.js
@@ -280,6 +280,9 @@
 		/** quote character */
 		var _quoteChar = '"';
 
+		/** whether to skip empty lines */
+		var _skipEmptyLines = false;
+
 		unpackConfig();
 
 		var quoteCharRegex = new RegExp(_quoteChar, 'g');
@@ -290,9 +293,9 @@
 		if (_input instanceof Array)
 		{
 			if (!_input.length || _input[0] instanceof Array)
-				return serialize(null, _input);
+				return serialize(null, _input, _skipEmptyLines);
 			else if (typeof _input[0] === 'object')
-				return serialize(objectKeys(_input[0]), _input);
+				return serialize(objectKeys(_input[0]), _input, _skipEmptyLines);
 		}
 		else if (typeof _input === 'object')
 		{
@@ -313,7 +316,7 @@
 					_input.data = [_input.data];	// handles input like [1,2,3] or ['asdf']
 			}
 
-			return serialize(_input.fields || [], _input.data || []);
+			return serialize(_input.fields || [], _input.data || [], _skipEmptyLines);
 		}
 
 		// Default (any valid paths should return before this)
@@ -335,6 +338,10 @@
 				|| _config.quotes instanceof Array)
 				_quotes = _config.quotes;
 
+			if (typeof _config.skipEmptyLines === 'boolean'
+				|| typeof _config.skipEmptyLines === 'string')
+				_skipEmptyLines = _config.skipEmptyLines;
+
 			if (typeof _config.newline === 'string')
 				_newline = _config.newline;
 
@@ -358,7 +365,7 @@
 		}
 
 		/** The double for loop that iterates the data and writes out a CSV string including header row */
-		function serialize(fields, data)
+		function serialize(fields, data, skipEmptyLines)
 		{
 			var csv = '';
 
@@ -387,19 +394,21 @@
 			for (var row = 0; row < data.length; row++)
 			{
 				var maxCol = hasHeader ? fields.length : data[row].length;
+				var r = hasHeader ? fields : data[row];
 
-				for (var col = 0; col < maxCol; col++)
+				if (skipEmptyLines !== 'greedy' || r.join('').trim() !== '')
 				{
-					if (col > 0)
-						csv += _delimiter;
-					var colIdx = hasHeader && dataKeyedByField ? fields[col] : col;
-					csv += safe(data[row][colIdx], col);
+					for (var col = 0; col < maxCol; col++)
+					{
+						if (col > 0)
+							csv += _delimiter;
+						var colIdx = hasHeader && dataKeyedByField ? fields[col] : col;
+						csv += safe(data[row][colIdx], col);
+					}
+					if (row < data.length - 1 && (!skipEmptyLines || maxCol > 0))
+						csv += _newline;
 				}
-
-				if (row < data.length - 1)
-					csv += _newline;
 			}
-
 			return csv;
 		}
 


Test patch:
diff --git a/tests/test-cases.js b/tests/test-cases.js
index be5cc5a..deb2e65 100644
--- a/tests/test-cases.js
+++ b/tests/test-cases.js
@@ -1636,6 +1636,24 @@ var UNPARSE_TESTS = [
 		description: "Date objects are exported in its ISO representation",
 		input: [{date: new Date("2018-05-04T21:08:03.269Z"), "not a date": 16}, {date: new Date("Tue May 08 2018 08:20:22 GMT-0700 (PDT)"), "not a date": 32}],
 		expected: 'date,not a date\r\n2018-05-04T21:08:03.269Z,16\r\n2018-05-08T15:20:22.000Z,32'
+	},
+	{
+		description: "Returns empty rows when empty rows are passed and skipEmptyLines is false",
+		input: [[null, ' '], [], ['1', '2']],
+		config: {skipEmptyLines: false},
+		expected: '," "\r\n\r\n1,2'
+	},
+	{
+		description: "Returns without empty rows when skipEmptyLines is true",
+		input: [[null, ' '], [], ['1', '2']],
+		config: {skipEmptyLines: true},
+		expected: '," "\r\n1,2'
+	},
+	{
+		description: "Returns without rows with no content when skipEmptyLines is 'greedy'",
+		input: [[null, ' '], [], ['1', '2']],
+		config: {skipEmptyLines: 'greedy'},
+		expected: '1,2'
 	}
 ];