jenkins-bot has submitted this change and it was merged.

Change subject: Provide a FileDropHandler for CSV and TSV files
......................................................................


Provide a FileDropHandler for CSV and TSV files

Introduced the "PapaParse" library, MIT licensed.

Change-Id: Idbe32c04e129c97c30a195dedfc7f3f4139b5f36
---
M .docs/eg-iframe.html
M .jshintrc
M AUTHORS.txt
M build/modules.json
M demos/ve/desktop-dist.html
M demos/ve/desktop.html
M demos/ve/mobile-dist.html
M demos/ve/mobile.html
A lib/papaparse/LICENSE
A lib/papaparse/README.md
A lib/papaparse/papaparse.js
A src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js
M tests/index.html
13 files changed, 1,571 insertions(+), 1 deletion(-)

Approvals:
  Catrope: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/.docs/eg-iframe.html b/.docs/eg-iframe.html
index ad2fa95..8580b52 100644
--- a/.docs/eg-iframe.html
+++ b/.docs/eg-iframe.html
@@ -94,6 +94,9 @@
                <script src="../lib/jquery.uls/src/jquery.uls.data.js"></script>
                <script 
src="../lib/jquery.uls/src/jquery.uls.data.utils.js"></script>
 
+               <!-- papaparse -->
+               <script src="../lib/papaparse/papaparse.js"></script>
+
                <!-- unicodejs -->
                <script src="../lib/unicodejs/unicodejs.js"></script>
 
@@ -306,6 +309,7 @@
                <script 
src="../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
                <script src="../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
                <script 
src="../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
+               <script 
src="../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
                <script 
src="../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
                <script 
src="../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
                <script 
src="../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
diff --git a/.jshintrc b/.jshintrc
index d8ff63f..d1244dc 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -22,6 +22,7 @@
                "QUnit": false,
                "unicodeJS": false,
                "RangeFix": false,
+               "Papa": false,
                "ve": false
        }
 }
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 507cfe9..7b3c03f 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -69,6 +69,11 @@
  – MIT license
  – OOjs UI Team and other contributors
 
+PapaParse
+ – http://papaparse.com
+ – MIT license
+ – Matthew Holt and other contributors
+
 RangeFix
  – https://github.com/edg2s/rangefix
  – MIT license
diff --git a/build/modules.json b/build/modules.json
index 316f665..a92f99b 100644
--- a/build/modules.json
+++ b/build/modules.json
@@ -103,13 +103,22 @@
                        "jquery"
                ]
        },
+       "papaparse": {
+               "scripts": [
+                       "lib/papaparse/papaparse.js"
+               ],
+               "dependencies": [
+                       "jquery"
+               ]
+       },
        "baselibs": {
                "dependencies": [
                        "jquery",
                        "oojs",
                        "oojs-ui",
                        "jquery.i18n",
-                       "jquery.uls.data"
+                       "jquery.uls.data",
+                       "papaparse"
                ]
        },
        "visualEditor.base": {
@@ -332,6 +341,7 @@
                        "src/ui/dialogs/ve.ui.FragmentDialog.js",
                        "src/ui/dialogs/ve.ui.NodeDialog.js",
                        "src/ui/dialogs/ve.ui.ProgressDialog.js",
+                       "src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js",
                        
"src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js",
                        "src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js",
                        "src/ui/widgets/ve.ui.LanguageSearchWidget.js",
diff --git a/demos/ve/desktop-dist.html b/demos/ve/desktop-dist.html
index 045cc83..2b37fce 100644
--- a/demos/ve/desktop-dist.html
+++ b/demos/ve/desktop-dist.html
@@ -68,6 +68,9 @@
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.js"></script>
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.utils.js"></script>
 
+               <!-- papaparse -->
+               <script src="../../lib/papaparse/papaparse.js"></script>
+
                <!-- oojs-ui-apex -->
                <script src="../../lib/oojs-ui/oojs-ui-apex.js"></script>
 
diff --git a/demos/ve/desktop.html b/demos/ve/desktop.html
index d01afc9..6d25ae3 100644
--- a/demos/ve/desktop.html
+++ b/demos/ve/desktop.html
@@ -104,6 +104,9 @@
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.js"></script>
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.utils.js"></script>
 
+               <!-- papaparse -->
+               <script src="../../lib/papaparse/papaparse.js"></script>
+
                <!-- unicodejs -->
                <script src="../../lib/unicodejs/unicodejs.js"></script>
 
@@ -319,6 +322,7 @@
                <script 
src="../../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
                <script src="../../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
                <script 
src="../../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
+               <script 
src="../../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
                <script 
src="../../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
                <script 
src="../../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
                <script 
src="../../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
diff --git a/demos/ve/mobile-dist.html b/demos/ve/mobile-dist.html
index 6269318..0bb36e4 100644
--- a/demos/ve/mobile-dist.html
+++ b/demos/ve/mobile-dist.html
@@ -68,6 +68,9 @@
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.js"></script>
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.utils.js"></script>
 
+               <!-- papaparse -->
+               <script src="../../lib/papaparse/papaparse.js"></script>
+
                <!-- oojs-ui-mediawiki -->
                <script src="../../lib/oojs-ui/oojs-ui-mediawiki.js"></script>
 
diff --git a/demos/ve/mobile.html b/demos/ve/mobile.html
index 28a92af..b08c073 100644
--- a/demos/ve/mobile.html
+++ b/demos/ve/mobile.html
@@ -105,6 +105,9 @@
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.js"></script>
                <script 
src="../../lib/jquery.uls/src/jquery.uls.data.utils.js"></script>
 
+               <!-- papaparse -->
+               <script src="../../lib/papaparse/papaparse.js"></script>
+
                <!-- unicodejs -->
                <script src="../../lib/unicodejs/unicodejs.js"></script>
 
@@ -320,6 +323,7 @@
                <script 
src="../../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
                <script src="../../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
                <script 
src="../../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
+               <script 
src="../../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
                <script 
src="../../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
                <script 
src="../../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
                <script 
src="../../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>
diff --git a/lib/papaparse/LICENSE b/lib/papaparse/LICENSE
new file mode 100644
index 0000000..0d04dcd
--- /dev/null
+++ b/lib/papaparse/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Matthew Holt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/papaparse/README.md b/lib/papaparse/README.md
new file mode 100644
index 0000000..279d469
--- /dev/null
+++ b/lib/papaparse/README.md
@@ -0,0 +1,79 @@
+Parse CSV with Javascript
+========================================
+
+[![mholt on 
Gratipay](http://img.shields.io/badge/tips-accepted-brightgreen.svg?style=flat)](https://www.gratipay.com/mholt/)
+
+Papa Parse (formerly the jQuery Parse Plugin) is a robust and powerful CSV 
(character-separated values) parser with these features:
+
+- Easy to use
+- Parse CSV files directly (local or over the network)
+- Stream large files (even via HTTP)
+- Reverse parsing (converts JSON to CSV)
+- Auto-detect the delimiter
+- Worker threads to keep your web page reactive
+- Header row support
+- Pause, resume, abort
+- Can convert numbers and booleans to their types
+- Graceful and robust error handling
+- Minor jQuery integration to get files from `<input type="file">` elements
+
+Papa Parse has **no dependencies** - not even jQuery.
+
+
+Homepage & Demo
+----------------
+
+- [Homepage](http://papaparse.com)
+- [Demo](http://papaparse.com/demo.html)
+
+
+Papa Parse for Node
+--------------------
+
+[Rich Harris](https://github.com/Rich-Harris) forked this project to make 
**[Baby Parse](https://github.com/Rich-Harris/BabyParse)** which runs in 
Node.js environments.
+
+```bash
+$ npm install babyparse
+```
+
+[Baby Parse on npm registry](https://www.npmjs.org/package/babyparse)
+
+Use it just like Papa Parse. However:
+
+- Files are not supported; strings only
+- Some config options are unavailable:
+       - worker
+       - download
+       - encoding
+       - chunk
+
+Otherwise, Baby Parse has nearly all the same functionality as Papa Parse 3.0, 
including the `unparse()` function.
+
+
+Get Started
+-----------
+
+Use 
[papaparse.min.js](https://github.com/mholt/PapaParse/blob/master/papaparse.min.js)
 for production.
+
+For usage instructions, see the [homepage](http://papaparse.com) and, for more 
detail, the [documentation](http://papaparse.com/docs.html).
+
+
+
+Tests
+-----
+
+Papa Parse is under test. Download this repository and open `tests/tests.html` 
in your browser to run them.
+
+
+
+Contributing
+------------
+
+To discuss a new feature or ask a question, open an issue. To fix a bug, 
submit a pull request to be credited with the 
[contributors](https://github.com/mholt/PapaParse/graphs/contributors)! 
Remember, a pull request, *with test*, is best.You may also discuss on Twitter 
with 
[#PapaParse](https://twitter.com/search?q=%23PapaParse&src=typd&f=realtime) or 
directly to me, [@mholt6](https://twitter.com/mholt6).
+
+
+
+Origins
+-------
+
+Papa Parse is the result of a successful experiment by 
[SmartyStreets](http://smartystreets.com) which matured into an independent, 
fully-featured Javascript library.
diff --git a/lib/papaparse/papaparse.js b/lib/papaparse/papaparse.js
new file mode 100644
index 0000000..941dca6
--- /dev/null
+++ b/lib/papaparse/papaparse.js
@@ -0,0 +1,1323 @@
+/*
+       Papa Parse
+       v3.1.2
+       https://github.com/mholt/PapaParse
+*/
+(function(global)
+{
+       "use strict";
+
+       var IS_WORKER = !global.document, SCRIPT_PATH;
+       var workers = {}, workerIdCounter = 0;
+
+       // A configuration object from which to draw default settings
+       var DEFAULTS = {
+               delimiter: "",  // empty: auto-detect
+               header: false,
+               dynamicTyping: false,
+               preview: 0,
+               step: undefined,
+               encoding: "",   // browser should default to "UTF-8"
+               worker: false,
+               comments: false,
+               complete: undefined,
+               error: undefined,
+               download: false,
+               chunk: undefined,
+               keepEmptyRows: false
+       };
+
+       global.Papa = {};
+
+       global.Papa.parse = CsvToJson;
+       global.Papa.unparse = JsonToCsv;
+
+       global.Papa.RECORD_SEP = String.fromCharCode(30);
+       global.Papa.UNIT_SEP = String.fromCharCode(31);
+       global.Papa.BYTE_ORDER_MARK = "\ufeff";
+       global.Papa.BAD_DELIMITERS = ["\r", "\n", "\"", 
global.Papa.BYTE_ORDER_MARK];
+       global.Papa.WORKERS_SUPPORTED = !!global.Worker;
+
+       // Configurable chunk sizes for local and remote files, respectively
+       global.Papa.LocalChunkSize = 1024 * 1024 * 10;  // 10 MB
+       global.Papa.RemoteChunkSize = 1024 * 1024 * 5;  // 5 MB
+       global.Papa.DefaultDelimiter = ",";                             // Used 
if not specified and detection fails
+
+       // Exposed for testing and development only
+       global.Papa.Parser = Parser;
+       global.Papa.ParserHandle = ParserHandle;
+       global.Papa.NetworkStreamer = NetworkStreamer;
+       global.Papa.FileStreamer = FileStreamer;
+
+       if (global.jQuery)
+       {
+               var $ = global.jQuery;
+               $.fn.parse = function(options)
+               {
+                       var config = options.config || {};
+                       var queue = [];
+
+                       this.each(function(idx)
+                       {
+                               var supported = 
$(this).prop('tagName').toUpperCase() == "INPUT"
+                                                               && 
$(this).attr('type').toLowerCase() == "file"
+                                                               && 
global.FileReader;
+
+                               if (!supported || !this.files || 
this.files.length == 0)
+                                       return true;    // continue to next 
input element
+
+                               for (var i = 0; i < this.files.length; i++)
+                               {
+                                       queue.push({
+                                               file: this.files[i],
+                                               inputElem: this,
+                                               instanceConfig: $.extend({}, 
config)
+                                       });
+                               }
+                       });
+
+                       parseNextFile();        // begin parsing
+                       return this;            // maintains chainability
+
+
+                       function parseNextFile()
+                       {
+                               if (queue.length == 0)
+                                       return;
+
+                               var f = queue[0];
+
+                               if (isFunction(options.before))
+                               {
+                                       var returned = options.before(f.file, 
f.inputElem);
+
+                                       if (typeof returned === 'object')
+                                       {
+                                               if (returned.action == "abort")
+                                               {
+                                                       error("AbortError", 
f.file, f.inputElem, returned.reason);
+                                                       return; // Aborts all 
queued files immediately
+                                               }
+                                               else if (returned.action == 
"skip")
+                                               {
+                                                       fileComplete(); // 
parse the next file in the queue, if any
+                                                       return;
+                                               }
+                                               else if (typeof returned.config 
=== 'object')
+                                                       f.instanceConfig = 
$.extend(f.instanceConfig, returned.config);
+                                       }
+                                       else if (returned == "skip")
+                                       {
+                                               fileComplete(); // parse the 
next file in the queue, if any
+                                               return;
+                                       }
+                               }
+
+                               // Wrap up the user's complete callback, if 
any, so that ours also gets executed
+                               var userCompleteFunc = 
f.instanceConfig.complete;
+                               f.instanceConfig.complete = function(results)
+                               {
+                                       if (isFunction(userCompleteFunc))
+                                               userCompleteFunc(results, 
f.file, f.inputElem);
+                                       fileComplete();
+                               };
+
+                               Papa.parse(f.file, f.instanceConfig);
+                       }
+
+                       function error(name, file, elem, reason)
+                       {
+                               if (isFunction(options.error))
+                                       options.error({name: name}, file, elem, 
reason);
+                       }
+
+                       function fileComplete()
+                       {
+                               queue.splice(0, 1);
+                               parseNextFile();
+                       }
+               }
+       }
+
+
+       if (IS_WORKER)
+               global.onmessage = workerThreadReceivedMessage;
+       else if (Papa.WORKERS_SUPPORTED)
+               SCRIPT_PATH = getScriptPath();
+
+
+
+
+       function CsvToJson(_input, _config)
+       {
+               var config = IS_WORKER ? _config : 
copyAndValidateConfig(_config);
+               var useWorker = config.worker && Papa.WORKERS_SUPPORTED && 
SCRIPT_PATH;
+
+               if (useWorker)
+               {
+                       var w = newWorker();
+
+                       w.userStep = config.step;
+                       w.userChunk = config.chunk;
+                       w.userComplete = config.complete;
+                       w.userError = config.error;
+
+                       config.step = isFunction(config.step);
+                       config.chunk = isFunction(config.chunk);
+                       config.complete = isFunction(config.complete);
+                       config.error = isFunction(config.error);
+                       delete config.worker;   // prevent infinite loop
+
+                       w.postMessage({
+                               input: _input,
+                               config: config,
+                               workerId: w.id
+                       });
+               }
+               else
+               {
+                       if (typeof _input === 'string')
+                       {
+                               if (config.download)
+                               {
+                                       var streamer = new 
NetworkStreamer(config);
+                                       streamer.stream(_input);
+                               }
+                               else
+                               {
+                                       var ph = new ParserHandle(config);
+                                       var results = ph.parse(_input);
+                                       return results;
+                               }
+                       }
+                       else if (_input instanceof File)
+                       {
+                               if (config.step || config.chunk)
+                               {
+                                       var streamer = new FileStreamer(config);
+                                       streamer.stream(_input);
+                               }
+                               else
+                               {
+                                       var ph = new ParserHandle(config);
+
+                                       if (IS_WORKER)
+                                       {
+                                               var reader = new 
FileReaderSync();
+                                               var input = 
reader.readAsText(_input, config.encoding);
+                                               return ph.parse(input);
+                                       }
+                                       else
+                                       {
+                                               reader = new FileReader();
+                                               reader.onload = function(event)
+                                               {
+                                                       var ph = new 
ParserHandle(config);
+                                                       var results = 
ph.parse(event.target.result);
+                                               };
+                                               reader.onerror = function()
+                                               {
+                                                       if 
(isFunction(config.error))
+                                                               
config.error(reader.error, _input);
+                                               };
+                                               reader.readAsText(_input, 
config.encoding);
+                                       }
+                               }
+                       }
+               }
+       }
+
+
+
+
+
+
+       function JsonToCsv(_input, _config)
+       {
+               var _output = "";
+               var _fields = [];
+
+               // Default configuration
+               var _quotes = false;    // whether to surround every datum with 
quotes
+               var _delimiter = ",";   // delimiting character
+               var _newline = "\r\n";  // newline character(s)
+
+               unpackConfig();
+
+               if (typeof _input === 'string')
+                       _input = JSON.parse(_input);
+
+               if (_input instanceof Array)
+               {
+                       if (!_input.length || _input[0] instanceof Array)
+                               return serialize(null, _input);
+                       else if (typeof _input[0] === 'object')
+                               return serialize(objectKeys(_input[0]), _input);
+               }
+               else if (typeof _input === 'object')
+               {
+                       if (typeof _input.data === 'string')
+                               _input.data = JSON.parse(_input.data);
+
+                       if (_input.data instanceof Array)
+                       {
+                               if (!_input.fields)
+                                       _input.fields = _input.data[0] 
instanceof Array
+                                                                       ? 
_input.fields
+                                                                       : 
objectKeys(_input.data[0]);
+
+                               if (!(_input.data[0] instanceof Array) && 
typeof _input.data[0] !== 'object')
+                                       _input.data = [_input.data];    // 
handles input like [1,2,3] or ["asdf"]
+                       }
+
+                       return serialize(_input.fields || [], _input.data || 
[]);
+               }
+
+               // Default (any valid paths should return before this)
+               throw "exception: Unable to serialize unrecognized input";
+
+
+               function unpackConfig()
+               {
+                       if (typeof _config !== 'object')
+                               return;
+
+                       if (typeof _config.delimiter === 'string'
+                               && _config.delimiter.length == 1
+                               && 
global.Papa.BAD_DELIMITERS.indexOf(_config.delimiter) == -1)
+                       {
+                               _delimiter = _config.delimiter;
+                       }
+
+                       if (typeof _config.quotes === 'boolean'
+                               || _config.quotes instanceof Array)
+                               _quotes = _config.quotes;
+
+                       if (typeof _config.newline === 'string')
+                               _newline = _config.newline;
+               }
+
+
+               // Turns an object's keys into an array
+               function objectKeys(obj)
+               {
+                       if (typeof obj !== 'object')
+                               return [];
+                       var keys = [];
+                       for (var key in obj)
+                               keys.push(key);
+                       return keys;
+               }
+
+               // The double for loop that iterates the data and writes out a 
CSV string including header row
+               function serialize(fields, data)
+               {
+                       var csv = "";
+
+                       if (typeof fields === 'string')
+                               fields = JSON.parse(fields);
+                       if (typeof data === 'string')
+                               data = JSON.parse(data);
+
+                       var hasHeader = fields instanceof Array && 
fields.length > 0;
+                       var dataKeyedByField = !(data[0] instanceof Array);
+
+                       // If there a header row, write it first
+                       if (hasHeader)
+                       {
+                               for (var i = 0; i < fields.length; i++)
+                               {
+                                       if (i > 0)
+                                               csv += _delimiter;
+                                       csv += safe(fields[i], i);
+                               }
+                               if (data.length > 0)
+                                       csv += _newline;
+                       }
+
+                       // Then write out the data
+                       for (var row = 0; row < data.length; row++)
+                       {
+                               var maxCol = hasHeader ? fields.length : 
data[row].length;
+
+                               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)
+                                       csv += _newline;
+                       }
+
+                       return csv;
+               }
+
+               // Encloses a value around quotes if needed (makes a value safe 
for CSV insertion)
+               function safe(str, col)
+               {
+                       if (typeof str === "undefined" || str === null)
+                               return "";
+
+                       str = str.toString().replace(/"/g, '""');
+
+                       var needsQuotes = (typeof _quotes === 'boolean' && 
_quotes)
+                                                       || (_quotes instanceof 
Array && _quotes[col])
+                                                       || hasAny(str, 
global.Papa.BAD_DELIMITERS)
+                                                       || 
str.indexOf(_delimiter) > -1
+                                                       || str.charAt(0) == ' '
+                                                       || 
str.charAt(str.length - 1) == ' ';
+
+                       return needsQuotes ? '"' + str + '"' : str;
+               }
+
+               function hasAny(str, substrings)
+               {
+                       for (var i = 0; i < substrings.length; i++)
+                               if (str.indexOf(substrings[i]) > -1)
+                                       return true;
+                       return false;
+               }
+       }
+
+
+
+       // TODO: Many of the functions of NetworkStreamer and FileStreamer are 
similar or the same. Consolidate?
+       function NetworkStreamer(config)
+       {
+               config = config || {};
+               if (!config.chunkSize)
+                       config.chunkSize = Papa.RemoteChunkSize;
+
+               var start = 0, fileSize = 0;
+               var aggregate = "";
+               var partialLine = "";
+               var xhr, nextChunk;
+               var handle = new ParserHandle(copy(config));
+
+               this.stream = function(url)
+               {
+                       if (IS_WORKER)
+                       {
+                               nextChunk = function()
+                               {
+                                       readChunk();
+                                       chunkLoaded();
+                               };
+                       }
+                       else
+                       {
+                               nextChunk = function()
+                               {
+                                       readChunk();
+                               };
+                       }
+
+                       nextChunk();    // Starts streaming
+
+
+                       function readChunk()
+                       {
+                               xhr = new XMLHttpRequest();
+                               if (!IS_WORKER)
+                               {
+                                       xhr.onload = chunkLoaded;
+                                       xhr.onerror = chunkError;
+                               }
+                               xhr.open("GET", url, !IS_WORKER);
+                               if (config.step)
+                               {
+                                       var end = start + config.chunkSize - 1; 
// minus one because byte range is inclusive
+                                       if (fileSize && end > fileSize) // Hack 
around a Chrome bug: http://stackoverflow.com/q/24745095/1048862
+                                               end = fileSize;
+                                       xhr.setRequestHeader("Range", 
"bytes="+start+"-"+end);
+                               }
+                               xhr.send();
+                               if (IS_WORKER && xhr.status == 0)
+                                       chunkError();
+                               else
+                                       start += config.chunkSize;
+                       }
+
+                       function chunkLoaded()
+                       {
+                               if (xhr.readyState != 4)
+                                       return;
+
+                               if (xhr.status < 200 || xhr.status >= 400)
+                               {
+                                       chunkError();
+                                       return;
+                               }
+
+                               // Rejoin the line we likely just split in two 
by chunking the file
+                               aggregate += partialLine + xhr.responseText;
+                               partialLine = "";
+
+                               var finishedWithEntireFile = !config.step || 
start > getFileSize(xhr);
+
+                               if (!finishedWithEntireFile)
+                               {
+                                       var lastLineEnd = 
aggregate.lastIndexOf("\n");
+
+                                       if (lastLineEnd < 0)
+                                               lastLineEnd = 
aggregate.lastIndexOf("\r");
+
+                                       if (lastLineEnd > -1)
+                                       {
+                                               partialLine = 
aggregate.substring(lastLineEnd + 1);     // skip the line ending character
+                                               aggregate = 
aggregate.substring(0, lastLineEnd);
+                                       }
+                                       else
+                                       {
+                                               // For chunk sizes smaller than 
a line (a line could not fit in a single chunk)
+                                               // we simply build our 
aggregate by reading in the next chunk, until we find a newline
+                                               nextChunk();
+                                               return;
+                                       }
+                               }
+
+                               var results = handle.parse(aggregate);
+                               aggregate = "";
+
+                               if (IS_WORKER)
+                               {
+                                       global.postMessage({
+                                               results: results,
+                                               workerId: Papa.WORKER_ID,
+                                               finished: finishedWithEntireFile
+                                       });
+                               }
+                               else if (isFunction(config.chunk))
+                               {
+                                       config.chunk(results);
+                                       results = undefined;
+                               }
+
+                               if (!finishedWithEntireFile && 
!results.meta.paused)
+                                       nextChunk();
+                       }
+
+                       function chunkError()
+                       {
+                               if (isFunction(config.error))
+                                       config.error(xhr.statusText);
+                               else if (IS_WORKER && config.error)
+                               {
+                                       global.postMessage({
+                                               workerId: Papa.WORKER_ID,
+                                               error: xhr.statusText,
+                                               finished: false
+                                       });
+                               }
+                       }
+
+                       function getFileSize(xhr)
+                       {
+                               var contentRange = 
xhr.getResponseHeader("Content-Range");
+                               return 
parseInt(contentRange.substr(contentRange.lastIndexOf("/") + 1));
+                       }
+               };
+       }
+
+
+
+
+
+
+
+
+
+       function FileStreamer(config)
+       {
+               config = config || {};
+               if (!config.chunkSize)
+                       config.chunkSize = Papa.LocalChunkSize;
+
+               var start = 0;
+               var aggregate = "";
+               var partialLine = "";
+               var reader, nextChunk, slice;
+               var handle = new ParserHandle(copy(config));
+
+               // FileReader is better than FileReaderSync (even in worker) - 
see http://stackoverflow.com/q/24708649/1048862
+               // But Firefox is a pill, too - see issue #76: 
https://github.com/mholt/PapaParse/issues/76
+               var usingAsyncReader = typeof FileReader === 'function';
+
+               this.stream = function(file)
+               {
+                       var slice = file.slice || file.webkitSlice || 
file.mozSlice;
+
+                       if (usingAsyncReader)
+                       {
+                               reader = new FileReader();              // 
Preferred method of reading files, even in workers
+                               reader.onload = chunkLoaded;
+                               reader.onerror = chunkError;
+                       }
+                       else
+                               reader = new FileReaderSync();  // Hack for 
running in a web worker in Firefox
+
+                       nextChunk();    // Starts streaming
+
+                       function nextChunk()
+                       {
+                               if (start < file.size)
+                                       readChunk();
+                       }
+
+                       function readChunk()
+                       {
+                               var end = Math.min(start + config.chunkSize, 
file.size);
+                               var txt = reader.readAsText(slice.call(file, 
start, end), config.encoding);
+                               if (!usingAsyncReader)
+                                       chunkLoaded({ target: { result: txt } 
});       // mimic the async signature
+                       }
+
+                       function chunkLoaded(event)
+                       {
+                               // Very important to increment start each time 
before handling results
+                               start += config.chunkSize;
+
+                               // Rejoin the line we likely just split in two 
by chunking the file
+                               aggregate += partialLine + event.target.result;
+                               partialLine = "";
+
+                               var finishedWithEntireFile = start >= file.size;
+
+                               if (!finishedWithEntireFile)
+                               {
+                                       var lastLineEnd = 
aggregate.lastIndexOf("\n");
+
+                                       if (lastLineEnd < 0)
+                                               lastLineEnd = 
aggregate.lastIndexOf("\r");
+
+                                       if (lastLineEnd > -1)
+                                       {
+                                               partialLine = 
aggregate.substring(lastLineEnd + 1);     // skip the line ending character
+                                               aggregate = 
aggregate.substring(0, lastLineEnd);
+                                       }
+                                       else
+                                       {
+                                               // For chunk sizes smaller than 
a line (a line could not fit in a single chunk)
+                                               // we simply build our 
aggregate by reading in the next chunk, until we find a newline
+                                               nextChunk();
+                                               return;
+                                       }
+                               }
+
+                               var results = handle.parse(aggregate);
+                               aggregate = "";
+
+                               if (IS_WORKER)
+                               {
+                                       global.postMessage({
+                                               results: results,
+                                               workerId: Papa.WORKER_ID,
+                                               finished: finishedWithEntireFile
+                                       });
+                               }
+                               else if (isFunction(config.chunk))
+                               {
+                                       config.chunk(results, file);
+                                       results = undefined;
+                               }
+
+                               if (!finishedWithEntireFile && 
!results.meta.paused)
+                                       nextChunk();
+                       }
+
+                       function chunkError()
+                       {
+                               if (isFunction(config.error))
+                                       config.error(reader.error, file);
+                               else if (IS_WORKER && config.error)
+                               {
+                                       global.postMessage({
+                                               workerId: Papa.WORKER_ID,
+                                               error: reader.error,
+                                               file: file,
+                                               finished: false
+                                       });
+                               }
+                       }
+               };
+       }
+
+
+
+
+
+       // Use one ParserHandle per entire CSV file or string
+       function ParserHandle(_config)
+       {
+               // One goal is to minimize the use of regular expressions...
+               var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
+
+               var self = this;
+               var _input;                             // The input being 
parsed
+               var _parser;                    // The core parser being used
+               var _paused = false;    // Whether we are paused or not
+               var _delimiterError;    // Temporary state between delimiter 
detection and processing results
+               var _fields = [];               // Fields are from the header 
row of the input, if there is one
+               var _results = {                // The last results returned 
from the parser
+                       data: [],
+                       errors: [],
+                       meta: {}
+               };
+               _config = copy(_config);
+
+               this.parse = function(input)
+               {
+                       _delimiterError = false;
+                       if (!_config.delimiter)
+                       {
+                               var delimGuess = guessDelimiter(input);
+                               if (delimGuess.successful)
+                                       _config.delimiter = 
delimGuess.bestDelimiter;
+                               else
+                               {
+                                       _delimiterError = true; // add error 
after parsing (otherwise it would be overwritten)
+                                       _config.delimiter = 
Papa.DefaultDelimiter;
+                               }
+                               _results.meta.delimiter = _config.delimiter;
+                       }
+
+                       if (isFunction(_config.step))
+                       {
+                               var userStep = _config.step;
+                               _config.step = function(results)
+                               {
+                                       _results = results;
+                                       if (needsHeaderRow())
+                                               processResults();
+                                       else
+                                               userStep(processResults(), 
self);
+                               };
+                       }
+
+                       if (_config.preview && _config.header)
+                               _config.preview++;      // to compensate for 
header row
+
+                       _input = input;
+                       _parser = new Parser(_config);
+                       _results = _parser.parse(_input);
+                       processResults();
+                       if (isFunction(_config.complete) && !_paused)
+                               _config.complete(_results);
+                       return _paused ? { meta: { paused: true } } : _results;
+               };
+
+               this.pause = function()
+               {
+                       _paused = true;
+                       _parser.abort();
+                       _input = _input.substr(_parser.getCharIndex());
+               };
+
+               this.resume = function()
+               {
+                       _paused = false;
+                       _parser = new Parser(_config);
+                       _parser.parse(_input);
+                       if (isFunction(_config.complete) && !_paused)
+                               _config.complete(_results);
+               };
+
+               this.abort = function()
+               {
+                       _parser.abort();
+                       if (isFunction(_config.complete))
+                               _config.complete(_results);
+                       _input = "";
+               }
+
+               function processResults()
+               {
+                       if (_results && _delimiterError)
+                       {
+                               addError("Delimiter", "UndetectableDelimiter", 
"Unable to auto-detect delimiting character; defaulted to 
'"+Papa.DefaultDelimiter+"'");
+                               _delimiterError = false;
+                       }
+
+                       if (needsHeaderRow())
+                               fillHeaderFields();
+
+                       return applyHeaderAndDynamicTyping();
+               }
+
+               function needsHeaderRow()
+               {
+                       return _config.header && _fields.length == 0;
+               }
+
+               function fillHeaderFields()
+               {
+                       if (!_results)
+                               return;
+                       for (var i = 0; needsHeaderRow() && i < 
_results.data.length; i++)
+                               for (var j = 0; j < _results.data[i].length; 
j++)
+                                       _fields.push(_results.data[i][j]);
+                       _results.data.splice(0, 1);
+               }
+
+               function applyHeaderAndDynamicTyping()
+               {
+                       if (!_results || (!_config.header && 
!_config.dynamicTyping))
+                               return _results;
+
+                       for (var i = 0; i < _results.data.length; i++)
+                       {
+                               var row = {};
+                               for (var j = 0; j < _results.data[i].length; 
j++)
+                               {
+                                       if (_config.dynamicTyping)
+                                       {
+                                               var value = _results.data[i][j];
+                                               if (value == "true")
+                                                       _results.data[i][j] = 
true;
+                                               else if (value == "false")
+                                                       _results.data[i][j] = 
false;
+                                               else
+                                                       _results.data[i][j] = 
tryParseFloat(value);
+                                       }
+
+                                       if (_config.header)
+                                       {
+                                               if (j >= _fields.length)
+                                               {
+                                                       if 
(!row["__parsed_extra"])
+                                                               
row["__parsed_extra"] = [];
+                                                       
row["__parsed_extra"].push(_results.data[i][j]);
+                                               }
+                                               else
+                                                       row[_fields[j]] = 
_results.data[i][j];
+                                       }
+                               }
+
+                               if (_config.header)
+                               {
+                                       _results.data[i] = row;
+                                       if (j > _fields.length)
+                                               addError("FieldMismatch", 
"TooManyFields", "Too many fields: expected " + _fields.length + " fields but 
parsed " + j, i);
+                                       else if (j < _fields.length)
+                                               addError("FieldMismatch", 
"TooFewFields", "Too few fields: expected " + _fields.length + " fields but 
parsed " + j, i);
+                               }
+                       }
+
+                       if (_config.header && _results.meta)
+                               _results.meta.fields = _fields;
+
+                       return _results;
+               }
+
+               function guessDelimiter(input)
+               {
+                       var delimChoices = [",", "\t", "|", ";", 
Papa.RECORD_SEP, Papa.UNIT_SEP];
+                       var bestDelim, bestDelta, fieldCountPrevRow;
+
+                       for (var i = 0; i < delimChoices.length; i++)
+                       {
+                               var delim = delimChoices[i];
+                               var delta = 0, avgFieldCount = 0;
+                               fieldCountPrevRow = undefined;
+
+                               var preview = new Parser({
+                                       delimiter: delim,
+                                       preview: 10
+                               }).parse(input);
+
+                               for (var j = 0; j < preview.data.length; j++)
+                               {
+                                       var fieldCount = preview.data[j].length;
+                                       avgFieldCount += fieldCount;
+
+                                       if (typeof fieldCountPrevRow === 
'undefined')
+                                       {
+                                               fieldCountPrevRow = fieldCount;
+                                               continue;
+                                       }
+                                       else if (fieldCount > 1)
+                                       {
+                                               delta += Math.abs(fieldCount - 
fieldCountPrevRow);
+                                               fieldCountPrevRow = fieldCount;
+                                       }
+                               }
+
+                               avgFieldCount /= preview.data.length;
+
+                               if ((typeof bestDelta === 'undefined' || delta 
< bestDelta)
+                                       && avgFieldCount > 1.99)
+                               {
+                                       bestDelta = delta;
+                                       bestDelim = delim;
+                               }
+                       }
+
+                       _config.delimiter = bestDelim;
+
+                       return {
+                               successful: !!bestDelim,
+                               bestDelimiter: bestDelim
+                       }
+               }
+
+               function tryParseFloat(val)
+               {
+                       var isNumber = FLOAT.test(val);
+                       return isNumber ? parseFloat(val) : val;
+               }
+
+               function addError(type, code, msg, row)
+               {
+                       _results.errors.push({
+                               type: type,
+                               code: code,
+                               message: msg,
+                               row: row
+                       });
+               }
+       }
+
+
+
+
+
+
+
+
+
+
+
+       function Parser(config)
+       {
+               var EMPTY = /^\s*$/;
+
+               var _input;             // The input text being parsed
+               var _delimiter; // The delimiting character
+               var _comments;  // Comment character (default '#') or boolean
+               var _step;              // The step (streaming) function
+               var _callback;  // The callback to invoke when finished
+               var _preview;   // Maximum number of lines (not rows) to parse
+               var _ch;                // Current character
+               var _i;                 // Current character's positional index
+               var _inQuotes;  // Whether in quotes or not
+               var _lineNum;   // Current line number (1-based indexing)
+               var _data;              // Parsed data (results)
+               var _errors;    // Parse errors
+               var _rowIdx;    // Current row index within results (0-based)
+               var _colIdx;    // Current col index within result row (0-based)
+               var _runningRowIdx;             // Cumulative row index, used 
by the preview feature
+               var _aborted = false;   // Abort flag
+
+               // Unpack the config object
+               config = config || {};
+               _delimiter = config.delimiter;
+               _comments = config.comments;
+               _step = config.step;
+               _preview = config.preview;
+
+               // Delimiter integrity check
+               if (typeof _delimiter !== 'string'
+                       || _delimiter.length != 1
+                       || Papa.BAD_DELIMITERS.indexOf(_delimiter) > -1)
+                       _delimiter = ",";
+
+               // Comment character integrity check
+               if (_comments === true)
+                       _comments = "#";
+               else if (typeof _comments !== 'string'
+                       || _comments.length != 1
+                       || Papa.BAD_DELIMITERS.indexOf(_comments) > -1
+                       || _comments == _delimiter)
+                       _comments = false;
+
+
+               this.parse = function(input)
+               {
+                       if (typeof input !== 'string')
+                               throw "Input must be a string";
+                       reset(input);
+                       return parserLoop();
+               };
+
+               this.abort = function()
+               {
+                       _aborted = true;
+               };
+
+               this.getCharIndex = function()
+               {
+                        return _i;
+               };
+
+               function parserLoop()
+               {
+                       while (_i < _input.length)
+                       {
+                               if (_aborted) break;
+                               if (_preview > 0 && _runningRowIdx >= _preview) 
break;
+
+                               if (_ch == '"')
+                                       parseQuotes();
+                               else if (_inQuotes)
+                                       parseInQuotes();
+                               else
+                                       parseNotInQuotes();
+
+                               nextChar();
+                       }
+
+                       return finishParsing();
+               }
+
+               function nextChar()
+               {
+                       _i++;
+                       _ch = _input[_i];
+               }
+
+               function finishParsing()
+               {
+                       if (_aborted)
+                               addError("Abort", "ParseAbort", "Parsing was 
aborted by the user's step function");
+                       if (_inQuotes)
+                               addError("Quotes", "MissingQuotes", "Unescaped 
or mismatched quotes");
+                       endRow();       // End of input is also end of the last 
row
+                       if (!isFunction(_step))
+                               return returnable();
+               }
+
+               function parseQuotes()
+               {
+                       if (quotesOnBoundary() && !quotesEscaped())
+                               _inQuotes = !_inQuotes;
+                       else
+                       {
+                               saveChar();
+                               if (_inQuotes && quotesEscaped())
+                                       _i++
+                               else
+                                       addError("Quotes", "UnexpectedQuotes", 
"Unexpected quotes");
+                       }
+               }
+
+               function parseInQuotes()
+               {
+                       if (twoCharLineBreak(_i) || oneCharLineBreak(_i))
+                               _lineNum++;
+                       saveChar();
+               }
+
+               function parseNotInQuotes()
+               {
+                       if (_ch == _delimiter)
+                               newField();
+                       else if (twoCharLineBreak(_i))
+                       {
+                               newRow();
+                               nextChar();
+                       }
+                       else if (oneCharLineBreak(_i))
+                               newRow();
+                       else if (isCommentStart())
+                               skipLine();
+                       else
+                               saveChar();
+               }
+
+               function isCommentStart()
+               {
+                       if (!_comments)
+                               return false;
+
+                       var firstCharOfLine = _i == 0
+                                                                       || 
oneCharLineBreak(_i-1)
+                                                                       || 
twoCharLineBreak(_i-2);
+                       return firstCharOfLine && _input[_i] === _comments;
+               }
+
+               function skipLine()
+               {
+                       while (!twoCharLineBreak(_i)
+                               && !oneCharLineBreak(_i)
+                               && _i < _input.length)
+                       {
+                               nextChar();
+                       }
+               }
+
+               function saveChar()
+               {
+                       _data[_rowIdx][_colIdx] += _ch;
+               }
+
+               function newField()
+               {
+                       _data[_rowIdx].push("");
+                       _colIdx = _data[_rowIdx].length - 1;
+               }
+
+               function newRow()
+               {
+                       endRow();
+
+                       _lineNum++;
+                       _runningRowIdx++;
+                       _data.push([]);
+                       _rowIdx = _data.length - 1;
+                       newField();
+               }
+
+               function endRow()
+               {
+                       trimEmptyLastRow();
+                       if (isFunction(_step))
+                       {
+                               if (_data[_rowIdx])
+                                       _step(returnable());
+                               clearErrorsAndData();
+                       }
+               }
+
+               function trimEmptyLastRow()
+               {
+                       if (_data[_rowIdx].length == 1 && 
EMPTY.test(_data[_rowIdx][0]))
+                       {
+                               if (config.keepEmptyRows)
+                                       _data[_rowIdx].splice(0, 1);    // 
leave row, but no fields
+                               else
+                                       _data.splice(_rowIdx, 1);               
// cut out row entirely
+                               _rowIdx = _data.length - 1;
+                       }
+               }
+
+               function twoCharLineBreak(i)
+               {
+                       return i < _input.length - 1 &&
+                               ((_input[i] == "\r" && _input[i+1] == "\n")
+                               || (_input[i] == "\n" && _input[i+1] == "\r"))
+               }
+
+               function oneCharLineBreak(i)
+               {
+                       return _input[i] == "\r" || _input[i] == "\n";
+               }
+
+               function quotesEscaped()
+               {
+                       // Quotes as data cannot be on boundary, for example: 
,"", are not escaped quotes
+                       return !quotesOnBoundary() && _i < _input.length - 1 && 
_input[_i+1] == '"';
+               }
+
+               function quotesOnBoundary()
+               {
+                       return (!_inQuotes && isBoundary(_i-1)) || 
isBoundary(_i+1);
+               }
+
+               function isBoundary(i)
+               {
+                       if (typeof i != 'number')
+                               i = _i;
+
+                       var ch = _input[i];
+
+                       return (i <= -1 || i >= _input.length)
+                               || (ch == _delimiter
+                                       || ch == "\r"
+                                       || ch == "\n");
+               }
+
+               function addError(type, code, msg)
+               {
+                       _errors.push({
+                               type: type,
+                               code: code,
+                               message: msg,
+                               line: _lineNum,
+                               row: _rowIdx,
+                               index: _i
+                       });
+               }
+
+               function reset(input)
+               {
+                       _input = input;
+                       _inQuotes = false;
+                       _i = 0, _runningRowIdx = 0, _lineNum = 1;
+                       clearErrorsAndData();
+                       _data = [ [""] ];       // starting parsing requires an 
empty field
+                       _ch = _input[_i];
+               }
+
+               function clearErrorsAndData()
+               {
+                       _data = [];
+                       _errors = [];
+                       _rowIdx = 0;
+                       _colIdx = 0;
+               }
+
+               function returnable()
+               {
+                       return {
+                               data: _data,
+                               errors: _errors,
+                               meta: {
+                                       lines: _lineNum,
+                                       delimiter: _delimiter,
+                                       aborted: _aborted,
+                                       truncated: _preview > 0 && _i < 
_input.length
+                               }
+                       };
+               }
+       }
+
+
+
+       function getScriptPath()
+       {
+               var id = "worker" + String(Math.random()).substr(2);
+               document.write('<script id="'+id+'"></script>');
+               return document.getElementById(id).previousSibling.src;
+       }
+
+       function newWorker()
+       {
+               if (!Papa.WORKERS_SUPPORTED)
+                       return false;
+               var w = new global.Worker(SCRIPT_PATH);
+               w.onmessage = mainThreadReceivedMessage;
+               w.id = workerIdCounter++;
+               workers[w.id] = w;
+               return w;
+       }
+
+       // Callback when main thread receives a message
+       function mainThreadReceivedMessage(e)
+       {
+               var msg = e.data;
+               var worker = workers[msg.workerId];
+
+               if (msg.error)
+                       worker.userError(msg.error, msg.file);
+               else if (msg.results && msg.results.data)
+               {
+                       if (isFunction(worker.userStep))
+                       {
+                               for (var i = 0; i < msg.results.data.length; 
i++)
+                               {
+                                       worker.userStep({
+                                               data: [msg.results.data[i]],
+                                               errors: msg.results.errors,
+                                               meta: msg.results.meta
+                                       });
+                               }
+                               delete msg.results;     // free memory ASAP
+                       }
+                       else if (isFunction(worker.userChunk))
+                       {
+                               worker.userChunk(msg.results, msg.file);
+                               delete msg.results;
+                       }
+               }
+
+               if (msg.finished)
+               {
+                       if (isFunction(workers[msg.workerId].userComplete))
+                               workers[msg.workerId].userComplete(msg.results);
+                       workers[msg.workerId].terminate();
+                       delete workers[msg.workerId];
+               }
+       }
+
+       // Callback when worker thread receives a message
+       function workerThreadReceivedMessage(e)
+       {
+               var msg = e.data;
+
+               if (typeof Papa.WORKER_ID === 'undefined' && msg)
+                       Papa.WORKER_ID = msg.workerId;
+
+               if (typeof msg.input === 'string')
+               {
+                       global.postMessage({
+                               workerId: Papa.WORKER_ID,
+                               results: Papa.parse(msg.input, msg.config),
+                               finished: true
+                       });
+               }
+               else if (msg.input instanceof File)
+               {
+                       var results = Papa.parse(msg.input, msg.config);
+                       if (results)
+                               global.postMessage({
+                                       workerId: Papa.WORKER_ID,
+                                       results: results,
+                                       finished: true
+                               });
+               }
+       }
+
+       // Replaces bad config values with good, default ones
+       function copyAndValidateConfig(origConfig)
+       {
+               if (typeof origConfig !== 'object')
+                       origConfig = {};
+
+               var config = copy(origConfig);
+
+               if (typeof config.delimiter !== 'string'
+                       || config.delimiter.length != 1
+                       || Papa.BAD_DELIMITERS.indexOf(config.delimiter) > -1)
+                       config.delimiter = DEFAULTS.delimiter;
+
+               if (typeof config.header !== 'boolean')
+                       config.header = DEFAULTS.header;
+
+               if (typeof config.dynamicTyping !== 'boolean')
+                       config.dynamicTyping = DEFAULTS.dynamicTyping;
+
+               if (typeof config.preview !== 'number')
+                       config.preview = DEFAULTS.preview;
+
+               if (typeof config.step !== 'function')
+                       config.step = DEFAULTS.step;
+
+               if (typeof config.complete !== 'function')
+                       config.complete = DEFAULTS.complete;
+
+               if (typeof config.error !== 'function')
+                       config.error = DEFAULTS.error;
+
+               if (typeof config.encoding !== 'string')
+                       config.encoding = DEFAULTS.encoding;
+
+               if (typeof config.worker !== 'boolean')
+                       config.worker = DEFAULTS.worker;
+
+               if (typeof config.download !== 'boolean')
+                       config.download = DEFAULTS.download;
+
+               if (typeof config.keepEmptyRows !== 'boolean')
+                       config.keepEmptyRows = DEFAULTS.keepEmptyRows;
+
+               return config;
+       }
+
+       function copy(obj)
+       {
+               if (typeof obj !== 'object')
+                       return obj;
+               var cpy = obj instanceof Array ? [] : {};
+               for (var key in obj)
+                       cpy[key] = copy(obj[key]);
+               return cpy;
+       }
+
+       function isFunction(func)
+       {
+               return typeof func === 'function';
+       }
+})(this);
diff --git a/src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js 
b/src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js
new file mode 100644
index 0000000..877bb5b
--- /dev/null
+++ b/src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js
@@ -0,0 +1,110 @@
+/*!
+ * VisualEditor UserInterface delimiter-separated values file drop handler 
class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see 
http://ve.mit-license.org
+ */
+
+/**
+ * Delimiter-separated values file drop handler.
+ *
+ * @class
+ * @extends ve.ui.FileDropHandler
+ *
+ * @constructor
+ * @param {ve.ui.Surface} surface
+ * @param {File} file
+ */
+ve.ui.DSVFileDropHandler = function VeUiDSVFileDropHandler() {
+       // Parent constructor
+       ve.ui.DSVFileDropHandler.super.apply( this, arguments );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.DSVFileDropHandler, ve.ui.FileDropHandler );
+
+/* Static properties */
+
+ve.ui.DSVFileDropHandler.static.name = 'dsv';
+
+ve.ui.DSVFileDropHandler.static.types = [ 'text/csv', 
'text/tab-separated-values' ];
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+ve.ui.DSVFileDropHandler.prototype.process = function () {
+       this.createProgress( this.insertableDataDeferred.promise() );
+       this.reader.readAsText( this.file );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.DSVFileDropHandler.prototype.onFileProgress = function ( e ) {
+       if ( e.lengthComputable ) {
+               this.setProgress( 100 * e.loaded / e.total );
+       } else {
+               this.setProgress( false );
+       }
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.DSVFileDropHandler.prototype.onFileLoad = function () {
+       var i, j, line,
+               data = [],
+               input = Papa.parse( this.reader.result );
+
+       if ( input.meta.aborted || ( input.data.length <= 0 ) ) {
+               this.insertableDataDeffered.reject();
+               return;
+       }
+
+       data.push( { type: 'table' } );
+       data.push( { type: 'tableSection', attributes: { style: 'body' } } );
+
+       for ( i = 0; i < input.data.length; i++ ) {
+               data.push( { type: 'tableRow' } );
+               line = input.data[i];
+               for ( j = 0; j < line.length; j++ ) {
+                       data.push( { type: 'tableCell', attributes: { style: ( 
i === 0 ? 'header' : 'data' ) } } );
+                       data.push( { type: 'paragraph', internal: { generated: 
'wrapper' } } );
+                       data = data.concat( line[j].split( '' ) );
+                       data.push( { type: '/paragraph' } );
+                       data.push( { type: '/tableCell' } );
+               }
+               data.push( { type: '/tableRow' } );
+       }
+
+       data.push( { type: '/tableSection' } );
+       data.push( { type: '/table' } );
+
+       this.insertableDataDeferred.resolve( data );
+       this.setProgress( 100 );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.DSVFileDropHandler.prototype.onFileLoadEnd = function () {
+       // 'loadend' fires after 'load'/'abort'/'error'.
+       // Reject the deferred if it hasn't already resolved.
+       this.insertableDataDeferred.reject();
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.DSVFileDropHandler.prototype.abort = function () {
+       // Parent method
+       ve.ui.DSVFileDropHandler.super.prototype.abort.call( this );
+
+       this.reader.abort();
+};
+
+/* Registration */
+
+ve.ui.fileDropHandlerFactory.register( ve.ui.DSVFileDropHandler );
diff --git a/tests/index.html b/tests/index.html
index b40a6cd..fe5cea7 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -57,6 +57,9 @@
                <script src="../lib/jquery.uls/src/jquery.uls.data.js"></script>
                <script 
src="../lib/jquery.uls/src/jquery.uls.data.utils.js"></script>
 
+               <!-- papaparse -->
+               <script src="../lib/papaparse/papaparse.js"></script>
+
                <!-- unicodejs -->
                <script src="../lib/unicodejs/unicodejs.js"></script>
 
@@ -269,6 +272,7 @@
                <script 
src="../src/ui/dialogs/ve.ui.FragmentDialog.js"></script>
                <script src="../src/ui/dialogs/ve.ui.NodeDialog.js"></script>
                <script 
src="../src/ui/dialogs/ve.ui.ProgressDialog.js"></script>
+               <script 
src="../src/ui/filedrophandlers/ve.ui.DSVFileDropHandler.js"></script>
                <script 
src="../src/ui/filedrophandlers/ve.ui.PlainTextFileDropHandler.js"></script>
                <script 
src="../src/ui/filedrophandlers/ve.ui.HTMLFileDropHandler.js"></script>
                <script 
src="../src/ui/widgets/ve.ui.LanguageSearchWidget.js"></script>

-- 
To view, visit https://gerrit.wikimedia.org/r/173296
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Idbe32c04e129c97c30a195dedfc7f3f4139b5f36
Gerrit-PatchSet: 9
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Jforrester <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to