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) {