Skip to content

Instantly share code, notes, and snippets.

@JoeRobich
Last active December 25, 2015 03:29

Revisions

  1. JoeRobich revised this gist Oct 9, 2013. 1 changed file with 61 additions and 66 deletions.
    127 changes: 61 additions & 66 deletions CSVReader.as
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    package com.thedevstop
    {
    import flash.events.ErrorEvent;
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.utils.setTimeout;
    @@ -9,7 +9,8 @@ package com.thedevstop
    [Event(name="error", type="flash.events.ErrorEvent")]
    public class CSVReader extends EventDispatcher
    {
    public var hasHeader:Boolean;
    private var _hasHeaders:Boolean;
    private var _lastHeader:String;

    private var _csv:String;
    private var _position:int;
    @@ -18,30 +19,13 @@ package com.thedevstop
    private var _records:Array;

    private var _error:Error;
    private var _complete:Boolean;

    public function read(csv:String):void
    {
    this._csv = csv;
    this._error = null;
    this._complete = false;
    this._position = 0;
    this._headers = [];
    this._records = [];

    doLater(continueRead);
    }
    public var separator:String = ",";

    public function getError():Error
    {
    return this._error;
    }

    public function getComplete():Boolean
    {
    return this._complete;
    }

    public function getHeaders():Array
    {
    return this._headers;
    @@ -52,33 +36,45 @@ package com.thedevstop
    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.hasHeader && !this._headers.length)
    if (this._hasHeaders && !this._headers.length)
    readHeader();
    else
    readRecord();

    if (!this._complete)
    if (atEOF())
    onComplete();
    else
    doLater(continueRead);
    }
    catch (e:Error)
    {
    error(e);
    onError(e);
    }
    }

    private function complete():void
    private function onComplete():void
    {
    this._complete = true;
    this.dispatchEvent(new Event(Event.COMPLETE));
    }

    private function error(e:Error):void
    private function onError(e:Error):void
    {
    this._complete = true;
    this._error = e;
    this.dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
    }
    @@ -90,17 +86,16 @@ package com.thedevstop

    while (!atEOL() && !atEOF())
    {
    readComma();
    readSeparator();

    headerName = readName();
    this._headers.push(headerName);
    }

    _lastHeader = headerName;

    if (atEOL())
    readEOL();

    if (atEOF())
    complete();
    }

    private function readRecord():void
    @@ -109,55 +104,53 @@ package com.thedevstop

    var fieldIndex:int = 0;
    var fieldValue:String = readField();
    addFieldValue(record, fieldIndex, fieldValue);
    addFieldValue(record, fieldIndex++, fieldValue);

    while (!atEOL() && !atEOF())
    {
    readComma();
    readSeparator();

    fieldIndex++;
    fieldValue = readField();
    addFieldValue(record, fieldIndex, fieldValue);
    addFieldValue(record, fieldIndex++, fieldValue);
    }

    if (!record.hasOwnProperty(this._headers[this._headers.length - 1]))
    throw new Error("Too few fields on row at position: " + this._position);
    if (!this._hasHeaders)
    {
    this._lastHeader = this._headers[this._headers.length - 1];
    this._hasHeaders = true;
    }

    this._records.push(record);
    if (!record.hasOwnProperty(this._lastHeader))
    throw new Error("Too few fields on record at position: " + this._position);

    if (!this.hasHeader)
    this.hasHeader = true;
    this._records.push(record);

    if (atEOL())
    readEOL();

    if (atEOF())
    complete();
    }

    private function addFieldValue(record:Object, fieldIndex:int, fieldValue:String):void
    {
    var headerName:String;

    if (fieldIndex < this._headers.length)
    {
    headerName = this._headers[fieldIndex];
    }
    else
    if (fieldIndex >= this._headers.length)
    {
    if (this.hasHeader)
    throw new Error("Too many fields on row at position: " + this._position);
    if (this._hasHeaders)
    throw new Error("Too many fields on record at position: " + this._position);

    var a:int = 'A'.charCodeAt(0);
    var letter = String.fromCharCode((fieldIndex % 26) + a);
    var repeats = ((fieldIndex / 26) | 0) + 1;
    headerName = Array(repeats + 1).join(letter);
    this._headers.push(headerName);
    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();
    @@ -196,9 +189,9 @@ package com.thedevstop
    continue;
    }

    if (atComma())
    if (atSeparator())
    {
    text += readComma();
    text += readSeparator();
    continue;
    }

    @@ -256,15 +249,15 @@ package com.thedevstop
    return readCharacter() + readCharacter();
    }

    private function atComma():Boolean
    private function atSeparator():Boolean
    {
    return peek() == ",";
    return peek() == separator;
    }

    private function readComma():String
    private function readSeparator():String
    {
    if (!atComma())
    throw Error("Expected Comma at position: " + this._position);
    if (!atSeparator())
    throw Error("Expected Separator at position: " + this._position);

    return readCharacter();
    }
    @@ -284,10 +277,12 @@ package com.thedevstop

    private function atTextData():Boolean
    {
    if (atSeparator())
    return false;

    var charCode:int = peek().charCodeAt(0);
    return (charCode >= 0x20 && charCode <= 0x21) ||
    (charCode >= 0x23 && charCode <= 0x2B) ||
    (charCode >= 0x2D && charCode <= 0x7E);
    (charCode >= 0x23 && charCode <= 0x7E);
    }

    private function readTextData():String
  2. JoeRobich created this gist Oct 9, 2013.
    375 changes: 375 additions & 0 deletions CSVReader.as
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,375 @@
    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
    {
    public var hasHeader:Boolean;

    private var _csv:String;
    private var _position:int;

    private var _headers:Array;
    private var _records:Array;

    private var _error:Error;
    private var _complete:Boolean;

    public function read(csv:String):void
    {
    this._csv = csv;
    this._error = null;
    this._complete = false;
    this._position = 0;
    this._headers = [];
    this._records = [];

    doLater(continueRead);
    }

    public function getError():Error
    {
    return this._error;
    }

    public function getComplete():Boolean
    {
    return this._complete;
    }

    public function getHeaders():Array
    {
    return this._headers;
    }

    public function getRecords():Array
    {
    return this._records;
    }

    private function continueRead():void
    {
    try
    {
    if (this.hasHeader && !this._headers.length)
    readHeader();
    else
    readRecord();

    if (!this._complete)
    doLater(continueRead);
    }
    catch (e:Error)
    {
    error(e);
    }
    }

    private function complete():void
    {
    this._complete = true;
    this.dispatchEvent(new Event(Event.COMPLETE));
    }

    private function error(e:Error):void
    {
    this._complete = true;
    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())
    {
    readComma();

    headerName = readName();
    this._headers.push(headerName);
    }

    if (atEOL())
    readEOL();

    if (atEOF())
    complete();
    }

    private function readRecord():void
    {
    var record:Object = { };

    var fieldIndex:int = 0;
    var fieldValue:String = readField();
    addFieldValue(record, fieldIndex, fieldValue);

    while (!atEOL() && !atEOF())
    {
    readComma();

    fieldIndex++;
    fieldValue = readField();
    addFieldValue(record, fieldIndex, fieldValue);
    }

    if (!record.hasOwnProperty(this._headers[this._headers.length - 1]))
    throw new Error("Too few fields on row at position: " + this._position);

    this._records.push(record);

    if (!this.hasHeader)
    this.hasHeader = true;

    if (atEOL())
    readEOL();

    if (atEOF())
    complete();
    }

    private function addFieldValue(record:Object, fieldIndex:int, fieldValue:String):void
    {
    var headerName:String;

    if (fieldIndex < this._headers.length)
    {
    headerName = this._headers[fieldIndex];
    }
    else
    {
    if (this.hasHeader)
    throw new Error("Too many fields on row at position: " + this._position);

    var a:int = 'A'.charCodeAt(0);
    var letter = String.fromCharCode((fieldIndex % 26) + a);
    var repeats = ((fieldIndex / 26) | 0) + 1;
    headerName = Array(repeats + 1).join(letter);
    this._headers.push(headerName);
    }

    record[headerName] = fieldValue;
    }

    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 (atComma())
    {
    text += readComma();
    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 atComma():Boolean
    {
    return peek() == ",";
    }

    private function readComma():String
    {
    if (!atComma())
    throw Error("Expected Comma 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
    {
    var charCode:int = peek().charCodeAt(0);
    return (charCode >= 0x20 && charCode <= 0x21) ||
    (charCode >= 0x23 && charCode <= 0x2B) ||
    (charCode >= 0x2D && 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);
    }
    }
    }