CsvSheet class
An indexable collection of CSV Values as a spreadsheet.
As spreadsheets (and unlike lists), a CsvSheet is indexed via column and then row, rather than row and column.
Columns can be referenced via their index number (1-based) or
via their column header String
.
CsvSheets can be visualized as such:
1 2 3
.----+----+----.
|col1|col2|col3|
+----+----+----+
1 | 1 | 2 | 3 |
+----+----+----+
2 | 4 | 5 | 6 |
+----+----+----+
3 | 7 | 8 | 9 |
'----+----+----'
// access the value of cell 2,3 (in a spreadsheet as B3)
var value = csvSheet[2][3]; // 8
// If the cell has the header value of 'first name'
var value = csvSheet['col1'][2]; // 4
class CsvSheet { List _contents; HashMap _headers; _CsvColumn _fakeColumn; CsvRow _row; /// Return true if this sheet was created with a header row. bool hasHeaderRow; /** * Creates a CSV Sheet with the given contents. * * If headerRow is provided indicates that the first row of contents is a * header and not part of the data. Default is [false]. * * fieldSep is an optional field seperator if values are not comma separated. * * // tab separated sheet can be parsed with: * var sheet = new CsvSheet(contents, fieldSep: '\t'); * * If you're using a file format which does not end lines with a simple '\n' * you can optionally specify what terminator rows are deliminated with. * * // Usually windows style should automatically be detected anyways. * var sheet = new CsvSheet(contents, lineSep: '\r\n'); */ CsvSheet(String contents, { bool headerRow: false, String fieldSep: ',', String lineSep: '\n' }) { _fakeColumn = new _CsvColumn(this); var allRows = contents.split(lineSep); hasHeaderRow = headerRow; if(hasHeaderRow) { // TODO: Should throw if a header name is repeated. var _rows = allRows[0].split(fieldSep); _headers = new HashMap(); for(var i = 0; i < _rows.length; i ++) { var val = _rows[i]; if(_rows.indexOf(val, i+1) != -1) { throw new FormatException('The header column $val appears more than once'); } _headers[_rows[i]] = i; } _row = new CsvRow(_headers); _contents = new List.generate(allRows.length - 1, (index) => allRows[index+1].split(fieldSep).map((cell) => cell.trim()).toList()); } else { _row = new CsvRow(); _contents = new List.generate(allRows.length, (index) => allRows[index].split(fieldSep).map((cell) => cell.trim()).toList()); } while(_contents.last.length != _contents.first.length) { _contents.removeLast(); } } /** * Access the column specified by [index]. Index may be a 1-based integer * value or a string matching one of the header rows. * * Throws a [RangeError] if invalid index or if a String index is used but * [hasHeaderRow] is false. */ operator [](index) { if(index is String) { var tmp = index; if(!hasHeaderRow) { throw new RangeError('$tmp is not a valid column header'); } if(!_headers.containsKey(index)) throw new RangeError('$tmp is not a valid column header'); index = _headers[index]; _fakeColumn.row = index; } else { _fakeColumn.row = index-1; } return _fakeColumn; } /** * Iterate through each row in the spreadsheet as a CsvRow, calling 'action' * for each row. It is an error if action tries to modify the list. * * The CsvRow is indexable via the headers if applicable. */ void forEachRow(void action(CsvRow cells)) { _contents.forEach((row) { _row.row = row; action(_row); }); } /** * Return the number of rows contained in this sheet. Does not included * the header row, if provided. */ int get numRows => _contents.length; /** * Return the number of columns contained in this sheet. */ int get numCols => _contents[0].length; // Used by _CsvColumn to access rows spreadsheet style instead of list style. _getValue(column, row) => _contents[row][column]; }
Constructors
new CsvSheet(String contents, {bool headerRow: false, String fieldSep: ',', String lineSep: '\n'}) #
Creates a CSV Sheet with the given contents.
If headerRow is provided indicates that the first row of contents is a
header and not part of the data. Default is false
.
fieldSep is an optional field seperator if values are not comma separated.
// tab separated sheet can be parsed with:
var sheet = new CsvSheet(contents, fieldSep: '\t');
If you're using a file format which does not end lines with a simple '\n' you can optionally specify what terminator rows are deliminated with.
// Usually windows style should automatically be detected anyways.
var sheet = new CsvSheet(contents, lineSep: '\r\n');
CsvSheet(String contents, { bool headerRow: false, String fieldSep: ',', String lineSep: '\n' }) { _fakeColumn = new _CsvColumn(this); var allRows = contents.split(lineSep); hasHeaderRow = headerRow; if(hasHeaderRow) { // TODO: Should throw if a header name is repeated. var _rows = allRows[0].split(fieldSep); _headers = new HashMap(); for(var i = 0; i < _rows.length; i ++) { var val = _rows[i]; if(_rows.indexOf(val, i+1) != -1) { throw new FormatException('The header column $val appears more than once'); } _headers[_rows[i]] = i; } _row = new CsvRow(_headers); _contents = new List.generate(allRows.length - 1, (index) => allRows[index+1].split(fieldSep).map((cell) => cell.trim()).toList()); } else { _row = new CsvRow(); _contents = new List.generate(allRows.length, (index) => allRows[index].split(fieldSep).map((cell) => cell.trim()).toList()); } while(_contents.last.length != _contents.first.length) { _contents.removeLast(); } }
Properties
bool hasHeaderRow #
Return true if this sheet was created with a header row.
bool hasHeaderRow
final int numCols #
Return the number of columns contained in this sheet.
int get numCols => _contents[0].length;
final int numRows #
Return the number of rows contained in this sheet. Does not included the header row, if provided.
int get numRows => _contents.length;
Operators
dynamic operator [](index) #
Access the column specified by index. Index may be a 1-based integer value or a string matching one of the header rows.
Throws a RangeError
if invalid index or if a String index is used but
hasHeaderRow is false.
operator [](index) { if(index is String) { var tmp = index; if(!hasHeaderRow) { throw new RangeError('$tmp is not a valid column header'); } if(!_headers.containsKey(index)) throw new RangeError('$tmp is not a valid column header'); index = _headers[index]; _fakeColumn.row = index; } else { _fakeColumn.row = index-1; } return _fakeColumn; }
Methods
void forEachRow(void action(CsvRow cells)) #
Iterate through each row in the spreadsheet as a CsvRow, calling 'action' for each row. It is an error if action tries to modify the list.
The CsvRow is indexable via the headers if applicable.
void forEachRow(void action(CsvRow cells)) { _contents.forEach((row) { _row.row = row; action(_row); }); }