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
+========================================
+
+[](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