Skip to content

Instantly share code, notes, and snippets.

@JoeRobich
Last active December 25, 2015 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoeRobich/6910065 to your computer and use it in GitHub Desktop.
Save JoeRobich/6910065 to your computer and use it in GitHub Desktop.
Asynchronous CSV Reader. Reads CSV file into an array of record objects. If header row exists then objects will have properties named after the headers, otherwise it will generate header names (ex. "A", "B",...,"AA","BB",...). Conforms to RFC4180 (http://tools.ietf.org/html/rfc4180) except that the separator character is now configurable.
package com.thedevstop
{
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.utils.setTimeout;
[Event(name="complete", type="flash.events.Event")]
[Event(name="error", type="flash.events.ErrorEvent")]
public class CSVReader extends EventDispatcher
{
private var _hasHeaders:Boolean;
private var _lastHeader:String;
private var _csv:String;
private var _position:int;
private var _headers:Array;
private var _records:Array;
private var _error:Error;
public var separator:String = ",";
public function getError():Error
{
return this._error;
}
public function getHeaders():Array
{
return this._headers;
}
public function getRecords():Array
{
return this._records;
}
public function read(csv:String, hasHeaders:Boolean=false):void
{
this._csv = csv;
this._hasHeaders = hasHeaders;
this._error = null;
this._position = 0;
this._headers = [];
this._records = [];
doLater(continueRead);
}
private function continueRead():void
{
try
{
if (this._hasHeaders && !this._headers.length)
readHeader();
else
readRecord();
if (atEOF())
onComplete();
else
doLater(continueRead);
}
catch (e:Error)
{
onError(e);
}
}
private function onComplete():void
{
this.dispatchEvent(new Event(Event.COMPLETE));
}
private function onError(e:Error):void
{
this._error = e;
this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
}
private function readHeader():void
{
var headerName:String = readName();
this._headers.push(headerName);
while (!atEOL() && !atEOF())
{
readSeparator();
headerName = readName();
this._headers.push(headerName);
}
_lastHeader = headerName;
if (atEOL())
readEOL();
}
private function readRecord():void
{
var record:Object = { };
var fieldIndex:int = 0;
var fieldValue:String = readField();
addFieldValue(record, fieldIndex++, fieldValue);
while (!atEOL() && !atEOF())
{
readSeparator();
fieldValue = readField();
addFieldValue(record, fieldIndex++, fieldValue);
}
if (!this._hasHeaders)
{
this._lastHeader = this._headers[this._headers.length - 1];
this._hasHeaders = true;
}
if (!record.hasOwnProperty(this._lastHeader))
throw new Error("Too few fields on record at position: " + this._position);
this._records.push(record);
if (atEOL())
readEOL();
}
private function addFieldValue(record:Object, fieldIndex:int, fieldValue:String):void
{
if (fieldIndex >= this._headers.length)
{
if (this._hasHeaders)
throw new Error("Too many fields on record at position: " + this._position);
this._headers.push(generateHeaderName(fieldIndex));
}
var headerName:String = this._headers[fieldIndex];
record[headerName] = fieldValue;
}
private function generateHeaderName(fieldIndex:int):String
{
var a:int = 'A'.charCodeAt(0);
var letter:String = String.fromCharCode((fieldIndex % 26) + a);
var repeats:int = ((fieldIndex / 26) | 0) + 1;
return Array(repeats + 1).join(letter);
}
private function readName():String
{
return readField();
}
private function readField():String
{
eatWhiteSpace();
if (atEOL() || atEOF())
throw Error("Expected Field at position: " + this._position);
var field:String;
if (atDoubleQuote())
field = readEscaped();
else
field = readNonEscaped();
eatWhiteSpace();
return field;
}
private function readEscaped():String
{
var text:String = "";
readDoubleQuote();
while(true)
{
if (atTextData())
{
text += readTextData();
continue;
}
if (atSeparator())
{
text += readSeparator();
continue;
}
if (atCarriageReturn())
{
text += readCarriageReturn();
continue;
}
if (atLineFeed())
{
text += readLineFeed();
continue;
}
if (atTwoDoubleQuotes())
{
text += readTwoDoubleQuotes();
continue;
}
break;
}
readDoubleQuote();
return text;
}
private function readNonEscaped():String
{
var text:String = "";
while (atTextData())
text += readTextData();
return text;
}
private function atEOF():Boolean
{
return this._position == this._csv.length;
}
private function atEOL():Boolean
{
return peek() == "\r" && peekNext() == "\n";
}
private function readEOL():String
{
if (!atEOL())
throw Error("Expected EOL at position: " + this._position);
return readCharacter() + readCharacter();
}
private function atSeparator():Boolean
{
return peek() == separator;
}
private function readSeparator():String
{
if (!atSeparator())
throw Error("Expected Separator at position: " + this._position);
return readCharacter();
}
private function atDoubleQuote():Boolean
{
return peek() == '"';
}
private function readDoubleQuote():String
{
if (!atDoubleQuote())
throw Error("Expected DoubleQuote at position: " + this._position);
return readCharacter();
}
private function atTextData():Boolean
{
if (atSeparator())
return false;
var charCode:int = peek().charCodeAt(0);
return (charCode >= 0x20 && charCode <= 0x21) ||
(charCode >= 0x23 && charCode <= 0x7E);
}
private function readTextData():String
{
if (!atTextData())
throw Error("Expected TextData at position: " + this._position);
return readCharacter();
}
private function atCarriageReturn():Boolean
{
return peek() == "\r";
}
private function readCarriageReturn():String
{
if (!atCarriageReturn())
throw Error("Expected CarriageReturn at position: " + this._position);
return readCharacter();
}
private function atLineFeed():Boolean
{
return peek() == "\n";
}
private function readLineFeed():String
{
if (!atLineFeed())
throw Error("Expected LineFeed at position: " + this._position);
return readCharacter();
}
private function atTwoDoubleQuotes():Boolean
{
return peek() == '"' && peekNext() == '"';
}
private function readTwoDoubleQuotes():String
{
if (!atTwoDoubleQuotes())
throw Error("Expected TwoDoubleQuotes at position: " + this._position);
readCharacter();
return readCharacter();
}
private function peek():String
{
return this._csv.charAt(this._position);
}
private function peekNext():String
{
if (this._position + 1 == this._csv.length)
return "";
return this._csv.charAt(this._position + 1);
}
private function readCharacter():String
{
return this._csv.charAt(this._position++);
}
private function atWhiteSpace():Boolean
{
return peek() == " " || peek() == "\t";
}
private function eatWhiteSpace():void
{
while (atWhiteSpace())
readCharacter();
}
private function doLater(func:Function):void
{
setTimeout(func, 1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment