From a681880965e8a724e6bb7a68bb8057f518c701e7 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Mon, 7 Jan 2019 18:54:19 +0100 Subject: [PATCH] directly export and import lists as csv files, under construction --- HISTORY.md | 3 ++ snap.html | 4 +-- src/lists.js | 89 ++++++++++++++++++++++++++++++++++++++++++-------- src/objects.js | 37 ++++++++++++++++++--- 4 files changed, 112 insertions(+), 21 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 69c70437..cfd2023c 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,9 @@ ## in development +### 2019-01-07 +* Lists, Objects: directly export and import lists as csv files, under construction + ### 2019-01-04 * Objects, Blocks, Threads: new feature/block: sense colors and sprites anywhere * updated German translation diff --git a/snap.html b/snap.html index abbb5824..b1f8aa77 100755 --- a/snap.html +++ b/snap.html @@ -8,10 +8,10 @@ - + - + diff --git a/src/lists.js b/src/lists.js index 9b9fa467..4e7881c2 100644 --- a/src/lists.js +++ b/src/lists.js @@ -7,7 +7,7 @@ written by Jens Mönig and Brian Harvey jens@moenig.org, bh@cs.berkeley.edu - Copyright (C) 2018 by Jens Mönig and Brian Harvey + Copyright (C) 2019 by Jens Mönig and Brian Harvey This file is part of Snap!. @@ -58,11 +58,11 @@ /*global modules, BoxMorph, HandleMorph, PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph, -CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize, +CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize, isString, MorphicPreferences, TableDialogMorph, SpriteBubbleMorph, SpeechBubbleMorph, TableFrameMorph, TableMorph, Variable, isSnapObject*/ -modules.lists = '2018-March-08'; +modules.lists = '2019-January-07'; var List; var ListWatcherMorph; @@ -80,26 +80,28 @@ var ListWatcherMorph; setters (linked): ----------------- - cons - answer a new list with the given item in front - cdr - answer all but the first element + cons - answer a new list with the given item in front + cdr - answer all but the first element setters (arrayed): ------------------ - add(element, index) - insert the element before the given slot, - put(element, index) - overwrite the element at the given slot - remove(index) - remove the given slot, shortening the list - clear() - remove all elements + add(element, index) - insert the element before the given slot, + put(element, index) - overwrite the element at the given slot + remove(index) - remove the given slot, shortening the list + clear() - remove all elements getters (all hybrid): --------------------- - length() - number of slots - at(index) - element present in specified slot - contains(element) - + length() - number of slots + at(index) - element present in specified slot + contains(element) - conversion: ----------- - asArray() - answer me as JavaScript array - asText() - answer my elements (recursively) concatenated + asArray() - answer me as JavaScript array, convert to arrayed + itemsArray() - answer a JavaScript array shallow copy of myself + asText() - answer my elements (recursively) concatenated + asCSV() - answer a csv-formatted String of myself */ // List instance creation: @@ -399,6 +401,46 @@ List.prototype.becomeLinked = function () { } }; +List.prototype.asCSV = function () { + // RFC 4180 + // Caution, no error catching! + // this method assumes that the list.canBeCSV() + + var items = this.itemsArray(), + rows = []; + + function encodeCell(atomicValue) { + var string = atomicValue.toString(), + cell; + if (string.indexOf('\"') === -1 && (string.indexOf('\n') === -1)) { + return string; + } + cell = ['\"']; + string.split('').forEach(function (letter) { + cell.push(letter); + if (letter === '\"') { + cell.push(letter); + } + }); + cell.push('\"'); + return cell.join(''); + } + + if (items.some(function (any) {return any instanceof List; })) { + // 2-dimensional table + items.forEach(function (item) { + if (item instanceof List) { + rows.push(item.itemsArray().map(encodeCell).join(',')); + } else { + rows.push(encodeCell(item)); + } + }); + return rows.join('\n'); + } + // single row + return items.map(encodeCell).join(','); +}; + // List testing List.prototype.equalTo = function (other) { @@ -447,6 +489,25 @@ List.prototype.equalTo = function (other) { return true; }; +List.prototype.canBeCSV = function () { + return this.itemsArray().every(function (value) { + return !isNaN(+value) || + isString(value) || + value === true || + value === false || + (value instanceof List && value.hasOnlyAtomicData); + }); +}; + +List.prototype.hasOnlyAtomicData = function () { + return this.itemsArray().every(function (value) { + return !isNaN(+value) || + isString(value) || + value === true || + value === false; + }); +}; + // ListWatcherMorph //////////////////////////////////////////////////// /* diff --git a/src/objects.js b/src/objects.js index 96067ef5..c1abcf39 100644 --- a/src/objects.js +++ b/src/objects.js @@ -83,7 +83,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph, AlignmentMorph, Process, XML_Element, VectorPaintEditorMorph*/ -modules.objects = '2019-January-04'; +modules.objects = '2019-January-07'; var SpriteMorph; var StageMorph; @@ -9567,16 +9567,30 @@ WatcherMorph.prototype.userMenu = function () { function readText(aFile) { var frd = new FileReader(); frd.onloadend = function (e) { - myself.target.setVar( - myself.getter, - e.target.result - ); + // +++ needs to be refactored + if (aFile.type.indexOf("csv") || + aFile.name.split('.').pop() + .toLowerCase() === 'csv') { + // catch parsing errors + myself.target.setVar( + myself.getter, + Process.prototype.parseCSV( + e.target.result + ) + ); + } else { + myself.target.setVar( + myself.getter, + e.target.result + ); + } }; if (aFile.type.indexOf("text") === -1) { // special cases for Windows // check the file extension for text-like-ness if (contains( + // +++ avoid doubling ['txt', 'csv', 'xml', 'json', 'tsv'], aFile.name.split('.').pop().toLowerCase() )) { @@ -9620,6 +9634,19 @@ WatcherMorph.prototype.userMenu = function () { ); } ); + } else if (this.currentValue instanceof List && + this.currentValue.canBeCSV()) { // +++ + menu.addItem( + 'export...', + function () { + var ide = myself.parentThatIsA(IDE_Morph); + ide.saveFileAs( + myself.currentValue.asCSV(), + 'text/csv;charset=utf-8', // RFC 4180 + myself.getter // variable name + ); + } + ); } else if (this.currentValue instanceof Context) { vNames = this.currentValue.outerContext.variables.names(); if (vNames.length) {