Compare commits

..

3 Commits
master ... 4.x

Author SHA1 Message Date
Sergi Almacellas Abellana 8b663aa17e Patch version bump 6 years ago
Sergi Almacellas Abellana 8af1d23646 Correctly guess deliminter when mixed with commas 6 years ago
Chris Zubak-Skees 886fdf3cb2 Consistently apply regex escaping to quoteChar (#602) 6 years ago
  1. 2
      .eslintrc.js
  2. 29
      .github/workflows/node.js.yml
  3. 7
      .travis.yml
  4. 6
      README.md
  5. 11
      docs/demo.html
  6. 197
      docs/docs.html
  7. 20
      docs/faq.html
  8. 38
      docs/index.html
  9. 62
      docs/resources/js/lovers.js
  10. 272
      docs/resources/js/papaparse.js
  11. 13
      package.json
  12. 330
      papaparse.js
  13. 4
      papaparse.min.js
  14. 2
      player/player.html
  15. 51
      tests/node-tests.js
  16. 3
      tests/sample-header.csv
  17. 590
      tests/test-cases.js
  18. 4
      tests/test.js
  19. 7
      tests/tests.html
  20. 2
      tests/verylong-sample.csv

2
.eslintrc.js

@ -178,7 +178,7 @@ module.exports = { @@ -178,7 +178,7 @@ module.exports = {
"no-tabs": "off",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "error",
"no-throw-literal": "off",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-undefined": "off",

29
.github/workflows/node.js.yml

@ -1,29 +0,0 @@ @@ -1,29 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test

7
.travis.yml

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
language: node_js
node_js:
- "6"
- "8"
- "9"
- "10"

6
README.md

@ -26,7 +26,7 @@ can be installed with the following command: @@ -26,7 +26,7 @@ can be installed with the following command:
npm install papaparse
If you don't want to use npm, [papaparse.min.js](https://unpkg.com/papaparse@latest/papaparse.min.js) can be downloaded to your project source.
If you don't want to use npm, [papaparse.min.js](https://github.com/mholt/PapaParse/blob/master/papaparse.min.js) can be downloaded to your project source.
Homepage & Demo
@ -39,7 +39,9 @@ To learn how to use Papa Parse: @@ -39,7 +39,9 @@ To learn how to use Papa Parse:
- [Documentation](http://papaparse.com/docs)
The website is hosted on [Github Pages](https://pages.github.com/). Its content is also included in the docs folder of this repository. If you want to contribute on it just clone the master of this repository and open a pull request.
The website is hosted on on [Github Pages](https://pages.github.com/). If
you want to contribute just clone the gh-branch of this repository and
open a pull request.
Papa Parse for Node

11
docs/demo.html

@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
<link rel="stylesheet" href="/resources/css/demo.css">
<script src="/resources/js/jquery.min.js"></script>
<script src="/resources/js/common.js"></script>
<script src="https://unpkg.com/papaparse@latest/papaparse.min.js"></script>
<script src="/resources/js/papaparse.js"></script>
<script src="/resources/js/demo.js"></script>
</head>
<body>
@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
</div>
</div>
<div class="grid-20 hide-on-mobile text-center">
<a href="/" class="text-logo">Papa Parse 5</a>
<a href="/" class="text-logo">Papa Parse 4</a>
</div>
<div class="grid-40 mobile-grid-50 text-right">
<div class="links">
@ -44,6 +44,9 @@ @@ -44,6 +44,9 @@
<a href="http://stackoverflow.com/questions/tagged/papaparse">
<i class="fa fa-stack-overflow fa-lg"></i> Help
</a>
<!-- <a href="https://matt.life/pay" class="donate">
<i class="fa fa-heart fa-lg"></i> Donate
</a> -->
</div>
</div>
</div>
@ -221,7 +224,7 @@ @@ -221,7 +224,7 @@
<br><br>
Papa Parse by <a href="https://twitter.com/mholt6">Matt Holt</a>
<br>
&copy; 2013-2019
&copy; 2013-2018
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Learn</h5>
@ -231,7 +234,7 @@ @@ -231,7 +234,7 @@
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Project</h5>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=S6VTL9FQ6L8EN&item_name=PapaParse&currency_code=EUR&source=url">Donate</a>
<a href="https://gratipay.com/mholt">Donate</a>
<a href="https://github.com/mholt/PapaParse">GitHub</a>
<a href="https://twitter.com/search?q=%23PapaParse">Share</a>
</div>

197
docs/docs.html

@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
</div>
</div>
<div class="grid-20 hide-on-mobile text-center">
<a href="/" class="text-logo">Papa Parse 5</a>
<a href="/" class="text-logo">Papa Parse 4</a>
</div>
<div class="grid-40 mobile-grid-50 text-right">
<div class="links">
@ -44,6 +44,9 @@ @@ -44,6 +44,9 @@
<a href="http://stackoverflow.com/questions/tagged/papaparse">
<i class="fa fa-stack-overflow fa-lg"></i> Help
</a>
<!-- <a href="https://matt.life/pay" class="donate">
<i class="fa fa-heart fa-lg"></i> Donate
</a> -->
</div>
</div>
</div>
@ -96,7 +99,7 @@ @@ -96,7 +99,7 @@
</div>
<div class="grid-50">
<pre><code class="language-javascript">Papa.parse(csvString<i>[, <a href="#config">config</a>]</i>)</code></pre>
<pre><code class="language-javascript">Papa.parse(csvString<i>[, <a href="#config">config</a>]</i>)</pre></code>
</div>
<div class="grid-50">
@ -185,7 +188,7 @@ @@ -185,7 +188,7 @@
reason: "Some reason",
config: <span class="comment">// altered config...</span>
}</code></pre>
to alter the flow of parsing. Actions can be <code>"abort"</code> to skip this and all other files in the queue, <code>"skip"</code> to skip just this file, or <code>"continue"</code> to carry on (equivalent to returning nothing). <code>reason</code> can be a reason for aborting. <code>config</code> can be a modified <a href="#config">configuration</a> for parsing just this file.
to alter the flow of parsing. Actions can be <code>"abort"</code> to skip this and all other files in the queue, <code>"skip"</code> to skip just this file, or <code>"continue"</code> to carry on (equivalent to returning nothing). <code>reason</code> can be a reason for aborting. <code>config</code> can be a modified <a href="#config">configuration</a> for parsing just this file.</li>
</li>
<li>The <code>complete</code> callback shown here is executed after <i>all</i> files are finished and does not receive any data. Use the complete callback in <a href="#config">config</a> for per-file results.</li>
</ul>
@ -225,7 +228,7 @@ @@ -225,7 +228,7 @@
<div class="grid-50">
<pre><code class="language-javascript">Papa.unparse(data<i>[, <a href="#unparse-config-default">config</a>]</i>)</code></pre>
<pre><code class="language-javascript">Papa.unparse(data<i>[, config]</i>)</code></pre>
</div>
<div class="grid-50">
@ -240,113 +243,19 @@ @@ -240,113 +243,19 @@
</ul>
</li>
<li>
<code>config</code> is an optional <a href="#unparse-config-default">config object</a>
</li>
</ul>
</div>
<div class="clear"></div>
<div class="grid-100">
<h5 id="unparse-config-default">Default Unparse Config with all options</h5>
</div>
<div class="prefix-25 grid-50 suffix-25">
<pre><code class="language-javascript">
<code>config</code> is an optional object with any of these properties:
<pre><code class="language-javascript">// defaults shown
{
quotes: false, //or array of booleans
quotes: false,
quoteChar: '"',
escapeChar: '"',
delimiter: ",",
header: true,
newline: "\r\n",
skipEmptyLines: false, //other option is 'greedy', meaning skip delimiters, quotes, and whitespace.
columns: null //or array of strings
}
</code></pre>
</div>
<div class="clear"></div>
<div class="grid-100">
<h5>Unparse Config Options</h5>
</div>
<div class="grid-100" style="overflow-x: auto;">
<table>
<tr>
<th style="width: 20%;">Option</th>
<th style="width: 80%;">Explanation</th>
</tr>
<tr>
<td>
<code>quotes</code>
</td>
<td>
If <code>true</code>, forces all fields to be enclosed in quotes. If an array of <code>true/false</code> values, specifies which fields should be force-quoted (first boolean is for the first column, second boolean for the second column, ...). A function that returns a boolean values can be used to determine the quotes value of a cell. This function accepts the cell value and column index as parameters. <br />
Note that this option is ignored for <code>undefined</code>, <code>null</code> and <code>date-object</code> values. The option <code>escapeFormulae</code> also takes precedence over this.
</td>
</tr>
<tr>
<td><code>quoteChar</code></td>
<td>
The character used to quote fields.
</td>
</tr>
<tr>
<td><code>escapeChar</code></td>
<td>
The character used to escape <code>quoteChar</code> inside field values.
</td>
</tr>
<tr>
<td>
<code>delimiter</code>
</td>
<td>
The delimiting character. Multi-character delimiters are supported. It must not be found in <a href="#readonly">Papa.BAD_DELIMITERS</a>.
</td>
</tr>
<tr>
<td>
<code>header</code>
</td>
<td>
If <code>false</code>, will omit the header row. If <code>data</code> is an array of arrays this option is ignored. If <code>data</code> is an array of objects the keys of the first object are the header row. If <code>data</code> is an object with the keys <code>fields</code> and <code>data</code> the <code>fields</code> are the header row.
</td>
</tr>
<tr>
<td>
<code>newline</code>
</td>
<td>
The character used to determine newline sequence. It defaults to <code>"\r\n"</code>.
</td>
</tr>
<tr>
<td>
<code>skipEmptyLines</code>
</td>
<td>
If <code>true</code>, lines that are completely empty (those which evaluate to an empty string) will be skipped. If set to <code>'greedy'</code>, lines that don't have any content (those which have only whitespace after parsing) will also be skipped.
</td>
</tr>
<tr>
<td>
<code>columns</code>
</td>
<td>
If <code>data</code> is an array of objects this option can be used to manually specify the keys (columns) you expect in the objects. If not set the keys of the first objects are used as column.
</td>
</tr>
<tr>
<td>
<code>escapeFormulae</code>
</td>
<td>
If <code>true</code>, field values that begin with <code>=</code>, <code>+</code>, <code>-</code>, <code>@</code>, <code>\t</code>, or <code>\r</code>, will be prepended with a <code>'</code> to defend against <a href="https://owasp.org/www-community/attacks/CSV_Injection" target="_blank" rel="noopener">injection attacks</a>, because Excel and LibreOffice will automatically parse such cells as formulae. You can override those values by setting this option to a regular expression
</td>
</tr>
</table>
newline: "\r\n"
}</code></pre>
Set <code>quotes</code> to <code>true</code> to always enclose each field in quotes, or an array of true/false values correlating to specific to columns to force-quote. The character used to quote can be customized using <code>quoteChar</code>. The character used to escape the <code>quoteChar</code> within a field can be customized using <code>escapeChar</code>. The <code>delimiter</code> can be any valid delimiting character. The <code>newline</code> character(s) may also be customized. Setting <code>header</code> to <code>false</code> will omit the header row.
</li>
</ul>
</div>
<div class="clear"></div>
@ -380,8 +289,8 @@ var csv = Papa.unparse([ @@ -380,8 +289,8 @@ var csv = Papa.unparse([
<div class="grid-33">
<pre><code class="language-javascript">// Specifying fields and data explicitly
var csv = Papa.unparse({
"fields": ["Column 1", "Column 2"],
"data": [
fields: ["Column 1", "Column 2"],
data: [
["foo", "bar"],
["abc", "def"]
]
@ -431,7 +340,7 @@ var csv = Papa.unparse({ @@ -431,7 +340,7 @@ var csv = Papa.unparse({
quoteChar: '"',
escapeChar: '"',
header: false,
transformHeader: undefined,
trimHeaders: false,
dynamicTyping: false,
preview: 0,
encoding: "",
@ -441,16 +350,12 @@ var csv = Papa.unparse({ @@ -441,16 +350,12 @@ var csv = Papa.unparse({
complete: undefined,
error: undefined,
download: false,
downloadRequestHeaders: undefined,
downloadRequestBody: undefined,
skipEmptyLines: false,
chunk: undefined,
chunkSize: undefined,
fastMode: undefined,
beforeFirstChunk: undefined,
withCredentials: undefined,
transform: undefined,
delimitersToGuess: [',', '\t', '|', ';', <a href="#readonly">Papa.RECORD_SEP</a>, <a href="#readonly">Papa.UNIT_SEP</a>]
transform: undefined
}</code></pre>
</div>
<div class="clear"></div>
@ -470,7 +375,7 @@ var csv = Papa.unparse({ @@ -470,7 +375,7 @@ var csv = Papa.unparse({
<code>delimiter</code>
</td>
<td>
The delimiting character. Leave blank to auto-detect from a list of most common delimiters, or any values passed in through <code>delimitersToGuess</code>. It can be a string or a function. If a string, it can be of any length (so multi-character delimiters are supported). If a function, it must accept the input as first parameter and it must return a string which will be used as delimiter. In both cases it cannot be found in <a href="#readonly">Papa.BAD_DELIMITERS</a>.
The delimiting character. Leave blank to auto-detect from a list of most common delimiters. It can be a string or a function. If string, it must be one of length 1. If a function, it must accept the input as first parameter and it must return a string which will be used as delimiter. In both cases it cannot be found in <a href="#readonly">Papa.BAD_DELIMITERS</a>.
</td>
</tr>
<tr>
@ -507,11 +412,10 @@ var csv = Papa.unparse({ @@ -507,11 +412,10 @@ var csv = Papa.unparse({
</tr>
<tr>
<td>
<code>transformHeader</code>
<code>trimHeaders</code>
</td>
<td>
A function to apply on each header. Requires <code>header</code> to be <code>true</code>. The function receives the header as its first argument and the index as second.<br>
Only available starting with version 5.0.
If true leading/trailing spaces will be trimed from headers.
</td>
</tr>
<tr>
@ -519,7 +423,7 @@ var csv = Papa.unparse({ @@ -519,7 +423,7 @@ var csv = Papa.unparse({
<code>dynamicTyping</code>
</td>
<td>
If true, numeric and boolean data will be converted to their type instead of remaining strings. Numeric data must conform to the definition of a decimal literal. Numerical values greater than <code>2^53</code> or less than <code>-2^53</code> will not be converted to numbers to preserve precision. European-formatted numbers must have commas and dots swapped. If also accepts an object or a function. If object it's values should be a boolean to indicate if dynamic typing should be applied for each column number (or header name if using headers). If it's a function, it should return a boolean value for each field number (or name if using headers) which will be passed as first argument.
If true, numeric and boolean data will be converted to their type instead of remaining strings. Numeric data must conform to the definition of a decimal literal. European-formatted numbers must have commas and dots swapped. If also accepts an object or a function. If object it's values should be a boolean to indicate if dynamic typing should be applied for each column number (or header name if using headers). If it's a function, it should return a boolean value for each field number (or name if using headers) which will be passed as first argument.
</td>
</tr>
<tr>
@ -527,7 +431,7 @@ var csv = Papa.unparse({ @@ -527,7 +431,7 @@ var csv = Papa.unparse({
<code>preview</code>
</td>
<td>
If &gt; 0, only that many rows will be parsed.
If > 0, only that many rows will be parsed.
</td>
</tr>
<tr>
@ -543,7 +447,7 @@ var csv = Papa.unparse({ @@ -543,7 +447,7 @@ var csv = Papa.unparse({
<code>worker</code>
</td>
<td>
Whether or not to use a <a href="/faq#workers">worker thread</a>. Using a worker will keep your page reactive, but may be slightly slower.
Whether or not to use a <a href="/faq#workers">worker thread</a>. Using a worker will keep your page reactive, but may be slightly slower. Web Workers also load the entire Javascript file, so be careful when <a href="/faq#combine">combining other libraries</a> in the same file as Papa Parse. Note that worker option is only available when parsing files and not when converting from JSON to CSV.
</td>
</tr>
<tr>
@ -596,27 +500,6 @@ var csv = Papa.unparse({ @@ -596,27 +500,6 @@ var csv = Papa.unparse({
If true, this indicates that the string you passed as the first argument to <code>parse()</code> is actually a URL from which to download a file and parse its contents.
</td>
</tr>
<tr>
<td>
<code>downloadRequestHeaders</code>
</td>
<td>
If defined, should be an object that describes the headers, example:
<pre>
<code class="language-javascript">downloadRequestHeaders: {
'Authorization': 'token 123345678901234567890',
}</code>
</pre>
</tr>
<tr>
<td>
<code>downloadRequestBody</code>
</td>
<td>
Use POST request on the URL of the download option. The value passed will be set as the body of the request.
</td>
</tr>
<tr>
<td>
<code>skipEmptyLines</code>
@ -633,14 +516,6 @@ var csv = Papa.unparse({ @@ -633,14 +516,6 @@ var csv = Papa.unparse({
A callback function, identical to step, which activates streaming. However, this function is executed after every <i>chunk</i> of the file is loaded and parsed rather than every row. Works only with local and remote files. Do not use both chunk and step callbacks together. For the function signature, see the documentation for the step function.
</td>
</tr>
<tr>
<td>
<code>chunkSize</code>
</td>
<td>
Overrides <code>Papa.LocalChunkSize</code> and <code>Papa.RemoteChunkSize</code>. See <a href="#configurable">configurable</a> section to know the usage of both parameters.
</td>
</tr>
<tr>
<td>
<code>fastMode</code>
@ -670,15 +545,7 @@ var csv = Papa.unparse({ @@ -670,15 +545,7 @@ var csv = Papa.unparse({
<code>transform</code>
</td>
<td>
A function to apply on each value. The function receives the value as its first argument and the column number or header name when enabled as its second argument. The return value of the function will replace the value it received. The transform function is applied before dynamicTyping.
</td>
</tr>
<tr>
<td>
<code>delimitersToGuess</code>
</td>
<td>
An array of delimiters to guess from if the <code>delimiter</code> option is not set.
A function to apply on each value. The function receives the value as its first argument and the column number as its second argument. The return value of the function will replace the value it received. The transform function is applied before dynamicTyping.
</td>
</tr>
</table>
@ -861,7 +728,7 @@ var csv = Papa.unparse({ @@ -861,7 +728,7 @@ var csv = Papa.unparse({
<tr>
<td><code>Papa.BAD_DELIMITERS</code></td>
<td>
An array of characters that are not allowed as delimiters (<code>\r, \n, ", \ufeff</code>).
An array of characters that are not allowed as delimiters.
</td>
</tr>
<tr>
@ -882,6 +749,12 @@ var csv = Papa.unparse({ @@ -882,6 +749,12 @@ var csv = Papa.unparse({
Whether or not the browser supports HTML5 Web Workers. If false, <code>worker: true</code> will have no effect.
</td>
</tr>
<tr>
<td><code>Papa.SCRIPT_PATH</code></td>
<td>
The relative path to Papa Parse. This is automatically detected when Papa Parse is loaded synchronously. However, if you load Papa Parse asynchronously (e.g. with RequireJS), you need to set this variable manually in order to use Web Workers. (In those cases, this variable is <i>not</i> read-only and you should set it!)
</td>
</tr>
</table>
</div>
@ -939,7 +812,7 @@ var csv = Papa.unparse({ @@ -939,7 +812,7 @@ var csv = Papa.unparse({
<br><br>
Papa Parse by <a href="https://twitter.com/mholt6">Matt Holt</a>
<br>
&copy; 2013-2019
&copy; 2013-2018
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Learn</h5>
@ -949,7 +822,7 @@ var csv = Papa.unparse({ @@ -949,7 +822,7 @@ var csv = Papa.unparse({
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Project</h5>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=S6VTL9FQ6L8EN&item_name=PapaParse&currency_code=EUR&source=url">Donate</a>
<a href="https://gratipay.com/mholt">Donate</a>
<a href="https://github.com/mholt/PapaParse">GitHub</a>
<a href="https://twitter.com/search?q=%23PapaParse">Share</a>
</div>

20
docs/faq.html

@ -35,7 +35,7 @@ @@ -35,7 +35,7 @@
</div>
</div>
<div class="grid-20 hide-on-mobile text-center">
<a href="/" class="text-logo">Papa Parse 5</a>
<a href="/" class="text-logo">Papa Parse 4</a>
</div>
<div class="grid-40 mobile-grid-50 text-right">
<div class="links">
@ -45,6 +45,9 @@ @@ -45,6 +45,9 @@
<a href="http://stackoverflow.com/questions/tagged/papaparse">
<i class="fa fa-stack-overflow fa-lg"></i> Help
</a>
<!-- <a href="https://matt.life/pay" class="donate">
<i class="fa fa-heart fa-lg"></i> Donate
</a> -->
</div>
</div>
</div>
@ -81,7 +84,7 @@ @@ -81,7 +84,7 @@
<h6 id="combine">Can I put other libraries in the same file as Papa Parse?</h6>
<p>
Yes.
Yes, but then don't use the Web Worker feature unless your other dependencies are battle-hardened for worker threads. A worker thread loads an entire file, not just a function, so all those dependencies would be executed in an environment without a DOM and other <code>window</code> features. If any of those dependencies crash (<code>Cannot read property "defaultView" of undefined</code> <a href="https://github.com/mholt/PapaParse/issues/114">is</a> <a href="https://github.com/mholt/PapaParse/issues/163">common</a>), the whole worker thread will crash and parsing will not succeed.
</p>
@ -93,7 +96,7 @@ @@ -93,7 +96,7 @@
<h6 id="async">Can Papa Parse be loaded asynchronously (after the page loads)?</h6>
<p>
Yes.
Yes. But if you want to use Web Workers, you'll need to specify the relative path to Papa Parse. To do this, set <a href="/docs#readonly">Papa.SCRIPT_PATH</a> to the relative path of the Papa Parse file. In synchronous loading, this is automatically detected.
</p>
@ -206,7 +209,7 @@ @@ -206,7 +209,7 @@
<h6>Can I use a worker if I combine/concatenate my Javascript files?</h6>
<p>
Yes.
Probably not. It's safest to concatenate the rest of your dependencies and include Papa Parse in a seperate file. Any library that expects to have access to the <code>window</code> or DOM will crash when executed in a worker thread. Only put <a href="/faq#combine">other libraries in the same file</a> if they are ready to be used in worker threads.
</p>
<h6>When should I use a worker?</h6>
@ -238,11 +241,6 @@ @@ -238,11 +241,6 @@
<p>
No. This would drastically slow down parsing, as it would require the worker to wait after every chunk for a "continue" signal from the main thread. But you <i>can</i> abort workers by calling <code>.abort()</code> on the parser that gets passed to your callback function.
</p>
<h6>I set worker:true and now I'm getting an error: "window is not defined." How do I fix it?</h6>
<p>
This is a fairly common issue with configuration and it appears to be related to the use of React or other third party tools. Since this is a configuration issue, the exact steps needed to solve it may vary. See <a href="https://github.com/mholt/PapaParse/issues/655">Issue #655</a> on GitHub for a solution that worked for one person, and for links to other related issues.
</p>
</div>
</div>
</main>
@ -259,7 +257,7 @@ @@ -259,7 +257,7 @@
<br><br>
Papa Parse by <a href="https://twitter.com/mholt6">Matt Holt</a>
<br>
&copy; 2013-2019
&copy; 2013-2018
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Learn</h5>
@ -269,7 +267,7 @@ @@ -269,7 +267,7 @@
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Project</h5>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=S6VTL9FQ6L8EN&item_name=PapaParse&currency_code=EUR&source=url">Donate</a>
<a href="https://gratipay.com/mholt">Donate</a>
<a href="https://github.com/mholt/PapaParse">GitHub</a>
<a href="https://twitter.com/search?q=%23PapaParse">Share</a>
</div>

38
docs/index.html

@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
<h1>Papa Parse</h1>
<h2>The powerful, in-browser CSV parser for big boys and girls</h2>
<a href="https://github.com/mholt/PapaParse/archive/5.0.2.zip" class="button">
<a href="https://github.com/mholt/PapaParse/archive/4.6.0.zip" class="button">
<i class="fa fa-download"></i>&nbsp; Download
</a>
<a href="/demo" class="button red">
@ -81,7 +81,7 @@ Papa.parse(bigFile, { @@ -81,7 +81,7 @@ Papa.parse(bigFile, {
</div>
</div>
<div class="grid-20 hide-on-mobile text-center">
<a href="/" class="text-logo">Papa Parse 5</a>
<a href="/" class="text-logo">Papa Parse 4</a>
</div>
<div class="grid-40 mobile-grid-50 text-right">
<div class="links">
@ -91,13 +91,16 @@ Papa.parse(bigFile, { @@ -91,13 +91,16 @@ Papa.parse(bigFile, {
<a href="http://stackoverflow.com/questions/tagged/papaparse">
<i class="fa fa-stack-overflow fa-lg"></i> Help
</a>
<!-- <a href="https://matt.life/pay" class="donate">
<i class="fa fa-heart fa-lg"></i> Donate
</a> -->
</div>
</div>
</div>
</header>
<div class="insignia">
<div class="firefox-hack"><div id="version-intro">Version</div><div id="version">5.0</div></div>
<div class="firefox-hack"><div id="version-intro">Version</div><div id="version">4.6</div></div>
</div>
@ -175,24 +178,6 @@ Papa.parse(bigFile, { @@ -175,24 +178,6 @@ Papa.parse(bigFile, {
<div class="grid-100 text-center">
<h3>People <i class="fa fa-heart"></i> Papa</h3>
</div>
<div class="grid-100 text-center">
<br>
<p>
<a href="https://www.npmjs.com/package/papaparse">
<img
src="https://img.shields.io/npm/dm/papaparse.svg"
alt="PapaParse"
/>
</a>
&nbsp;
<a href="https://www.npmjs.com/package/react-papaparse">
<img
src="https://img.shields.io/npm/dt/papaparse.svg?label=total%20downloads"
alt="PapaParse"
/>
</a>
</p>
</div>
<div class="grid-33">
<p class="lover">
@ -214,7 +199,7 @@ Papa.parse(bigFile, { @@ -214,7 +199,7 @@ Papa.parse(bigFile, {
<div class="grid-100 text-center">
<br>
<b><a href="https://github.com/mholt/PapaParse/blob/master/docs/resources/js/lovers.js" class="add-lover-link subheader"><i class="fa fa-plus-square"></i> Add your link (it's free)</a></b>
<b><a href="https://github.com/mholt/PapaParse/blob/gh-pages/resources/js/lovers.js" class="add-lover-link subheader"><i class="fa fa-plus-square"></i> Add your link (it's free)</a></b>
</div>
</div>
</section>
@ -521,7 +506,6 @@ var csv = Papa.unparse(yourData);</code></pre> @@ -521,7 +506,6 @@ var csv = Papa.unparse(yourData);</code></pre>
<i class="fa fa-book"></i>&nbsp; Documentation
</a>
</div>
</div>
</section>
@ -540,7 +524,7 @@ var csv = Papa.unparse(yourData);</code></pre> @@ -540,7 +524,7 @@ var csv = Papa.unparse(yourData);</code></pre>
<br><br>
Papa Parse by <a href="https://twitter.com/mholt6">Matt Holt</a>
<br>
&copy; 2013-2019
&copy; 2013-2018
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Learn</h5>
@ -550,7 +534,7 @@ var csv = Papa.unparse(yourData);</code></pre> @@ -550,7 +534,7 @@ var csv = Papa.unparse(yourData);</code></pre>
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Project</h5>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=S6VTL9FQ6L8EN&item_name=PapaParse&currency_code=EUR&source=url">Donate</a>
<a href="https://gratipay.com/mholt">Donate</a>
<a href="https://github.com/mholt/PapaParse">GitHub</a>
<a href="https://twitter.com/search?q=%23PapaParse">Share</a>
</div>
@ -559,8 +543,8 @@ var csv = Papa.unparse(yourData);</code></pre> @@ -559,8 +543,8 @@ var csv = Papa.unparse(yourData);</code></pre>
<h5>Download</h5>
<a href="https://github.com/mholt/PapaParse/archive/master.zip">Latest (master)</a>
<hr>
<a href="https://unpkg.com/papaparse@latest/papaparse.min.js">Lil' Papa</a>
<a href="https://unpkg.com/papaparse@latest/papaparse.js">Fat Papa</a>
<a href="https://github.com/mholt/PapaParse/blob/master/papaparse.min.js">Lil' Papa</a>
<a href="https://github.com/mholt/PapaParse/blob/master/papaparse.js">Fat Papa</a>
</div>
<div class="grid-15 mobile-grid-50 links">
<h5>Community</h5>

62
docs/resources/js/lovers.js

@ -56,57 +56,27 @@ var peopleLovePapa = [ @@ -56,57 +56,27 @@ var peopleLovePapa = [
description: "uses Papa Parse in VisualEditor to help article editors effortlessly build data tables from text files."
},
{
link: "https://github.com/Nanofus/novel.js",
name: "Novel.js",
description: "is a text adventure framework that uses Papa Parse to enable user-friendly translations.",
quote: "Papa saves countless hours of work and makes reading large CSV files so easy!"
},
{
link: "https://mailcheck.co",
name: "Mailcheck.co",
description: "Mailcheck is email validation service. All emails usually stored in CSV's. We use Papa Parse to process data from our customers in browser",
quote: "Papa Parser allowed our customers to preview and process csv's in browser, without uploading them to server. It saves lots of time and space :)"
},
{
link: "https://flatfile.io",
name: "Flatfile.io",
description: "is an add-in data importer for web-apps, providing the full UX to upload a spreadsheet, field match, and repair issues found during import.",
quote: "Papa is a core part of our importer, so much so that we're committed to helping maintain it!"
},
{
link: "https://familiohq.com",
name: "Familio",
description: "is a brand-new messaging app made specifically for busy families. Automatically align all family members when sending text messages to parents in the kindergarten or school or when planning your kids birthday parties.",
quote: "With Papa it was a joy to implement our tool for importing messages and places from external systems."
},
{
link: "https://monei.net",
name: "MONEI",
description: "Digital payments made easy.",
quote: "With Papa life became much easier for us to manage huge csv payments files of our merchants."
},
{
link: "https://moonmail.io",
name: "MoonMail",
description: "OmniChannel Communication Platform powered by AWS PinPoint",
quote: "Papa makes contact imports a plain sailing."
link: "https://www.webucator.com/webdesign/javascript.cfm",
name: "Webucator",
description: "created a video showing how to use Papa Parse and FileDrop.js to create a drag-and-drop CSV-JSON converter.",
quote: "It's often easy to convert data to CSV. With Papa, it's easy to turn that CSV into JSON."
},
{
link: "https://apps.shopify.com/wholesaler",
name: "Wholesaler for Shopify",
description: "Shopify App to offer Wholesaling within one unique Shopify store",
quote: "Super fast bulk Wholesale product price uploads. Love Papa!."
link: "http://www.yolpo.com/social/gist.github?1dbd4556e748bdb830b3&autoplay=1&interimresults=0&failfast=1",
name: "Yolpo",
description: "created a simple regression test for Papa Parse.",
quote: "Papa's API is so intuitive, it took me no time to get it to work."
},
{
link: "https://www.unnitmetaliya.com/sop-sample/",
name: "Visa SOP Sample",
description: "Providing free guide to international students.",
quote: "Use Papa Parse for many of side projects. Super fast and works all the time. Love it!"
link: "https://www.appstax.com",
name: "Appstax",
description: "uses Papa Parse to import and export CSV data in their visual databrowser.",
quote: "Papa is a great for parsing CSV. And what a great tone of voice - love it!"
},
{
link: "https://retool.com/",
name: "Retool",
description: "A remarkably fast way to build internal tools.",
quote: "Papa makes it easy for our users to customize CSV parsing to match their business logic."
link: "https://github.com/Nanofus/novel.js",
name: "Novel.js",
description: "is a text adventure framework that uses Papa Parse to enable user-friendly translations.",
quote: "Papa saves countless hours of work and makes reading large CSV files so easy!"
}
];

272
docs/resources/js/papaparse.js

@ -1,10 +1,19 @@ @@ -1,10 +1,19 @@
/* @license
Papa Parse
v5.0.0
v4.6.1
https://github.com/mholt/PapaParse
License: MIT
*/
// Polyfills
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray#Polyfill
if (!Array.isArray)
{
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
(function(root, factory)
{
/* globals define */
@ -25,10 +34,7 @@ License: MIT @@ -25,10 +34,7 @@ License: MIT
// Browser globals (root is window)
root.Papa = factory();
}
// in strict mode we cannot access arguments.callee, so we need a named reference to
// stringify the factory method for the blob worker
// eslint-disable-next-line func-name
}(this, function moduleFactory()
}(this, function()
{
'use strict';
@ -45,15 +51,9 @@ License: MIT @@ -45,15 +51,9 @@ License: MIT
return {};
})();
function getWorkerBlob() {
var URL = global.URL || global.webkitURL || null;
var code = moduleFactory.toString();
return Papa.BLOB_URL || (Papa.BLOB_URL = URL.createObjectURL(new Blob(['(', code, ')();'], {type: 'text/javascript'})));
}
var IS_WORKER = !global.document && !!global.postMessage,
IS_PAPA_WORKER = IS_WORKER && /blob:/i.test((global.location || {}).protocol);
IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search),
LOADED_SYNC = false, AUTO_SCRIPT_PATH;
var workers = {}, workerIdCounter = 0;
var Papa = {};
@ -66,6 +66,7 @@ License: MIT @@ -66,6 +66,7 @@ License: MIT
Papa.BYTE_ORDER_MARK = '\ufeff';
Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK];
Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker;
Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously
Papa.NODE_STREAM_INPUT = 1;
// Configurable chunk sizes for local and remote files, respectively
@ -80,9 +81,7 @@ License: MIT @@ -80,9 +81,7 @@ License: MIT
Papa.FileStreamer = FileStreamer;
Papa.StringStreamer = StringStreamer;
Papa.ReadableStreamStreamer = ReadableStreamStreamer;
if (typeof PAPA_BROWSER_CONTEXT === 'undefined') {
Papa.DuplexStreamStreamer = DuplexStreamStreamer;
}
if (global.jQuery)
{
@ -183,6 +182,23 @@ License: MIT @@ -183,6 +182,23 @@ License: MIT
{
global.onmessage = workerThreadReceivedMessage;
}
else if (Papa.WORKERS_SUPPORTED)
{
AUTO_SCRIPT_PATH = getScriptPath();
// Check if the script was loaded synchronously
if (!document.body)
{
// Body doesn't exist yet, must be synchronous
LOADED_SYNC = true;
}
else
{
document.addEventListener('DOMContentLoaded', function() {
LOADED_SYNC = true;
}, true);
}
}
@ -225,7 +241,7 @@ License: MIT @@ -225,7 +241,7 @@ License: MIT
}
var streamer = null;
if (_input === Papa.NODE_STREAM_INPUT && typeof PAPA_BROWSER_CONTEXT === 'undefined')
if (_input === Papa.NODE_STREAM_INPUT)
{
// create a node Duplex stream for use
// with .pipe
@ -273,18 +289,12 @@ License: MIT @@ -273,18 +289,12 @@ License: MIT
/** quote character */
var _quoteChar = '"';
/** escaped quote character, either "" or <config.escapeChar>" */
var _escapedQuote = _quoteChar + _quoteChar;
/** whether to skip empty lines */
var _skipEmptyLines = false;
/** the columns (keys) we expect when we unparse objects */
var _columns = null;
unpackConfig();
var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g');
var quoteCharRegex = new RegExp(_quoteChar, 'g');
if (typeof _input === 'string')
_input = JSON.parse(_input);
@ -294,7 +304,7 @@ License: MIT @@ -294,7 +304,7 @@ License: MIT
if (!_input.length || Array.isArray(_input[0]))
return serialize(null, _input, _skipEmptyLines);
else if (typeof _input[0] === 'object')
return serialize(_columns || objectKeys(_input[0]), _input, _skipEmptyLines);
return serialize(objectKeys(_input[0]), _input, _skipEmptyLines);
}
else if (typeof _input === 'object')
{
@ -319,7 +329,7 @@ License: MIT @@ -319,7 +329,7 @@ License: MIT
}
// Default (any valid paths should return before this)
throw new Error('Unable to serialize unrecognized input');
throw 'exception: Unable to serialize unrecognized input';
function unpackConfig()
@ -349,17 +359,6 @@ License: MIT @@ -349,17 +359,6 @@ License: MIT
if (typeof _config.header === 'boolean')
_writeHeader = _config.header;
if (Array.isArray(_config.columns)) {
if (_config.columns.length === 0) throw new Error('Option columns is empty');
_columns = _config.columns;
}
if (_config.escapeChar !== undefined) {
_escapedQuote = _config.escapeChar + _quoteChar;
}
}
@ -404,36 +403,21 @@ License: MIT @@ -404,36 +403,21 @@ License: MIT
for (var row = 0; row < data.length; row++)
{
var maxCol = hasHeader ? fields.length : data[row].length;
var r = hasHeader ? fields : data[row];
var emptyLine = false;
var nullLine = hasHeader ? Object.keys(data[row]).length === 0 : data[row].length === 0;
if (skipEmptyLines && !hasHeader)
{
emptyLine = skipEmptyLines === 'greedy' ? data[row].join('').trim() === '' : data[row].length === 1 && data[row][0].length === 0;
}
if (skipEmptyLines === 'greedy' && hasHeader) {
var line = [];
for (var c = 0; c < maxCol; c++) {
var cx = dataKeyedByField ? fields[c] : c;
line.push(data[row][cx]);
}
emptyLine = line.join('').trim() === '';
}
if (!emptyLine)
if (skipEmptyLines !== 'greedy' || r.join('').trim() !== '')
{
for (var col = 0; col < maxCol; col++)
{
if (col > 0 && !nullLine)
if (col > 0)
csv += _delimiter;
var colIdx = hasHeader && dataKeyedByField ? fields[col] : col;
csv += safe(data[row][colIdx], col);
}
if (row < data.length - 1 && (!skipEmptyLines || (maxCol > 0 && !nullLine)))
{
if (row < data.length - 1 && (!skipEmptyLines || maxCol > 0))
csv += _newline;
}
}
}
return csv;
}
@ -446,7 +430,7 @@ License: MIT @@ -446,7 +430,7 @@ License: MIT
if (str.constructor === Date)
return JSON.stringify(str).slice(1, 25);
str = str.toString().replace(quoteCharRegex, _escapedQuote);
str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar);
var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
|| (Array.isArray(_quotes) && _quotes[col])
@ -473,7 +457,6 @@ License: MIT @@ -473,7 +457,6 @@ License: MIT
this._handle = null;
this._finished = false;
this._completed = false;
this._halted = false;
this._input = null;
this._baseIndex = 0;
this._partialLine = '';
@ -498,7 +481,6 @@ License: MIT @@ -498,7 +481,6 @@ License: MIT
chunk = modifiedChunk;
}
this.isFirstChunk = false;
this._halted = false;
// Rejoin the line we likely just split in two by chunking the file
var aggregate = this._partialLine + chunk;
@ -506,10 +488,8 @@ License: MIT @@ -506,10 +488,8 @@ License: MIT
var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);
if (this._handle.paused() || this._handle.aborted()) {
this._halted = true;
if (this._handle.paused() || this._handle.aborted())
return;
}
var lastIndex = results.meta.cursor;
@ -535,10 +515,8 @@ License: MIT @@ -535,10 +515,8 @@ License: MIT
else if (isFunction(this._config.chunk) && !isFakeChunk)
{
this._config.chunk(results, this._handle);
if (this._handle.paused() || this._handle.aborted()) {
this._halted = true;
if (this._handle.paused() || this._handle.aborted())
return;
}
results = undefined;
this._completeResults = undefined;
}
@ -656,6 +634,7 @@ License: MIT @@ -656,6 +634,7 @@ License: MIT
{
var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive
xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end);
xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672
}
try {
@ -902,12 +881,14 @@ License: MIT @@ -902,12 +881,14 @@ License: MIT
this._onCsvData = function(results)
{
var data = results.data;
if (!stream.push(data) && !this._handle.paused()) {
for (var i = 0; i < data.length; i++) {
if (!stream.push(data[i]) && !this._handle.paused()) {
// the writeable consumer buffer has filled up
// so we need to pause until more items
// can be processed
this._handle.pause();
}
}
};
this._onCsvComplete = function()
@ -986,10 +967,8 @@ License: MIT @@ -986,10 +967,8 @@ License: MIT
});
stream.once('finish', bindFunction(this._onWriteComplete, this));
}
if (typeof PAPA_BROWSER_CONTEXT === 'undefined') {
DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype);
DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer;
}
// Use one ParserHandle per entire CSV file or string
@ -998,6 +977,7 @@ License: MIT @@ -998,6 +977,7 @@ License: MIT
// One goal is to minimize the use of regular expressions...
var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
var ISO_DATE = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
var self = this;
var _stepCounter = 0; // Number of times step was called (number of rows parsed)
var _rowCounter = 0; // Number of rows that have been parsed so far
@ -1053,7 +1033,7 @@ License: MIT @@ -1053,7 +1033,7 @@ License: MIT
_delimiterError = false;
if (!_config.delimiter)
{
var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments, _config.delimitersToGuess);
var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments);
if (delimGuess.successful)
_config.delimiter = delimGuess.bestDelimiter;
else
@ -1094,14 +1074,8 @@ License: MIT @@ -1094,14 +1074,8 @@ License: MIT
this.resume = function()
{
if(self.streamer._halted) {
_paused = false;
self.streamer.parseChunk(_input, true);
} else {
// Bugfix: #636 In case the processing hasn't halted yet
// wait for it to halt in order to resume
setTimeout(this.resume, 3);
}
};
this.aborted = function()
@ -1153,25 +1127,18 @@ License: MIT @@ -1153,25 +1127,18 @@ License: MIT
{
if (!_results)
return;
function addHeder(header)
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
for (var j = 0; j < _results.data[i].length; j++)
{
if (isFunction(_config.transformHeader))
header = _config.transformHeader(header);
var header = _results.data[i][j];
_fields.push(header);
if (_config.trimHeaders) {
header = header.trim();
}
if (Array.isArray(_results.data[0]))
{
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
_results.data[i].forEach(addHeder);
_results.data.splice(0, 1);
_fields.push(header);
}
// if _results.data[0] is not an array, we are in a step where _results.data is the row.
else
_results.data.forEach(addHeder);
_results.data.splice(0, 1);
}
function shouldApplyDynamicTyping(field) {
@ -1205,15 +1172,15 @@ License: MIT @@ -1205,15 +1172,15 @@ License: MIT
if (!_results || (!_config.header && !_config.dynamicTyping && !_config.transform))
return _results;
function processRow(rowSource, i)
for (var i = 0; i < _results.data.length; i++)
{
var row = _config.header ? {} : [];
var j;
for (j = 0; j < rowSource.length; j++)
for (j = 0; j < _results.data[i].length; j++)
{
var field = j;
var value = rowSource[j];
var value = _results.data[i][j];
if (_config.header)
field = j >= _fields.length ? '__parsed_extra' : _fields[j];
@ -1232,6 +1199,7 @@ License: MIT @@ -1232,6 +1199,7 @@ License: MIT
row[field] = value;
}
_results.data[i] = row;
if (_config.header)
{
@ -1240,36 +1208,23 @@ License: MIT @@ -1240,36 +1208,23 @@ License: MIT
else if (j < _fields.length)
addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i);
}
return row;
}
var incrementBy = 1;
if (!_results.data[0] || Array.isArray(_results.data[0]))
{
_results.data = _results.data.map(processRow);
incrementBy = _results.data.length;
}
else
_results.data = processRow(_results.data, 0);
if (_config.header && _results.meta)
_results.meta.fields = _fields;
_rowCounter += incrementBy;
_rowCounter += _results.data.length;
return _results;
}
function guessDelimiter(input, newline, skipEmptyLines, comments, delimitersToGuess)
function guessDelimiter(input, newline, skipEmptyLines, comments)
{
var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
var bestDelim, bestDelta, fieldCountPrevRow;
delimitersToGuess = delimitersToGuess || [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
for (var i = 0; i < delimitersToGuess.length; i++)
for (var i = 0; i < delimChoices.length; i++)
{
var delim = delimitersToGuess[i];
var delim = delimChoices[i];
var delta = 0, avgFieldCount = 0, emptyLinesCount = 0;
fieldCountPrevRow = undefined;
@ -1292,7 +1247,7 @@ License: MIT @@ -1292,7 +1247,7 @@ License: MIT
if (typeof fieldCountPrevRow === 'undefined')
{
fieldCountPrevRow = 0;
fieldCountPrevRow = fieldCount;
continue;
}
else if (fieldCount > 1)
@ -1305,7 +1260,7 @@ License: MIT @@ -1305,7 +1260,7 @@ License: MIT
if (preview.data.length > 0)
avgFieldCount /= (preview.data.length - emptyLinesCount);
if ((typeof bestDelta === 'undefined' || delta > bestDelta)
if ((typeof bestDelta === 'undefined' || delta < bestDelta)
&& avgFieldCount > 1.99)
{
bestDelta = delta;
@ -1394,7 +1349,7 @@ License: MIT @@ -1394,7 +1349,7 @@ License: MIT
// Comment character must be valid
if (comments === delim)
throw new Error('Comment character same as delimiter');
throw 'Comment character same as delimiter';
else if (comments === true)
comments = '#';
else if (typeof comments !== 'string'
@ -1413,7 +1368,7 @@ License: MIT @@ -1413,7 +1368,7 @@ License: MIT
{
// For some reason, in Chrome, this speeds things up (!?)
if (typeof input !== 'string')
throw new Error('Input must be a string');
throw 'Input must be a string';
// We don't need to compute some of these every time parse() is called,
// but having them in a more local scope seems to perform better
@ -1464,8 +1419,8 @@ License: MIT @@ -1464,8 +1419,8 @@ License: MIT
var nextDelim = input.indexOf(delim, cursor);
var nextNewline = input.indexOf(newline, cursor);
var quoteCharRegex = new RegExp(escapeRegExp(escapeChar) + escapeRegExp(quoteChar), 'g');
var quoteSearch = input.indexOf(quoteChar, cursor);
var quoteCharRegex = new RegExp(escapeChar.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') + quoteChar, 'g');
var quoteSearch;
// Parser loop
for (;;)
@ -1530,12 +1485,6 @@ License: MIT @@ -1530,12 +1485,6 @@ License: MIT
{
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen;
// If char after following delimiter is not quoteChar, we find next quote char position
if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen] !== quoteChar)
{
quoteSearch = input.indexOf(quoteChar, cursor);
}
nextDelim = input.indexOf(delim, cursor);
nextNewline = input.indexOf(newline, cursor);
break;
@ -1549,7 +1498,6 @@ License: MIT @@ -1549,7 +1498,6 @@ License: MIT
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen);
nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field
quoteSearch = input.indexOf(quoteChar, cursor); // we search for first quote in next line
if (stepIsFunction)
{
@ -1596,28 +1544,11 @@ License: MIT @@ -1596,28 +1544,11 @@ License: MIT
// Next delimiter comes before next newline, so we've reached end of field
if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1))
{
// we check, if we have quotes, because delimiter char may be part of field enclosed in quotes
if (quoteSearch !== -1) {
// we have quotes, so we try to find the next delimiter not enclosed in quotes and also next starting quote char
var nextDelimObj = getNextUnqotedDelimiter(nextDelim, quoteSearch, nextNewline);
// if we have next delimiter char which is not enclosed in quotes
if (nextDelimObj && nextDelimObj.nextDelim) {
nextDelim = nextDelimObj.nextDelim;
quoteSearch = nextDelimObj.quoteSearch;
row.push(input.substring(cursor, nextDelim));
cursor = nextDelim + delimLen;
// we look for next delimiter char
nextDelim = input.indexOf(delim, cursor);
continue;
}
} else {
row.push(input.substring(cursor, nextDelim));
cursor = nextDelim + delimLen;
nextDelim = input.indexOf(delim, cursor);
continue;
}
}
// End of row
if (nextNewline !== -1)
@ -1699,11 +1630,10 @@ License: MIT @@ -1699,11 +1630,10 @@ License: MIT
}
/** Returns an object with the results, errors, and meta. */
function returnable(stopped, step)
function returnable(stopped)
{
var isStep = step || false;
return {
data: isStep ? data[0] : data,
data: data,
errors: errors,
meta: {
delimiter: delim,
@ -1718,44 +1648,10 @@ License: MIT @@ -1718,44 +1648,10 @@ License: MIT
/** Executes the user's step function and resets data & errors. */
function doStep()
{
step(returnable(undefined, true));
step(returnable());
data = [];
errors = [];
}
/** Gets the delimiter character, which is not inside the quoted field */
function getNextUnqotedDelimiter(nextDelim, quoteSearch, newLine) {
var result = {
nextDelim: undefined,
quoteSearch: undefined
};
// get the next closing quote character
var nextQuoteSearch = input.indexOf(quoteChar, quoteSearch + 1);
// if next delimiter is part of a field enclosed in quotes
if (nextDelim > quoteSearch && nextDelim < nextQuoteSearch && (nextQuoteSearch < newLine || newLine === -1)) {
// get the next delimiter character after this one
var nextNextDelim = input.indexOf(delim, nextQuoteSearch);
// if there is no next delimiter, return default result
if (nextNextDelim === -1) {
return result;
}
// find the next opening quote char position
if (nextNextDelim > nextQuoteSearch) {
nextQuoteSearch = input.indexOf(quoteChar, nextQuoteSearch + 1);
}
// try to get the next delimiter position
result = getNextUnqotedDelimiter(nextNextDelim, nextQuoteSearch, newLine);
} else {
result = {
nextDelim: nextDelim,
quoteSearch: quoteSearch
};
}
return result;
}
};
/** Sets the abort flag */
@ -1772,12 +1668,26 @@ License: MIT @@ -1772,12 +1668,26 @@ License: MIT
}
// If you need to load Papa Parse asynchronously and you also need worker threads, hard-code
// the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358
function getScriptPath()
{
var scripts = document.getElementsByTagName('script');
return scripts.length ? scripts[scripts.length - 1].src : '';
}
function newWorker()
{
if (!Papa.WORKERS_SUPPORTED)
return false;
var workerUrl = getWorkerBlob();
if (!LOADED_SYNC && Papa.SCRIPT_PATH === null)
throw new Error(
'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' +
'You need to set Papa.SCRIPT_PATH manually.'
);
var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH;
// Append 'papaworker' to the search string to tell papaparse that this is our worker.
workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker';
var w = new global.Worker(workerUrl);
w.onmessage = mainThreadReceivedMessage;
w.id = workerIdCounter++;
@ -1812,7 +1722,7 @@ License: MIT @@ -1812,7 +1722,7 @@ License: MIT
for (var i = 0; i < msg.results.data.length; i++)
{
worker.userStep({
data: msg.results.data[i],
data: [msg.results.data[i]],
errors: msg.results.errors,
meta: msg.results.meta
}, handle);
@ -1841,7 +1751,7 @@ License: MIT @@ -1841,7 +1751,7 @@ License: MIT
}
function notImplemented() {
throw new Error('Not implemented.');
throw 'Not implemented.';
}
/** Callback when worker thread receives a message */

13
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "papaparse",
"version": "5.3.2",
"version": "4.6.3",
"description": "Fast and powerful CSV parser for the browser that supports web workers and streaming large files. Converts CSV to JSON and JSON to CSV.",
"keywords": [
"csv",
@ -42,16 +42,17 @@ @@ -42,16 +42,17 @@
"eslint": "^4.19.1",
"grunt": "^1.0.2",
"grunt-contrib-uglify": "^3.3.0",
"mocha": "^5.2.0",
"mocha-headless-chrome": "^4.0.0",
"open": "7.0.0",
"mocha": "^3.5.0",
"mocha-phantomjs": "^4.1.0",
"open": "0.0.5",
"phantomjs-prebuilt": "^2.1.16",
"serve-static": "^1.7.1"
},
"scripts": {
"lint": "eslint --no-ignore papaparse.js Gruntfile.js .eslintrc.js 'tests/**/*.js'",
"test-browser": "node tests/test.js",
"test-mocha-headless-chrome": "node tests/test.js --mocha-headless-chrome",
"test-phantomjs": "node tests/test.js --phantomjs",
"test-node": "mocha tests/node-tests.js tests/test-cases.js",
"test": "npm run lint && npm run test-node && npm run test-mocha-headless-chrome"
"test": "npm run lint && npm run test-node && npm run test-phantomjs"
}
}

330
papaparse.js

@ -1,10 +1,19 @@ @@ -1,10 +1,19 @@
/* @license
Papa Parse
v5.3.2
v4.6.3
https://github.com/mholt/PapaParse
License: MIT
*/
// Polyfills
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray#Polyfill
if (!Array.isArray)
{
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
(function(root, factory)
{
/* globals define */
@ -25,10 +34,7 @@ License: MIT @@ -25,10 +34,7 @@ License: MIT
// Browser globals (root is window)
root.Papa = factory();
}
// in strict mode we cannot access arguments.callee, so we need a named reference to
// stringify the factory method for the blob worker
// eslint-disable-next-line func-name
}(this, function moduleFactory()
}(this, function()
{
'use strict';
@ -45,15 +51,9 @@ License: MIT @@ -45,15 +51,9 @@ License: MIT
return {};
})();
function getWorkerBlob() {
var URL = global.URL || global.webkitURL || null;
var code = moduleFactory.toString();
return Papa.BLOB_URL || (Papa.BLOB_URL = URL.createObjectURL(new Blob(['(', code, ')();'], {type: 'text/javascript'})));
}
var IS_WORKER = !global.document && !!global.postMessage,
IS_PAPA_WORKER = IS_WORKER && /blob:/i.test((global.location || {}).protocol);
IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search),
LOADED_SYNC = false, AUTO_SCRIPT_PATH;
var workers = {}, workerIdCounter = 0;
var Papa = {};
@ -66,6 +66,7 @@ License: MIT @@ -66,6 +66,7 @@ License: MIT
Papa.BYTE_ORDER_MARK = '\ufeff';
Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK];
Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker;
Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously
Papa.NODE_STREAM_INPUT = 1;
// Configurable chunk sizes for local and remote files, respectively
@ -183,6 +184,23 @@ License: MIT @@ -183,6 +184,23 @@ License: MIT
{
global.onmessage = workerThreadReceivedMessage;
}
else if (Papa.WORKERS_SUPPORTED)
{
AUTO_SCRIPT_PATH = getScriptPath();
// Check if the script was loaded synchronously
if (!document.body)
{
// Body doesn't exist yet, must be synchronous
LOADED_SYNC = true;
}
else
{
document.addEventListener('DOMContentLoaded', function() {
LOADED_SYNC = true;
}, true);
}
}
@ -273,18 +291,9 @@ License: MIT @@ -273,18 +291,9 @@ License: MIT
/** quote character */
var _quoteChar = '"';
/** escaped quote character, either "" or <config.escapeChar>" */
var _escapedQuote = _quoteChar + _quoteChar;
/** whether to skip empty lines */
var _skipEmptyLines = false;
/** the columns (keys) we expect when we unparse objects */
var _columns = null;
/** whether to prevent outputting cells that can be parsed as formulae by spreadsheet software (Excel and LibreOffice) */
var _escapeFormulae = false;
unpackConfig();
var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g');
@ -297,7 +306,7 @@ License: MIT @@ -297,7 +306,7 @@ License: MIT
if (!_input.length || Array.isArray(_input[0]))
return serialize(null, _input, _skipEmptyLines);
else if (typeof _input[0] === 'object')
return serialize(_columns || Object.keys(_input[0]), _input, _skipEmptyLines);
return serialize(objectKeys(_input[0]), _input, _skipEmptyLines);
}
else if (typeof _input === 'object')
{
@ -307,14 +316,12 @@ License: MIT @@ -307,14 +316,12 @@ License: MIT
if (Array.isArray(_input.data))
{
if (!_input.fields)
_input.fields = _input.meta && _input.meta.fields || _columns;
_input.fields = _input.meta && _input.meta.fields;
if (!_input.fields)
_input.fields = Array.isArray(_input.data[0])
? _input.fields
: typeof _input.data[0] === 'object'
? Object.keys(_input.data[0])
: [];
: objectKeys(_input.data[0]);
if (!(Array.isArray(_input.data[0])) && typeof _input.data[0] !== 'object')
_input.data = [_input.data]; // handles input like [1,2,3] or ['asdf']
@ -324,7 +331,7 @@ License: MIT @@ -324,7 +331,7 @@ License: MIT
}
// Default (any valid paths should return before this)
throw new Error('Unable to serialize unrecognized input');
throw 'exception: Unable to serialize unrecognized input';
function unpackConfig()
@ -339,7 +346,6 @@ License: MIT @@ -339,7 +346,6 @@ License: MIT
}
if (typeof _config.quotes === 'boolean'
|| typeof _config.quotes === 'function'
|| Array.isArray(_config.quotes))
_quotes = _config.quotes;
@ -355,21 +361,18 @@ License: MIT @@ -355,21 +361,18 @@ License: MIT
if (typeof _config.header === 'boolean')
_writeHeader = _config.header;
if (Array.isArray(_config.columns)) {
if (_config.columns.length === 0) throw new Error('Option columns is empty');
_columns = _config.columns;
}
if (_config.escapeChar !== undefined) {
_escapedQuote = _config.escapeChar + _quoteChar;
}
if (typeof _config.escapeFormulae === 'boolean' || _config.escapeFormulae instanceof RegExp) {
_escapeFormulae = _config.escapeFormulae instanceof RegExp ? _config.escapeFormulae : /^[=+\-@\t\r].*$/;
}
/** 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 */
@ -444,25 +447,16 @@ License: MIT @@ -444,25 +447,16 @@ License: MIT
if (str.constructor === Date)
return JSON.stringify(str).slice(1, 25);
var needsQuotes = false;
if (_escapeFormulae && typeof str === "string" && _escapeFormulae.test(str)) {
str = "'" + str;
needsQuotes = true;
}
var escapedQuoteStr = str.toString().replace(quoteCharRegex, _escapedQuote);
str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar);
needsQuotes = needsQuotes
|| _quotes === true
|| (typeof _quotes === 'function' && _quotes(str, col))
var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
|| (Array.isArray(_quotes) && _quotes[col])
|| hasAny(escapedQuoteStr, Papa.BAD_DELIMITERS)
|| escapedQuoteStr.indexOf(_delimiter) > -1
|| escapedQuoteStr.charAt(0) === ' '
|| escapedQuoteStr.charAt(escapedQuoteStr.length - 1) === ' ';
|| hasAny(str, Papa.BAD_DELIMITERS)
|| str.indexOf(_delimiter) > -1
|| str.charAt(0) === ' '
|| str.charAt(str.length - 1) === ' ';
return needsQuotes ? _quoteChar + escapedQuoteStr + _quoteChar : escapedQuoteStr;
return needsQuotes ? _quoteChar + str + _quoteChar : str;
}
function hasAny(str, substrings)
@ -480,7 +474,6 @@ License: MIT @@ -480,7 +474,6 @@ License: MIT
this._handle = null;
this._finished = false;
this._completed = false;
this._halted = false;
this._input = null;
this._baseIndex = 0;
this._partialLine = '';
@ -505,7 +498,6 @@ License: MIT @@ -505,7 +498,6 @@ License: MIT
chunk = modifiedChunk;
}
this.isFirstChunk = false;
this._halted = false;
// Rejoin the line we likely just split in two by chunking the file
var aggregate = this._partialLine + chunk;
@ -513,10 +505,8 @@ License: MIT @@ -513,10 +505,8 @@ License: MIT
var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);
if (this._handle.paused() || this._handle.aborted()) {
this._halted = true;
if (this._handle.paused() || this._handle.aborted())
return;
}
var lastIndex = results.meta.cursor;
@ -542,10 +532,8 @@ License: MIT @@ -542,10 +532,8 @@ License: MIT
else if (isFunction(this._config.chunk) && !isFakeChunk)
{
this._config.chunk(results, this._handle);
if (this._handle.paused() || this._handle.aborted()) {
this._halted = true;
if (this._handle.paused() || this._handle.aborted())
return;
}
results = undefined;
this._completeResults = undefined;
}
@ -647,7 +635,7 @@ License: MIT @@ -647,7 +635,7 @@ License: MIT
xhr.onerror = bindFunction(this._chunkError, this);
}
xhr.open(this._config.downloadRequestBody ? 'POST' : 'GET', this._input, !IS_WORKER);
xhr.open('GET', this._input, !IS_WORKER);
// Headers can only be set when once the request state is OPENED
if (this._config.downloadRequestHeaders)
{
@ -663,10 +651,11 @@ License: MIT @@ -663,10 +651,11 @@ License: MIT
{
var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive
xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end);
xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672
}
try {
xhr.send(this._config.downloadRequestBody);
xhr.send();
}
catch (err) {
this._chunkError(err.message);
@ -674,6 +663,8 @@ License: MIT @@ -674,6 +663,8 @@ License: MIT
if (IS_WORKER && xhr.status === 0)
this._chunkError();
else
this._start += this._config.chunkSize;
};
this._chunkLoaded = function()
@ -687,9 +678,7 @@ License: MIT @@ -687,9 +678,7 @@ License: MIT
return;
}
// Use chunckSize as it may be a diference on reponse lentgh due to characters with more than 1 byte
this._start += this._config.chunkSize ? this._config.chunkSize : xhr.responseText.length;
this._finished = !this._config.chunkSize || this._start >= getFileSize(xhr);
this._finished = !this._config.chunkSize || this._start > getFileSize(xhr);
this.parseChunk(xhr.responseText);
};
@ -705,7 +694,7 @@ License: MIT @@ -705,7 +694,7 @@ License: MIT
if (contentRange === null) { // no content range, then finish!
return -1;
}
return parseInt(contentRange.substring(contentRange.lastIndexOf('/') + 1));
return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1));
}
}
NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype);
@ -794,14 +783,8 @@ License: MIT @@ -794,14 +783,8 @@ License: MIT
{
if (this._finished) return;
var size = this._config.chunkSize;
var chunk;
if(size) {
chunk = remaining.substring(0, size);
remaining = remaining.substring(size);
} else {
chunk = remaining;
remaining = '';
}
var chunk = size ? remaining.substr(0, size) : remaining;
remaining = size ? remaining.substr(size) : '';
this._finished = !remaining;
return this.parseChunk(chunk);
};
@ -915,12 +898,14 @@ License: MIT @@ -915,12 +898,14 @@ License: MIT
this._onCsvData = function(results)
{
var data = results.data;
if (!stream.push(data) && !this._handle.paused()) {
for (var i = 0; i < data.length; i++) {
if (!stream.push(data[i]) && !this._handle.paused()) {
// the writeable consumer buffer has filled up
// so we need to pause until more items
// can be processed
this._handle.pause();
}
}
};
this._onCsvComplete = function()
@ -1009,10 +994,9 @@ License: MIT @@ -1009,10 +994,9 @@ License: MIT
function ParserHandle(_config)
{
// One goal is to minimize the use of regular expressions...
var MAX_FLOAT = Math.pow(2, 53);
var MIN_FLOAT = -MAX_FLOAT;
var FLOAT = /^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/;
var ISO_DATE = /^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))$/;
var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
var ISO_DATE = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
var self = this;
var _stepCounter = 0; // Number of times step was called (number of rows parsed)
var _rowCounter = 0; // Number of rows that have been parsed so far
@ -1048,11 +1032,9 @@ License: MIT @@ -1048,11 +1032,9 @@ License: MIT
_stepCounter += results.data.length;
if (_config.preview && _stepCounter > _config.preview)
_parser.abort();
else {
_results.data = _results.data[0];
else
userStep(_results, self);
}
}
};
}
@ -1070,7 +1052,7 @@ License: MIT @@ -1070,7 +1052,7 @@ License: MIT
_delimiterError = false;
if (!_config.delimiter)
{
var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments, _config.delimitersToGuess);
var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments);
if (delimGuess.successful)
_config.delimiter = delimGuess.bestDelimiter;
else
@ -1106,22 +1088,13 @@ License: MIT @@ -1106,22 +1088,13 @@ License: MIT
{
_paused = true;
_parser.abort();
// If it is streaming via "chunking", the reader will start appending correctly already so no need to substring,
// otherwise we can get duplicate content within a row
_input = isFunction(_config.chunk) ? "" : _input.substring(_parser.getCharIndex());
_input = _input.substr(_parser.getCharIndex());
};
this.resume = function()
{
if(self.streamer._halted) {
_paused = false;
self.streamer.parseChunk(_input, true);
} else {
// Bugfix: #636 In case the processing hasn't halted yet
// wait for it to halt in order to resume
setTimeout(self.resume, 3);
}
};
this.aborted = function()
@ -1143,16 +1116,6 @@ License: MIT @@ -1143,16 +1116,6 @@ License: MIT
return _config.skipEmptyLines === 'greedy' ? s.join('').trim() === '' : s.length === 1 && s[0].length === 0;
}
function testFloat(s) {
if (FLOAT.test(s)) {
var floatValue = parseFloat(s);
if (floatValue > MIN_FLOAT && floatValue < MAX_FLOAT) {
return true;
}
}
return false;
}
function processResults()
{
if (_results && _delimiterError)
@ -1163,9 +1126,9 @@ License: MIT @@ -1163,9 +1126,9 @@ License: MIT
if (_config.skipEmptyLines)
{
_results.data = _results.data.filter(function(d) {
return !testEmptyLine(d);
});
for (var i = 0; i < _results.data.length; i++)
if (testEmptyLine(_results.data[i]))
_results.data.splice(i--, 1);
}
if (needsHeaderRow())
@ -1183,25 +1146,18 @@ License: MIT @@ -1183,25 +1146,18 @@ License: MIT
{
if (!_results)
return;
function addHeader(header, i)
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
for (var j = 0; j < _results.data[i].length; j++)
{
if (isFunction(_config.transformHeader))
header = _config.transformHeader(header, i);
var header = _results.data[i][j];
_fields.push(header);
if (_config.trimHeaders) {
header = header.trim();
}
if (Array.isArray(_results.data[0]))
{
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
_results.data[i].forEach(addHeader);
_results.data.splice(0, 1);
_fields.push(header);
}
// if _results.data[0] is not an array, we are in a step where _results.data is the row.
else
_results.data.forEach(addHeader);
_results.data.splice(0, 1);
}
function shouldApplyDynamicTyping(field) {
@ -1220,7 +1176,7 @@ License: MIT @@ -1220,7 +1176,7 @@ License: MIT
return true;
else if (value === 'false' || value === 'FALSE')
return false;
else if (testFloat(value))
else if (FLOAT.test(value))
return parseFloat(value);
else if (ISO_DATE.test(value))
return new Date(value);
@ -1235,15 +1191,15 @@ License: MIT @@ -1235,15 +1191,15 @@ License: MIT
if (!_results || (!_config.header && !_config.dynamicTyping && !_config.transform))
return _results;
function processRow(rowSource, i)
for (var i = 0; i < _results.data.length; i++)
{
var row = _config.header ? {} : [];
var j;
for (j = 0; j < rowSource.length; j++)
for (j = 0; j < _results.data[i].length; j++)
{
var field = j;
var value = rowSource[j];
var value = _results.data[i][j];
if (_config.header)
field = j >= _fields.length ? '__parsed_extra' : _fields[j];
@ -1262,6 +1218,7 @@ License: MIT @@ -1262,6 +1218,7 @@ License: MIT
row[field] = value;
}
_results.data[i] = row;
if (_config.header)
{
@ -1270,34 +1227,23 @@ License: MIT @@ -1270,34 +1227,23 @@ License: MIT
else if (j < _fields.length)
addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i);
}
return row;
}
var incrementBy = 1;
if (!_results.data.length || Array.isArray(_results.data[0]))
{
_results.data = _results.data.map(processRow);
incrementBy = _results.data.length;
}
else
_results.data = processRow(_results.data, 0);
if (_config.header && _results.meta)
_results.meta.fields = _fields;
_rowCounter += incrementBy;
_rowCounter += _results.data.length;
return _results;
}
function guessDelimiter(input, newline, skipEmptyLines, comments, delimitersToGuess) {
var bestDelim, bestDelta, fieldCountPrevRow, maxFieldCount;
delimitersToGuess = delimitersToGuess || [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
function guessDelimiter(input, newline, skipEmptyLines, comments)
{
var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
var bestDelim, bestDelta, fieldCountPrevRow;
for (var i = 0; i < delimitersToGuess.length; i++) {
var delim = delimitersToGuess[i];
for (var i = 0; i < delimChoices.length; i++)
{
var delim = delimChoices[i];
var delta = 0, avgFieldCount = 0, emptyLinesCount = 0;
fieldCountPrevRow = undefined;
@ -1308,19 +1254,23 @@ License: MIT @@ -1308,19 +1254,23 @@ License: MIT
preview: 10
}).parse(input);
for (var j = 0; j < preview.data.length; j++) {
if (skipEmptyLines && testEmptyLine(preview.data[j])) {
for (var j = 0; j < preview.data.length; j++)
{
if (skipEmptyLines && testEmptyLine(preview.data[j]))
{
emptyLinesCount++;
continue;
}
var fieldCount = preview.data[j].length;
avgFieldCount += fieldCount;
if (typeof fieldCountPrevRow === 'undefined') {
fieldCountPrevRow = fieldCount;
if (typeof fieldCountPrevRow === 'undefined')
{
fieldCountPrevRow = 0;
continue;
}
else if (fieldCount > 0) {
else if (fieldCount > 1)
{
delta += Math.abs(fieldCount - fieldCountPrevRow);
fieldCountPrevRow = fieldCount;
}
@ -1329,11 +1279,11 @@ License: MIT @@ -1329,11 +1279,11 @@ License: MIT
if (preview.data.length > 0)
avgFieldCount /= (preview.data.length - emptyLinesCount);
if ((typeof bestDelta === 'undefined' || delta <= bestDelta)
&& (typeof maxFieldCount === 'undefined' || avgFieldCount > maxFieldCount) && avgFieldCount > 1.99) {
if ((typeof bestDelta === 'undefined' || delta > bestDelta)
&& avgFieldCount > 1.99)
{
bestDelta = delta;
bestDelim = delim;
maxFieldCount = avgFieldCount;
}
}
@ -1347,7 +1297,7 @@ License: MIT @@ -1347,7 +1297,7 @@ License: MIT
function guessLineEndings(input, quoteChar)
{
input = input.substring(0, 1024 * 1024); // max length 1 MB
input = input.substr(0, 1024 * 1024); // max length 1 MB
// Replace all the text inside quotes
var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm');
input = input.replace(re, '');
@ -1373,15 +1323,12 @@ License: MIT @@ -1373,15 +1323,12 @@ License: MIT
function addError(type, code, msg, row)
{
var error = {
_results.errors.push({
type: type,
code: code,
message: msg
};
if(row !== undefined) {
error.row = row;
}
_results.errors.push(error);
message: msg,
row: row
});
}
}
@ -1403,7 +1350,8 @@ License: MIT @@ -1403,7 +1350,8 @@ License: MIT
var preview = config.preview;
var fastMode = config.fastMode;
var quoteChar;
if (config.quoteChar === undefined || config.quoteChar === null) {
/** Allows for no quoteChar by setting quoteChar to undefined in config */
if (config.quoteChar === undefined) {
quoteChar = '"';
} else {
quoteChar = config.quoteChar;
@ -1420,7 +1368,7 @@ License: MIT @@ -1420,7 +1368,7 @@ License: MIT
// Comment character must be valid
if (comments === delim)
throw new Error('Comment character same as delimiter');
throw 'Comment character same as delimiter';
else if (comments === true)
comments = '#';
else if (typeof comments !== 'string'
@ -1439,7 +1387,7 @@ License: MIT @@ -1439,7 +1387,7 @@ License: MIT
{
// For some reason, in Chrome, this speeds things up (!?)
if (typeof input !== 'string')
throw new Error('Input must be a string');
throw 'Input must be a string';
// We don't need to compute some of these every time parse() is called,
// but having them in a more local scope seems to perform better
@ -1467,7 +1415,7 @@ License: MIT @@ -1467,7 +1415,7 @@ License: MIT
cursor += newline.length;
else if (ignoreLastRow)
return returnable();
if (comments && row.substring(0, commentsLen) === comments)
if (comments && row.substr(0, commentsLen) === comments)
continue;
if (stepIsFunction)
{
@ -1491,7 +1439,7 @@ License: MIT @@ -1491,7 +1439,7 @@ License: MIT
var nextDelim = input.indexOf(delim, cursor);
var nextNewline = input.indexOf(newline, cursor);
var quoteCharRegex = new RegExp(escapeRegExp(escapeChar) + escapeRegExp(quoteChar), 'g');
var quoteSearch = input.indexOf(quoteChar, cursor);
var quoteSearch;
// Parser loop
for (;;)
@ -1547,27 +1495,15 @@ License: MIT @@ -1547,27 +1495,15 @@ License: MIT
continue;
}
if(nextDelim !== -1 && nextDelim < (quoteSearch + 1)) {
nextDelim = input.indexOf(delim, (quoteSearch + 1));
}
if(nextNewline !== -1 && nextNewline < (quoteSearch + 1)) {
nextNewline = input.indexOf(newline, (quoteSearch + 1));
}
// Check up to nextDelim or nextNewline, whichever is closest
var checkUpTo = nextNewline === -1 ? nextDelim : Math.min(nextDelim, nextNewline);
var spacesBetweenQuoteAndDelimiter = extraSpaces(checkUpTo);
// Closing quote followed by delimiter or 'unnecessary spaces + delimiter'
if (input.substr(quoteSearch + 1 + spacesBetweenQuoteAndDelimiter, delimLen) === delim)
if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter] === delim)
{
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen;
// If char after following delimiter is not quoteChar, we find next quote char position
if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen] !== quoteChar)
{
quoteSearch = input.indexOf(quoteChar, cursor);
}
nextDelim = input.indexOf(delim, cursor);
nextNewline = input.indexOf(newline, cursor);
break;
@ -1576,12 +1512,11 @@ License: MIT @@ -1576,12 +1512,11 @@ License: MIT
var spacesBetweenQuoteAndNewLine = extraSpaces(nextNewline);
// Closing quote followed by newline or 'unnecessary spaces + newLine'
if (input.substring(quoteSearch + 1 + spacesBetweenQuoteAndNewLine, quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen) === newline)
if (input.substr(quoteSearch + 1 + spacesBetweenQuoteAndNewLine, newlineLen) === newline)
{
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen);
nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field
quoteSearch = input.indexOf(quoteChar, cursor); // we search for first quote in next line
if (stepIsFunction)
{
@ -1615,7 +1550,7 @@ License: MIT @@ -1615,7 +1550,7 @@ License: MIT
}
// Comment found at start of new line
if (comments && row.length === 0 && input.substring(cursor, cursor + commentsLen) === comments)
if (comments && row.length === 0 && input.substr(cursor, commentsLen) === comments)
{
if (nextNewline === -1) // Comment ends at EOF
return returnable();
@ -1630,7 +1565,6 @@ License: MIT @@ -1630,7 +1565,6 @@ License: MIT
{
row.push(input.substring(cursor, nextDelim));
cursor = nextDelim + delimLen;
// we look for next delimiter char
nextDelim = input.indexOf(delim, cursor);
continue;
}
@ -1691,7 +1625,7 @@ License: MIT @@ -1691,7 +1625,7 @@ License: MIT
if (ignoreLastRow)
return returnable();
if (typeof value === 'undefined')
value = input.substring(cursor);
value = input.substr(cursor);
row.push(value);
cursor = inputLen; // important in case parsing is paused
pushRow(row);
@ -1753,12 +1687,26 @@ License: MIT @@ -1753,12 +1687,26 @@ License: MIT
}
// If you need to load Papa Parse asynchronously and you also need worker threads, hard-code
// the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358
function getScriptPath()
{
var scripts = document.getElementsByTagName('script');
return scripts.length ? scripts[scripts.length - 1].src : '';
}
function newWorker()
{
if (!Papa.WORKERS_SUPPORTED)
return false;
var workerUrl = getWorkerBlob();
if (!LOADED_SYNC && Papa.SCRIPT_PATH === null)
throw new Error(
'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' +
'You need to set Papa.SCRIPT_PATH manually.'
);
var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH;
// Append 'papaworker' to the search string to tell papaparse that this is our worker.
workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker';
var w = new global.Worker(workerUrl);
w.onmessage = mainThreadReceivedMessage;
w.id = workerIdCounter++;
@ -1793,7 +1741,7 @@ License: MIT @@ -1793,7 +1741,7 @@ License: MIT
for (var i = 0; i < msg.results.data.length; i++)
{
worker.userStep({
data: msg.results.data[i],
data: [msg.results.data[i]],
errors: msg.results.errors,
meta: msg.results.meta
}, handle);
@ -1822,7 +1770,7 @@ License: MIT @@ -1822,7 +1770,7 @@ License: MIT
}
function notImplemented() {
throw new Error('Not implemented.');
throw 'Not implemented.';
}
/** Callback when worker thread receives a message */

4
papaparse.min.js vendored

File diff suppressed because one or more lines are too long

2
player/player.html

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
<title>Papa Parse Player</title>
<meta charset="utf-8">
<link rel="stylesheet" href="player.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="../papaparse.js"></script>
<script src="player.js"></script>
</head>

51
tests/node-tests.js

@ -41,42 +41,6 @@ describe('PapaParse', function() { @@ -41,42 +41,6 @@ describe('PapaParse', function() {
assertLongSampleParsedCorrectly(Papa.parse(longSampleRawCsv));
});
it('Pause and resume works (Regression Test for Bug #636)', function(done) {
this.timeout(30000);
var mod200Rows = [
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Lorem ipsum dolor sit","42","ABC"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Etiam a dolor vitae est vestibulum","84"],
["Lorem ipsum dolor sit","42","ABC"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Lorem ipsum dolor sit","42","ABC"],
["Lorem ipsum dolor sit","42"]
];
var stepped = 0;
var dataRows = [];
Papa.parse(fs.createReadStream(__dirname + '/verylong-sample.csv'), {
step: function(results, parser) {
stepped++;
if (results)
{
parser.pause();
parser.resume();
if (results.data && stepped % 200 === 0) {
dataRows.push(results.data);
}
}
},
complete: function() {
assert.strictEqual(2001, stepped);
assert.deepEqual(mod200Rows, dataRows);
done();
}
});
});
it('asynchronously parsed CSV should be correctly parsed', function(done) {
Papa.parse(longSampleRawCsv, {
complete: function(parsedCsv) {
@ -162,21 +126,6 @@ describe('PapaParse', function() { @@ -162,21 +126,6 @@ describe('PapaParse', function() {
});
});
it('piped streaming CSV should be correctly parsed when header is true', function(done) {
var data = [];
var readStream = fs.createReadStream(__dirname + '/sample-header.csv', 'utf8');
var csvStream = readStream.pipe(Papa.parse(Papa.NODE_STREAM_INPUT, {header: true}));
csvStream.on('data', function(item) {
data.push(item);
});
csvStream.on('end', function() {
assert.deepEqual(data[0], { title: 'test title 01', name: 'test name 01' });
assert.deepEqual(data[1], { title: '', name: 'test name 02' });
done();
});
});
it('should support pausing and resuming on same tick when streaming', function(done) {
var rows = [];
Papa.parse(fs.createReadStream(__dirname + '/long-sample.csv', 'utf8'), {

3
tests/sample-header.csv

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
title,name
test title 01,test name 01
,test name 02
1 title name
2 test title 01 test name 01
3 test name 02

590
tests/test-cases.js

@ -7,7 +7,6 @@ if (typeof module !== 'undefined' && module.exports) { @@ -7,7 +7,6 @@ if (typeof module !== 'undefined' && module.exports) {
var assert = chai.assert;
var BASE_PATH = (typeof document === 'undefined') ? './' : document.getElementById('test-cases').src.replace(/test-cases\.js$/, '');
var RECORD_SEP = String.fromCharCode(30);
var UNIT_SEP = String.fromCharCode(31);
var FILES_ENABLED = false;
@ -323,14 +322,6 @@ var CORE_PARSER_TESTS = [ @@ -323,14 +322,6 @@ var CORE_PARSER_TESTS = [
errors: []
}
},
{
description: "Line starts with unquoted empty field",
input: ',b,c\n"d",e,f',
expected: {
data: [['', 'b', 'c'], ['d', 'e', 'f']],
errors: []
}
},
{
description: "Line ends with quoted field",
input: 'a,b,c\nd,e,f\n"g","h","i"\n"j","k","l"',
@ -592,7 +583,7 @@ describe('Core Parser Tests', function() { @@ -592,7 +583,7 @@ describe('Core Parser Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function() {
var actual = new Papa.Parser(test.config).parse(test.input);
assert.deepEqual(actual.errors, test.expected.errors);
assert.deepEqual(JSON.stringify(actual.errors), JSON.stringify(test.expected.errors));
assert.deepEqual(actual.data, test.expected.data);
});
}
@ -670,14 +661,6 @@ var PARSE_TESTS = [ @@ -670,14 +661,6 @@ var PARSE_TESTS = [
errors: []
}
},
{
description: "Misplaced quotes in data twice, not as opening quotes",
input: 'A,B",C\nD,E",F',
expected: {
data: [['A', 'B"', 'C'], ['D', 'E"', 'F']],
errors: []
}
},
{
description: "Mixed slash n and slash r should choose first as precident",
input: 'a,b,c\nd,e,f\rg,h,i\n',
@ -732,23 +715,6 @@ var PARSE_TESTS = [ @@ -732,23 +715,6 @@ var PARSE_TESTS = [
}]
}
},
{
description: "Row with enough fields but blank field in the begining",
input: 'A,B,C\r\n,b1,c1\r\na2,b2,c2',
expected: {
data: [["A", "B", "C"], ['', 'b1', 'c1'], ['a2', 'b2', 'c2']],
errors: []
}
},
{
description: "Row with enough fields but blank field in the begining using headers",
input: 'A,B,C\r\n,b1,c1\r\n,b2,c2',
config: { header: true },
expected: {
data: [{"A": "", "B": "b1", "C": "c1"}, {"A": "", "B": "b2", "C": "c2"}],
errors: []
}
},
{
description: "Row with enough fields but blank field at end",
input: 'A,B,C\r\na,b,',
@ -759,20 +725,11 @@ var PARSE_TESTS = [ @@ -759,20 +725,11 @@ var PARSE_TESTS = [
}
},
{
description: "Header rows are transformed when transformHeader function is provided",
input: 'A,B,C\r\na,b,c',
config: { header: true, transformHeader: function(header) { return header.toLowerCase(); } },
description: "Header rows are trimmed when trimHeaders is set",
input: ' A , B , C \r\na,b ,c',
config: { header: true, trimHeaders: true },
expected: {
data: [{"a": "a", "b": "b", "c": "c"}],
errors: []
}
},
{
description: "transformHeader accepts and optional index attribute",
input: 'A,B,C\r\na,b,c',
config: { header: true, transformHeader: function(header, i) { return i % 2 ? header.toLowerCase() : header; } },
expected: {
data: [{"A": "a", "b": "b", "C": "c"}],
data: [{"A": "a", "B": "b ", "C": "c"}],
errors: []
}
},
@ -847,16 +804,6 @@ var PARSE_TESTS = [ @@ -847,16 +804,6 @@ var PARSE_TESTS = [
errors: []
}
},
{
description: "Multi-character delimiter (length 2) with quoted field",
input: 'a, b, "c, e", d',
config: { delimiter: ", " },
notes: "The quotes must be immediately adjacent to the delimiter to indicate a quoted field",
expected: {
data: [['a', 'b', 'c, e', 'd']],
errors: []
}
},
{
description: "Callback delimiter",
input: 'a$ b$ c',
@ -867,11 +814,11 @@ var PARSE_TESTS = [ @@ -867,11 +814,11 @@ var PARSE_TESTS = [
}
},
{
description: "Dynamic typing converts numeric literals and maintains precision",
input: '1,2.2,1e3\r\n-4,-4.5,-4e-5\r\n-,5a,5-2\r\n16142028098527942586,9007199254740991,-9007199254740992',
description: "Dynamic typing converts numeric literals",
input: '1,2.2,1e3\r\n-4,-4.5,-4e-5\r\n-,5a,5-2',
config: { dynamicTyping: true },
expected: {
data: [[1, 2.2, 1000], [-4, -4.5, -0.00004], ["-", "5a", "5-2"], ["16142028098527942586", 9007199254740991, "-9007199254740992"]],
data: [[1, 2.2, 1000], [-4, -4.5, -0.00004], ["-", "5a", "5-2"]],
errors: []
}
},
@ -965,39 +912,6 @@ var PARSE_TESTS = [ @@ -965,39 +912,6 @@ var PARSE_TESTS = [
errors: []
}
},
{
description: "Custom transform accepts column number also",
input: 'A,B,C\r\nd,e,f',
config: {
transform: function(value, column) {
if (column % 2) {
value = value.toLowerCase();
}
return value;
}
},
expected: {
data: [["A","b","C"], ["d","e","f"]],
errors: []
}
},
{
description: "Custom transform accepts header name when using header",
input: 'A,B,C\r\nd,e,f',
config: {
header: true,
transform: function(value, name) {
if (name === 'B') {
value = value.toUpperCase();
}
return value;
}
},
expected: {
data: [{'A': "d", 'B': "E", 'C': "f"}],
errors: []
}
},
{
description: "Dynamic typing converts ISO date strings to Dates",
input: 'ISO date,long date\r\n2018-05-04T21:08:03.269Z,Fri May 04 2018 14:08:03 GMT-0700 (PDT)\r\n2018-05-08T15:20:22.642Z,Tue May 08 2018 08:20:22 GMT-0700 (PDT)',
@ -1007,15 +921,6 @@ var PARSE_TESTS = [ @@ -1007,15 +921,6 @@ var PARSE_TESTS = [
errors: []
}
},
{
description: "Dynamic typing skips ISO date strings ocurring in other strings",
input: 'ISO date,String with ISO date\r\n2018-05-04T21:08:03.269Z,The date is 2018-05-04T21:08:03.269Z\r\n2018-05-08T15:20:22.642Z,The date is 2018-05-08T15:20:22.642Z',
config: { dynamicTyping: true },
expected: {
data: [["ISO date", "String with ISO date"], [new Date("2018-05-04T21:08:03.269Z"), "The date is 2018-05-04T21:08:03.269Z"], [new Date("2018-05-08T15:20:22.642Z"), "The date is 2018-05-08T15:20:22.642Z"]],
errors: []
}
},
{
description: "Blank line at beginning",
input: '\r\na,b,c\r\nd,e,f',
@ -1264,36 +1169,6 @@ var PARSE_TESTS = [ @@ -1264,36 +1169,6 @@ var PARSE_TESTS = [
errors: []
}
},
{
description: "Pipe delimiter is guessed correctly choose avgFildCount max one",
notes: "Guessing the delimiter should work choose the min delta one and the max one",
config: {},
input: 'a,b,c\na,b,c|d|e|f',
expected: {
data: [['a', 'b', 'c'], ['a','b','c|d|e|f']],
errors: []
}
},
{
description: "Pipe delimiter is guessed correctly when first field are enclosed in quotes and contain delimiter characters",
notes: "Guessing the delimiter should work if the first field is enclosed in quotes, but others are not",
input: '"Field1,1,1";Field2;"Field3";Field4;Field5;Field6',
config: {},
expected: {
data: [['Field1,1,1','Field2','Field3', 'Field4', 'Field5', 'Field6']],
errors: []
}
},
{
description: "Pipe delimiter is guessed correctly when some fields are enclosed in quotes and contain delimiter characters and escaoped quotes",
notes: "Guessing the delimiter should work even if the first field is not enclosed in quotes, but others are",
input: 'Field1;Field2;"Field,3,""3,3";Field4;Field5;"Field6,6"',
config: {},
expected: {
data: [['Field1','Field2','Field,3,"3,3', 'Field4', 'Field5', 'Field6,6']],
errors: []
}
},
{
description: "Single quote as quote character",
notes: "Must parse correctly when single quote is specified as a quote character",
@ -1509,22 +1384,6 @@ var PARSE_TESTS = [ @@ -1509,22 +1384,6 @@ var PARSE_TESTS = [
data: [['a', 'b'], ['c', 'd'], [' , ', ','], ['" "', '""']],
errors: []
}
},
{
description: "Quoted fields with spaces between closing quote and next delimiter and contains delimiter",
input: 'A,",B" ,C,D\nE,F,G,H',
expected: {
data: [['A', ',B', 'C', 'D'],['E', 'F', 'G', 'H']],
errors: []
}
},
{
description: "Quoted fields with spaces between closing quote and newline and contains newline",
input: 'a,b,"c\n" \nd,e,f',
expected: {
data: [['a', 'b', 'c\n'], ['d', 'e', 'f']],
errors: []
}
}
];
@ -1536,7 +1395,7 @@ describe('Parse Tests', function() { @@ -1536,7 +1395,7 @@ describe('Parse Tests', function() {
if (test.expected.meta) {
assert.deepEqual(actual.meta, test.expected.meta);
}
assert.deepEqual(actual.errors, test.expected.errors);
assert.deepEqual(JSON.stringify(actual.errors), JSON.stringify(test.expected.errors));
assert.deepEqual(actual.data, test.expected.data);
});
}
@ -1563,7 +1422,7 @@ var PARSE_ASYNC_TESTS = [ @@ -1563,7 +1422,7 @@ var PARSE_ASYNC_TESTS = [
},
{
description: "Simple download",
input: BASE_PATH + "sample.csv",
input: "sample.csv",
config: {
download: true
},
@ -1575,7 +1434,7 @@ var PARSE_ASYNC_TESTS = [ @@ -1575,7 +1434,7 @@ var PARSE_ASYNC_TESTS = [
},
{
description: "Simple download + worker",
input: BASE_PATH + "sample.csv",
input: "tests/sample.csv",
config: {
worker: true,
download: true
@ -1608,31 +1467,6 @@ var PARSE_ASYNC_TESTS = [ @@ -1608,31 +1467,6 @@ var PARSE_ASYNC_TESTS = [
data: [['A','B','C'],['X','Y','Z']],
errors: []
}
},
{
description: "File with a few regular and lots of empty lines",
disabled: !FILES_ENABLED,
input: FILES_ENABLED ? new File(["A,B,C\nX,Y,Z\n" + new Array(500000).fill(",,").join("\n")], "sample.csv") : false,
config: {
skipEmptyLines: "greedy"
},
expected: {
data: [['A','B','C'],['X','Y','Z']],
errors: []
}
},
{
description: "File with a few regular and lots of empty lines + worker",
disabled: !FILES_ENABLED,
input: FILES_ENABLED ? new File(["A,B,C\nX,Y,Z\n" + new Array(500000).fill(",,").join("\n")], "sample.csv") : false,
config: {
worker: true,
skipEmptyLines: "greedy"
},
expected: {
data: [['A','B','C'],['X','Y','Z']],
errors: []
}
}
];
@ -1642,7 +1476,7 @@ describe('Parse Async Tests', function() { @@ -1642,7 +1476,7 @@ describe('Parse Async Tests', function() {
var config = test.config;
config.complete = function(actual) {
assert.deepEqual(actual.errors, test.expected.errors);
assert.deepEqual(JSON.stringify(actual.errors), JSON.stringify(test.expected.errors));
assert.deepEqual(actual.data, test.expected.data);
done();
};
@ -1748,12 +1582,6 @@ var UNPARSE_TESTS = [ @@ -1748,12 +1582,6 @@ var UNPARSE_TESTS = [
config: { delimiter: ', ' },
expected: 'A, b, c\r\nd, e, f'
},
{
description: "Custom delimiter (Multi-character), field contains custom delimiter",
input: [['A', 'b', 'c'], ['d', 'e', 'f, g']],
config: { delimiter: ', ' },
expected: 'A, b, c\r\nd, e, "f, g"'
},
{
description: "Bad delimiter (\\n)",
notes: "Should default to comma",
@ -1803,18 +1631,6 @@ var UNPARSE_TESTS = [ @@ -1803,18 +1631,6 @@ var UNPARSE_TESTS = [
config: { quotes: [true, false, true] },
expected: '"Col1",Col2,"Col3"\r\n"a",b,"c"\r\n"d",e,"f"'
},
{
description: "Force quotes around string fields only",
input: [['a', 'b', 'c'], ['d', 10, true]],
config: { quotes: function(value) { return typeof value === 'string'; } },
expected: '"a","b","c"\r\n"d",10,true'
},
{
description: "Force quotes around string fields only (with header row)",
input: [{ "Col1": "a", "Col2": "b", "Col3": "c" }, { "Col1": "d", "Col2": 10, "Col3": true }],
config: { quotes: function(value) { return typeof value === 'string'; } },
expected: '"Col1","Col2","Col3"\r\n"a","b","c"\r\n"d",10,true'
},
{
description: "Empty input",
input: [],
@ -1861,9 +1677,9 @@ var UNPARSE_TESTS = [ @@ -1861,9 +1677,9 @@ var UNPARSE_TESTS = [
},
{
description: "Returns without rows with no content when skipEmptyLines is 'greedy'",
input: [[null, ' '], [], ['1', '2']].concat(new Array(500000).fill(['', ''])).concat([['3', '4']]),
input: [[null, ' '], [], ['1', '2']],
config: {skipEmptyLines: 'greedy'},
expected: '1,2\r\n3,4'
expected: '1,2'
},
{
description: "Returns empty rows when empty rows are passed and skipEmptyLines is false with headers",
@ -1882,87 +1698,7 @@ var UNPARSE_TESTS = [ @@ -1882,87 +1698,7 @@ var UNPARSE_TESTS = [
input: [{a: null, b: ' '}, {}, {a: '1', b: '2'}],
config: {skipEmptyLines: 'greedy', header: true},
expected: 'a,b\r\n1,2'
},
{
description: "Column option used to manually specify keys",
notes: "Should not throw any error when attempting to serialize key not present in object. Columns are different than keys of the first object. When an object is missing a key then the serialized value should be an empty string.",
input: [{a: 1, b: '2'}, {}, {a: 3, d: 'd', c: 4,}],
config: {columns: ['a', 'b', 'c']},
expected: 'a,b,c\r\n1,2,\r\n\r\n3,,4'
},
{
description: "Column option used to manually specify keys with input type object",
notes: "Should not throw any error when attempting to serialize key not present in object. Columns are different than keys of the first object. When an object is missing a key then the serialized value should be an empty string.",
input: { data: [{a: 1, b: '2'}, {}, {a: 3, d: 'd', c: 4,}] },
config: {columns: ['a', 'b', 'c']},
expected: 'a,b,c\r\n1,2,\r\n\r\n3,,4'
},
{
description: "Use different escapeChar",
input: [{a: 'foo', b: '"quoted"'}],
config: {header: false, escapeChar: '\\'},
expected: 'foo,"\\"quoted\\""'
},
{
description: "test defeault escapeChar",
input: [{a: 'foo', b: '"quoted"'}],
config: {header: false},
expected: 'foo,"""quoted"""'
},
{
description: "Escape formulae",
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
config: { escapeFormulae: true },
expected: 'Col1,Col2,Col3\r\n"\'=danger","\'@danger",safe\r\nsafe=safe,"\'+danger","\'-danger, danger"\r\n\'+safe,\'@safe,"safe, safe"'
},
{
description: "Don't escape formulae by default",
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
expected: 'Col1,Col2,Col3\r\n=danger,@danger,safe\r\nsafe=safe,+danger,"-danger, danger"\r\n\'+safe,\'@safe,"safe, safe"'
},
{
description: "Escape formulae with forced quotes",
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
config: { escapeFormulae: true, quotes: true },
expected: '"Col1","Col2","Col3"\r\n"\'=danger","\'@danger","safe"\r\n"safe=safe","\'+danger","\'-danger, danger"\r\n"\'+safe","\'@safe","safe, safe"'
},
{
description: "Escape formulae with single-quote quoteChar and escapeChar",
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
config: { escapeFormulae: true, quoteChar: "'", escapeChar: "'" },
expected: 'Col1,Col2,Col3\r\n\'\'\'=danger\',\'\'\'@danger\',safe\r\nsafe=safe,\'\'\'+danger\',\'\'\'-danger, danger\'\r\n\'\'+safe,\'\'@safe,\'safe, safe\''
},
{
description: "Escape formulae with single-quote quoteChar and escapeChar and forced quotes",
input: [{ "Col1": "=danger", "Col2": "@danger", "Col3": "safe" }, { "Col1": "safe=safe", "Col2": "+danger", "Col3": "-danger, danger" }, { "Col1": "'+safe", "Col2": "'@safe", "Col3": "safe, safe" }],
config: { escapeFormulae: true, quotes: true, quoteChar: "'", escapeChar: "'" },
expected: '\'Col1\',\'Col2\',\'Col3\'\r\n\'\'\'=danger\',\'\'\'@danger\',\'safe\'\r\n\'safe=safe\',\'\'\'+danger\',\'\'\'-danger, danger\'\r\n\'\'\'+safe\',\'\'\'@safe\',\'safe, safe\''
},
// new escapeFormulae values:
{
description: "Escape formulae with tab and carriage-return",
input: [{ "Col1": "\tdanger", "Col2": "\rdanger,", "Col3": "safe\t\r" }],
config: { escapeFormulae: true },
expected: 'Col1,Col2,Col3\r\n"\'\tdanger","\'\rdanger,","safe\t\r"'
},
{
description: "Escape formulae with tab and carriage-return, with forced quotes",
input: [{ "Col1": " danger", "Col2": "\rdanger,", "Col3": "safe\t\r" }],
config: { escapeFormulae: true, quotes: true },
expected: '"Col1","Col2","Col3"\r\n"\'\tdanger","\'\rdanger,","safe\t\r"'
},
{
description: "Escape formulae with tab and carriage-return, with single-quote quoteChar and escapeChar",
input: [{ "Col1": " danger", "Col2": "\rdanger,", "Col3": "safe, \t\r" }],
config: { escapeFormulae: true, quoteChar: "'", escapeChar: "'" },
expected: 'Col1,Col2,Col3\r\n\'\'\'\tdanger\',\'\'\'\rdanger,\',\'safe, \t\r\''
},
{
description: "Escape formulae with tab and carriage-return, with single-quote quoteChar and escapeChar and forced quotes",
input: [{ "Col1": " danger", "Col2": "\rdanger,", "Col3": "safe, \t\r" }],
config: { escapeFormulae: true, quotes: true, quoteChar: "'", escapeChar: "'" },
expected: '\'Col1\',\'Col2\',\'Col3\'\r\n\'\'\'\tdanger\',\'\'\'\rdanger,\',\'safe, \t\r\''
},
}
];
describe('Unparse Tests', function() {
@ -1991,151 +1727,6 @@ describe('Unparse Tests', function() { @@ -1991,151 +1727,6 @@ describe('Unparse Tests', function() {
var CUSTOM_TESTS = [
{
description: "Pause and resume works (Regression Test for Bug #636)",
disabled: !XHR_ENABLED,
timeout: 30000,
expected: [2001, [
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Lorem ipsum dolor sit","42","ABC"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Etiam a dolor vitae est vestibulum","84"],
["Lorem ipsum dolor sit","42","ABC"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Etiam a dolor vitae est vestibulum","84","DEF"],
["Lorem ipsum dolor sit","42","ABC"],
["Lorem ipsum dolor sit","42"]
], 0],
run: function(callback) {
var stepped = 0;
var dataRows = [];
var errorCount = 0;
var output = [];
Papa.parse(BASE_PATH + "verylong-sample.csv", {
download: true,
step: function(results, parser) {
stepped++;
if (results)
{
parser.pause();
parser.resume();
if (results.data && stepped % 200 === 0) {
dataRows.push(results.data);
}
}
},
complete: function() {
output.push(stepped);
output.push(dataRows);
output.push(errorCount);
callback(output);
}
});
}
},
{
description: "Pause and resume works for chunks with NetworkStreamer",
disabled: !XHR_ENABLED,
timeout: 30000,
expected: ["Etiam a dolor vitae est vestibulum", "84", "DEF"],
run: function(callback) {
var chunkNum = 0;
Papa.parse(BASE_PATH + "verylong-sample.csv", {
download: true,
chunkSize: 1000,
chunk: function(results, parser) {
chunkNum++;
parser.pause();
if (chunkNum === 2) {
callback(results.data[0]);
return;
}
parser.resume();
},
complete: function() {
callback(new Error("Should have found matched row before parsing whole file"));
}
});
}
},
{
description: "Pause and resume works for chunks with FileStreamer",
disabled: !XHR_ENABLED,
timeout: 30000,
expected: ["Etiam a dolor vitae est vestibulum", "84", "DEF"],
run: function(callback) {
var chunkNum = 0;
var xhr = new XMLHttpRequest();
xhr.onload = function() {
Papa.parse(new File([xhr.responseText], './verylong-sample.csv'), {
chunkSize: 1000,
chunk: function(results, parser) {
chunkNum++;
parser.pause();
if (chunkNum === 2) {
callback(results.data[0]);
return;
}
parser.resume();
},
complete: function() {
callback(new Error("Should have found matched row before parsing whole file"));
}
});
};
xhr.open("GET", BASE_PATH + "verylong-sample.csv");
try {
xhr.send();
} catch (err) {
callback(err);
return;
}
}
},
{
description: "Pause and resume works for chunks with StringStreamer",
disabled: !XHR_ENABLED,
timeout: 30000,
// Test also with string as byte size may be diferent
expected: ["Etiam a dolor vitae est vestibulum", "84", "DEF"],
run: function(callback) {
var chunkNum = 0;
var xhr = new XMLHttpRequest();
xhr.onload = function() {
Papa.parse(xhr.responseText, {
chunkSize: 1000,
chunk: function(results, parser) {
chunkNum++;
parser.pause();
if (chunkNum === 2) {
callback(results.data[0]);
return;
}
parser.resume();
},
complete: function() {
callback(new Error("Should have found matched row before parsing whole file"));
}
});
};
xhr.open("GET", BASE_PATH + "verylong-sample.csv");
try {
xhr.send();
} catch (err) {
callback(err);
return;
}
}
},
{
description: "Complete is called with all results if neither step nor chunk is defined",
expected: [['A', 'b', 'c'], ['d', 'E', 'f'], ['G', 'h', 'i']],
@ -2164,93 +1755,13 @@ var CUSTOM_TESTS = [ @@ -2164,93 +1755,13 @@ var CUSTOM_TESTS = [
});
}
},
{
description: "Data is correctly parsed with steps",
expected: [['A', 'b', 'c'], ['d', 'E', 'f']],
run: function(callback) {
var data = [];
Papa.parse('A,b,c\nd,E,f', {
step: function(results) {
data.push(results.data);
},
complete: function() {
callback(data);
}
});
}
},
{
description: "Data is correctly parsed with steps (headers)",
expected: [{One: 'A', Two: 'b', Three: 'c'}, {One: 'd', Two: 'E', Three: 'f'}],
run: function(callback) {
var data = [];
Papa.parse('One,Two,Three\nA,b,c\nd,E,f', {
header: true,
step: function(results) {
data.push(results.data);
},
complete: function() {
callback(data);
}
});
}
},
{
description: "Data is correctly parsed with steps and worker (headers)",
expected: [{One: 'A', Two: 'b', Three: 'c'}, {One: 'd', Two: 'E', Three: 'f'}],
run: function(callback) {
var data = [];
Papa.parse('One,Two,Three\nA,b,c\nd,E,f', {
header: true,
worker: true,
step: function(results) {
data.push(results.data);
},
complete: function() {
callback(data);
}
});
}
},
{
description: "Data is correctly parsed with steps and worker",
expected: [['A', 'b', 'c'], ['d', 'E', 'f']],
run: function(callback) {
var data = [];
Papa.parse('A,b,c\nd,E,f', {
worker: true,
step: function(results) {
data.push(results.data);
},
complete: function() {
callback(data);
}
});
}
},
{
description: "Data is correctly parsed with steps when skipping empty lines",
expected: [['A', 'b', 'c'], ['d', 'E', 'f']],
run: function(callback) {
var data = [];
Papa.parse('A,b,c\n\nd,E,f', {
skipEmptyLines: true,
step: function(results) {
data.push(results.data);
},
complete: function() {
callback(data);
}
});
}
},
{
description: "Step is called with the contents of the row",
expected: ['A', 'b', 'c'],
run: function(callback) {
Papa.parse('A,b,c', {
step: function(response) {
callback(response.data);
callback(response.data[0]);
}
});
}
@ -2276,7 +1787,7 @@ var CUSTOM_TESTS = [ @@ -2276,7 +1787,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
step: function(response) {
updates.push(response.meta.cursor);
@ -2293,7 +1804,7 @@ var CUSTOM_TESTS = [ @@ -2293,7 +1804,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
chunkSize: 500,
step: function(response) {
@ -2311,7 +1822,7 @@ var CUSTOM_TESTS = [ @@ -2311,7 +1822,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
chunkSize: 500,
worker: true,
@ -2330,7 +1841,7 @@ var CUSTOM_TESTS = [ @@ -2330,7 +1841,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
chunkSize: 500,
chunk: function(response) {
@ -2348,7 +1859,7 @@ var CUSTOM_TESTS = [ @@ -2348,7 +1859,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
chunkSize: 500,
chunk: function(response) {
@ -2472,7 +1983,7 @@ var CUSTOM_TESTS = [ @@ -2472,7 +1983,7 @@ var CUSTOM_TESTS = [
Papa.parse(new File(['A,B,C\nX,"Y\n1\n2\n3",Z'], 'sample.csv'), {
chunkSize: 3,
step: function(response) {
updates.push(response.data);
updates.push(response.data[0]);
},
complete: function() {
callback(updates);
@ -2487,7 +1998,7 @@ var CUSTOM_TESTS = [ @@ -2487,7 +1998,7 @@ var CUSTOM_TESTS = [
var updates = [];
Papa.parse('A,b,c\nd,E,f\nG,h,i', {
step: function(response, handle) {
updates.push(response.data);
updates.push(response.data[0]);
handle.abort();
callback(updates);
},
@ -2517,7 +2028,7 @@ var CUSTOM_TESTS = [ @@ -2517,7 +2028,7 @@ var CUSTOM_TESTS = [
var updates = [];
Papa.parse('A,b,c\nd,E,f\nG,h,i', {
step: function(response, handle) {
updates.push(response.data);
updates.push(response.data[0]);
handle.pause();
callback(updates);
},
@ -2536,7 +2047,7 @@ var CUSTOM_TESTS = [ @@ -2536,7 +2047,7 @@ var CUSTOM_TESTS = [
var first = true;
Papa.parse('A,b,c\nd,E,f\nG,h,i', {
step: function(response, h) {
updates.push(response.data);
updates.push(response.data[0]);
if (!first) return;
handle = h;
handle.pause();
@ -2557,7 +2068,7 @@ var CUSTOM_TESTS = [ @@ -2557,7 +2068,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = 0;
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
worker: true,
download: true,
chunkSize: 500,
@ -2577,7 +2088,7 @@ var CUSTOM_TESTS = [ @@ -2577,7 +2088,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = 0;
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
chunkSize: 500,
beforeFirstChunk: function(chunk) {
@ -2598,7 +2109,7 @@ var CUSTOM_TESTS = [ @@ -2598,7 +2109,7 @@ var CUSTOM_TESTS = [
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = 0;
Papa.parse(BASE_PATH + "long-sample.csv", {
Papa.parse("/tests/long-sample.csv", {
download: true,
chunkSize: 500,
beforeFirstChunk: function(chunk) {
@ -2613,33 +2124,44 @@ var CUSTOM_TESTS = [ @@ -2613,33 +2124,44 @@ var CUSTOM_TESTS = [
}
},
{
description: "Should correctly guess custom delimiter when passed delimiters to guess.",
expected: "~",
description: "Should not assume we own the worker unless papaworker is in the search string",
disabled: typeof Worker === 'undefined',
expected: [false, true, true, true, true],
run: function(callback) {
var results = Papa.parse('"A"~"B"~"C"~"D"', {
delimitersToGuess: ['~', '@', '%']
var searchStrings = [
'',
'?papaworker',
'?x=1&papaworker',
'?x=1&papaworker&y=1',
'?x=1&papaworker=1'
];
var results = searchStrings.map(function() { return false; });
var workers = [];
// Give it .5s to do something
setTimeout(function() {
workers.forEach(function(w) { w.terminate(); });
callback(results);
}, 500);
searchStrings.forEach(function(searchString, idx) {
var w = new Worker('../papaparse.js' + searchString);
workers.push(w);
w.addEventListener('message', function() {
results[idx] = true;
});
w.postMessage({input: 'a,b,c\n1,2,3'});
});
callback(results.meta.delimiter);
}
},
{
description: "Should still correctly guess default delimiters when delimiters to guess are not given.",
expected: ",",
run: function(callback) {
var results = Papa.parse('"A","B","C","D"');
callback(results.meta.delimiter);
}
}
];
describe('Custom Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function(done) {
if(test.timeout) {
this.timeout(test.timeout);
}
test.run(function(actual) {
assert.deepEqual(actual, test.expected);
assert.deepEqual(JSON.stringify(actual), JSON.stringify(test.expected));
done();
});
});

4
tests/test.js

@ -5,8 +5,8 @@ var path = require('path'); @@ -5,8 +5,8 @@ var path = require('path');
var childProcess = require('child_process');
var server = connect().use(serveStatic(path.join(__dirname, '/..'))).listen(8071, function() {
if (process.argv.indexOf('--mocha-headless-chrome') !== -1) {
childProcess.spawn('node_modules/.bin/mocha-headless-chrome', ['-f', 'http://localhost:8071/tests/tests.html'], {
if (process.argv.indexOf('--phantomjs') !== -1) {
childProcess.spawn('node_modules/.bin/mocha-phantomjs', ['http://localhost:8071/tests/tests.html'], {
stdio: 'inherit'
}).on('exit', function(code) {
server.close();

7
tests/tests.html

@ -9,14 +9,19 @@ @@ -9,14 +9,19 @@
<script src="../node_modules/chai/chai.js"></script>
<script>mocha.setup('bdd')</script>
<script src="test-cases.js" id="test-cases"></script>
<script src="test-cases.js"></script>
</head>
<body>
<div id="mocha"></div>
<script>
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
} else {
mocha.checkLeaks();
mocha.run();
}
</script>
</body>
</html>

2
tests/verylong-sample.csv

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
placeholder,meaning of life,TLD
Lorem ipsum dolor sit,42,ABC
Etiam a dolor vitae est vestibulum,84,DEF
"Lorem ipsum dolor sit",42,ABC
Lorem ipsum dolor sit,42,ABC
Etiam a dolor vitae est vestibulum,84,DEF
Etiam a dolor vitae est vestibulum,84,DEF
Lorem ipsum dolor sit,42,ABC

1 placeholder meaning of life TLD
2 Lorem ipsum dolor sit 42 ABC
3 Etiam a dolor vitae est vestibulum 84 DEF
4 Lorem ipsum dolor sit 42 ABC
5 Etiam a dolor vitae est vestibulum 84 DEF
6 Etiam a dolor vitae est vestibulum 84 DEF
7 Lorem ipsum dolor sit 42 ABC
Loading…
Cancel
Save