<!DOCTYPE html>
<html>

  <head>
    <script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="jquery.csv-0.71.js"></script>
    <script src="script.js"></script>
  </head>

  <body>
    <span id="input">
      <h1>Input</h1>
      <textarea id="csvInput" wrap="off"></textarea>
      <div>
        <button type="button" onclick="convertToMarkup()">Convert</button>
      </div>
    </span>
    <span id="extras"></span>
    <span id="output">
      <h1>Output</h1>  
      <textarea id="markupOutput" wrap="off"></textarea>
    </span>
    <span id="preview"></span>
  </body>
</html>
var csvData;

function setGroup() {
  convertToMarkup($("#headGroup").val())
}

function convertToMarkup(group) {
  var groupByRow = group;
  var groupIndex = -1;
  var tableList;
  
  if(!group)
    csvData = $("#csvInput").val();
  
  var lines = csvData.split("\n");
  lines = lines.filter(function(e){return e});
  
  var headerList = $.csv.toArray(lines[0]);
  
  $("#extras").empty();
  $("#extras").append("<select id='headGroup'></select><button type='button' onclick='setGroup()'>Group</button>")
  
  $.each(headerList, function(key, value) {   
     $('#headGroup')
         .append($("<option></option>")
         .attr("value",value)
         .text(value)); 
  });
  
  if(groupByRow) {
    groupIndex = headerList.indexOf(groupByRow);
    headerList.splice(groupIndex, 1);
    tableList = {};
  }
  else
    tableList = [];
  
  for(i = 1; i < lines.length; i++) {
    var row = $.csv.toArray(lines[i]);
    
    if(groupIndex > -1) {
      var sGroup = row.splice(groupIndex,1)[0];
      
      if(!tableList[sGroup])
        tableList[sGroup] = [];
        
      tableList[sGroup].push(row);
    }
    else
      tableList.push(row);
  }
  
  var sMarkup = "";
  var sHead;
  var sDivider;
  var sCells;
  
  $("#preview").empty();
  
  if(groupByRow)
  {
    
    for(var groupName in tableList)
    {
      sGroup = buildGroup(groupName);
      
      sHead = buildHeader(headerList);
      sDivider = buildDivider(headerList.length);
      sCells = buildCells(tableList[groupName]);
      
      sMarkup = sMarkup.concat(sGroup + "\n\n" + sHead + "\n" + sDivider + "\n" + sCells + "\n");
      sMarkup = sMarkup.replace(/http:\/\/www.reddit.com\/user/g, "/u");
      
      $("#preview").append(buildHTMLTitle(groupName));
      $("#preview").append(buildHTMLTable(headerList, tableList[groupName]));
    }
  } else {
    sHead = buildHeader(headerList);
    sDivider = buildDivider(headerList.length);
    sCells = buildCells(tableList);
    
    sMarkup = sHead + "\n" + sDivider + "\n" + sCells;
    
    $("#preview").append(buildHTMLTable(headerList, tableList))
  }
  
  sMarkup = sMarkup.replace(/http:\/\/www.reddit.com\/user/g, "/u");
  
  $("#markupOutput").html(sMarkup);
}

function buildHTMLTitle(name) {
  return "<hr><h5>" + name + "</h5>"
}
  

function buildHTMLTable(headers, rows) {
  var sTable = "";
  var sHeader = "";
  
  $.each(headers, function(key, value) {
    sHeader = sHeader.concat("\n\t\t<th>" + value + "</th>");
  });
  
  sTable = sTable.concat("\n\t<tr>" + sHeader + "</tr>");
  
  var sRows = "";
  $.each(rows, function(key, row) {
    
    
    sRow = "";
    $.each(row, function(key, value) {
      sRow = sRow.concat("\n\t\t<td>" + value + "</td>");
    });
    
    sRows = sRows.concat("\n\t<tr>" + sRow + "</tr>");
  });
  
  sTable = "<table>" + sTable.concat(sRows) + "</table>";
  sTable = sTable.replace(/http:\/\/www.reddit.com\/user/g, "/u");
  return sTable;
}

function buildGroup(sGroup) {
  return "---\n#####" + sGroup;
}

function buildHeader(headerArray) {
  return "|" + headerArray.join("|") + "|";
}

function buildDivider(columnCount) {
  var divider = "|";
  for(var c = 0; c < columnCount; c++)
    divider = divider.concat("------|");
    
  return divider;
}

function buildCells(cellArray) {
  var sCells = "";
  
  for(var r = 0; r < cellArray.length; r++) {
    if (cellArray[r])
      sCells = sCells.concat("|" + cellArray[r].join("|") + "|\n")
  }
  
  return sCells;
}
/* Styles go here */

textarea {
  height: 300px;
  width: 800px;
}

table {
  border-collapse: collapse;
  margin: 8px;
}

td, th {
  border: 1px solid lightgray;
}

h5 {
  font-size: 1.2em;
  font-weight: bold;
  margin: 0;
  padding: 0;
}
Player Name,Server,Faction,Outfit,Languages,Area of Speciality,Reddit Name,Contact,Notes
NSGDX,Briggs,VS,,English/Hindi,Anything and Everything,http://www.reddit.com/user/NSGDX1,Ingame/reddit,
"60seconds, 60secondsNC",Briggs,VS/NC,,English,Combat Medic,http://www.reddit.com/user/60secondsNC,Reddit/Ingame,
ChillyPhilly27,Briggs,VS,FCLM,English,Infantry/vehicles,http://www.reddit.com/user/ChillyPhilly27,Reddit/Ingame,
BurntScythe,Briggs,VS,ThunderCougarFalconBirds,English,"All round, Heavy assault, Infiltrator, flying",http://www.reddit.com/user/_BurntToast_,Reddit/Ingame,
M0N0 (zero's),Briggs,VS,FCLM,English,All round,http://www.reddit.com/user/Mononz,Reddit/Ingame,
SystemsTwo,Briggs,VS,Trojan Trolls (TROL),English,"Light assault, infiltrator, medic",http://www.reddit.com/user/systemtwo,Ingame,
Rougey,Briggs,VS,Got batteries ?,Australian,"Designated Driver, Redditside/moral support",http://www.reddit.com/user/Rougey,Reddit/Ingame,
ctrlchris,Briggs,VS,Got batteries ?,Australian,Auraxium collecter/Heavy Assault,http://www.reddit.com/user/ctrlchris,Reddit/Ingame,
PixxulPie,Briggs,VS,ThunderCougarFalconBirds,English,Medic/Engineer/Introducing basics,http://www.reddit.com/user/Pixxultech,Reddit/Ingame,
eatenalive,Briggs,VS,Got batteries ?,English,"Speciality - solo libbing, close-mid range infiltrator",http://www.reddit.com/user/eatenalive2,Reddit/Ingame,
"KeilosVS ,Keilos and KeilosNC",Briggs,VS (TR+NC alts),Got batteries ?,English/Australian,"Heavy Assault, engineer, medic and ESF piloting",http://www.reddit.com/user/Keilos,Reddit/ingame,
Sen7ryGun,Briggs,TR,Juggernaut,English. Also NSFW/NSFK English.,"Infantry,combat strategy and tactics, group armoured combat,800+ hours infantry engineer",http://www.reddit.com/user/Sen7ryGun,Reddit/ingame,
Squuiizzy,Briggs,TR,BOTM,English,"All classes,squad and platoon lead, logistics and land vehicle loadouts/game-play",http://www.reddit.com/user/Squiizzy,Reddit/Ingame,
Magikarpin,Briggs,TR,Redback Company,English/'Strayan,"Medic, engineer",http://www.reddit.com/user/Magikarpn,Reddit/Ingame,
KelainOaksreach,Briggs,TR,,English/German,"Lightning, prowler",http://www.reddit.com/user/Elm11,Reddit/Ingame,
Spudmonkey123456,Briggs,NC (vs/tr alts),,English,All round,http://www.reddit.com/user/spudmonkey12345,Reddit/Ingame/Steam,Steam - spudmonkey12345 current name ron burgundy
XCVJoRDANXCV,Briggs,NC,Cannabinoid Optics,English,All round,http://www.reddit.com/user/XCVJoRDANXCV,Reddit/steam,
EagleZ504,Briggs,NC,Renegade 18,English,"Medic, all round",http://www.reddit.com/user/Actual_EagleZ504,Reddit/Ingame,
Esmey,Cobalt,VS,,English/Swedish,Infiltrator/All round,http://www.reddit.com/user/Ztiller1,Reddit/Ingame,
Edexcel,Cobalt,VS,Fools,English,"Fling ESF's, maxes, heavy assault",http://www.reddit.com/user/Edexcel_V,Reddit/Ingame,Alternatively contact anyoen with a fool tag and leave a message (but be patient they may be busy)
McPopovic,Cobalt,VS,Phantom Company,English/German/Swiss,"Heavy, medic,tanks",http://www.reddit.com/user/McPopovic,Reddit/Ingame/Steam,Steam -McPopovic
Solar15,Cobalt,VS,TheVipers,English,All round,http://www.reddit.com/user/SOLAR15,Reddit/Ingame (reddit first please),
SalzVS,Cobalt,VS (+alts),XDC,English,"Infantry, understanding the game",http://www.reddit.com/user/Oliver_Closeov,Reddit/Ingame,
Langesholz,Cobalt,TR,,English/German,,http://www.reddit.com/user/LangesHolz,Reddit/Ingame,
Seal33,Cobalt,TR,,English/German,,http://www.reddit.com/user/Seal33,Reddit/Ingame,
SeyK7 / SeyK7VS,Cobalt,TR/VS,,English/Slovakian/Czech,All round,http://www.reddit.com/user/SeyK7,Reddit/Ingame or tweet @Aveik103,TeamSpeak Required
Marthalion,Cobalt,TR,,English/Swedish,"Air - speciality, All round",http://www.reddit.com/user/Marthalion,Reddit,
LordMcze,Cobalt,NC,,Czech,,http://www.reddit.com/user/LordMcze,Reddit/Ingame,
D4NIN4TOR,Cobalt,NC,,"German (preferred), English",All round,http://www.reddit.com/user/D4NIN4TOR,Reddit/Ingame,
Treadstone01,Connery,VS (TR +NC alts),,English,"Engineer, Heavy assault, lightning, sunderer,galaxy and support roles",http://www.reddit.com/user/treadstone01,Reddit,
ZenosArrow,Connery,VS,,"English, (possible language lessons in exchange)",Infiltrator +basics,http://www.reddit.com/user/bwtaha,Reddit,
Wizard0fOz,Connery,VS,,English/Spanish,All round,http://www.reddit.com/user/Fairyland_Noir,Reddit/Ingame,
Oarc2,Connery,VS,,English,All round,http://www.reddit.com/user/Oarc2,Reddit/Ingame,
Trooper454,Connery,TR,,English ,All round,http://www.reddit.com/user/tdavis25,Reddit/Ingame,
Wowbaggertheinfinate,Connery,TR,,English,"Piloting (all factions), infiltrating, all around",http://www.reddit.com/user/Wowbaggertheinfinate,Reddit/Ingame,Teamspeak would make things easier 
PlanetSnide/Northkoreaisnumber1/wowigetterriblefps,Connery,TR/VS/NC,,English,All round,http://www.reddit.com/user/nelson1tom,Reddit/Ingame,
Frizbee2,Connery,TR (vs+nc alts),AFTERSHOX,English,All round except air game,http://www.reddit.com/user/frizbee2,Reddit,
Dustfinger,Connery,TR,,English/some french,All round,http://www.reddit.com/user/Dustfinger_,Reddit/Ingame,
DeedleFakeTR,Connery,TR,,English,All round,http://www.reddit.com/user/DeedleFake,Reddit/Ingame,
TKEE2,Connery,TR,,English,All round,http://www.reddit.com/user/TKEE,Reddit/Ingame,
Aiiua,Connery,TR,,English,All round,http://www.reddit.com/user/kna5041,Reddit/Ingame/Website,"trggaming.net, 10PM-8am UTC"
Fremont,Connery,TR,No Quarter Gaming (http://www.noqr.org/),English,"Basics, team play and cohesion",http://www.reddit.com/user/rhygaar,Reddit/Ingame/Website/Twitter,tweet@NOQRorg
Viking18,Connery,TR,,English,All round,http://www.reddit.com/user/Viking18,Reddit/Ingame,
Aremnant,Connery,NC,,English,"Infiltrators and light assaults, flying basics, leadership training",http://www.reddit.com/user/Aremnant,Reddit/Ingame,
CupofFriedGold(NC)/CupofFriedDAKKA(TR)/CupofFriedPlasma(VS),Connery,NC (TR+VS Alts),,English,"Light assault, medic, heavy assault,give certed vehicles only on NC",http://www.reddit.com/user/CupofFriedGold,Reddit/Ingame,
TheSchneity,Connery,NC,,English,"Heavy,Medic,LA,Infiltrator",http://www.reddit.com/user/TheSchneity,Reddit/Ingame,
Coltorlnc,Connery,NC,,English,"Mainly light assault,",http://www.reddit.com/user/fackitbaylife,Reddit,
Mkgrider23,Connery,NC,,English,All round + Pilot,http://www.reddit.com/user/mkgrider23,Reddit/Ingame,PST
sandman0893,Connery,NC (TR+VS Alts),LNVA,English,"All round, leadership",http://www.reddit.com/user/sandman0893,Reddit/Ingame,
dahazeyninja,Emerald,VS,,English,Infantry +  ground vehicles,http://www.reddit.com/user/dahazeyniinja,Reddit/Ingame,Usually on after 5PM East Coast Time
Yehhh1g,Emerald,VS,Das Anfall (DA),English,Infantry + advanced game play,http://www.reddit.com/user/yeHHH1g,Reddit/Ingame,
insanlydisturbed,Emerald,VS,S0VU,English,"All round, except ESF's",http://www.reddit.com/user/insanlydisturbed,Reddit/Ingame,
SilentUmbra,Emerald,VS,Das Anfall,English,"Medic, Heavy, Light assault",http://www.reddit.com/user/SilentUmbra,Reddit/Ingame,
Arche0s,Emerald,VS (+NC/TR alts),,English,All round,http://www.reddit.com/user/Arche0s,Reddit/Ingame,
PurelyGumbo2,Emerald,VS,"RBLE, WCTP ",English,All round -speciality Heavy assault,http://www.reddit.com/user/PurelyGumbo,Reddit/Ingame (online fridays),
Wobberjockey,Emerlad,VS,,English,Infiltrator/all round,http://www.reddit.com/user/Wobberjockey,Reddit,
CptHacks,Emerald,VS,,English/Hindi,All round,http://www.reddit.com/user/Larbat,Reddit/Ingame,
Mustarde,Emerald,TR,NoNonsenseGamers,English,Infiltrator specialist - not for beginners,http://www.reddit.com/user/Mustarde,Ingame/twitter,
Wiifi,Emerald,TR,,English,All round,http://www.reddit.com/user/Wiifi,"Private Message ""AOD_Wiifi"" at www.clanAOD.net -> Clan membership not required for mentoring. Nor will I market the clan to you, I solely wish to help",twitter @TheWiifi
Keppler,Emerald,TR,,English,All round,http://www.reddit.com/user/Gitahjunkie,Reddit,
Mankiller27x,Emerald,TR,,"English, Spanish",Main Light assault,http://www.reddit.com/user/mankiller27,Reddit/Ingame,Online most weekends 7-12 EST
Urechi,Emerald,TR,Black Widow Company,English,"Infantry based, good experience with other factions",http://www.reddit.com/user/Urechi,Reddit/Ingame,
DrCoathanger,Emerald,TR,,English,All round,http://www.reddit.com/user/DrCoathanger,Reddit/Ingame/ teamspeak,"ts.hstl.org, hstl.org."
DrunkCommy,Emerald,TR,,English,"Online most week nights, All round",http://www.reddit.com/user/DrunkCommy,Reddit/Ingame,
turkboy17,Emerald,NC,,English,All round,http://www.reddit.com/user/turkboy17,Reddit/Ingame/Steam,http://steamcommunity.com/id/turkboy17
TehVerge,Emerald,NC,New Conglomerate Crusaders(NCC4),English,All round,http://www.reddit.com/user/TheVergeOfSiik,Reddit,
Smegz,Emerald,NC,Sturmgrenadier,English,"Harasser,galaxy,liberator, medic, heavy and sunderer",http://www.reddit.com/user/mrsmegz,Reddit/Ingame,
"shelTR, Shelbourne, shelNC (emerald) Shelb0urne (BR100), Shelba, Shelbune (miller)",Emerald/Miller,VS/TR/NC,,English/German,"All round, Air",http://www.reddit.com/user/weird_guy_,Reddit/Ingame,
RallyPointAlpha (TR) RallyPointBravo(NC) RallyPointDelta(VS),Emerald,TR/NC/VS,,English,"Vehicle,infantry play",http://www.reddit.com/user/GrumpyGremlin,Reddit/Ingame,"5am-10am Central time zone, some evenings"
Mustakrakish,Emerald,NC,Blue Cloak Clan,English,Teamwork/tactics/strategy,http://www.reddit.com/user/BLUE_Mustakrakish,Ingame/reddit/email,BLUEmustakrakish@gmail.com
Boaky,Emerald,NC,,English,All round,http://www.reddit.com/user/NethChild,Reddit/Ingame,
BeastG01,Emerald,NC,NC10 (nc10.org),English,"Vanguard, engineer,medic,Galaxy, Sunderer",http://www.reddit.com/user/BeastG01,Reddit/Ingame Around 8PM EST,
Dylonius1,Emerald,NC,,English,All round,http://www.reddit.com/user/RemoteAerial,Reddit/Ingame,8PM -3am EST most nights
GamblingSabre VS / BilliBobBillsen TR,Miller,VS/TR,,German/English,All round problem solver,http://www.reddit.com/user/GamnlingSabre,Reddit/Ingame,
Mag1c,Miller,VS,,English,"LA primarily, basics",http://www.reddit.com/user/Shenel,Reddit/Ingame,
Justicia,Miller,VS,Dignity of War Tactical,English/Dutch,"Training, base layout, squad play, infantry classes, air game etc, speciality -scythe/solo lib",http://www.reddit.com/user/JusticiaDIGT,Reddit/Teamspeak,ts.dignityofwar.com:9988 (
berberi,Miller,VS,Eternal Honor,English/Finish,"Infantry, base capture/defense mechanics, AV vehicles",http://www.reddit.com/user/samitheberber,Reddit/Ingame,
Lelionmoddeur,Miller,VS,Vanu Inglorious Basterds,French/English,"Pilot, basics, UI etc",http://www.reddit.com/user/lalionnemoddeuse,Reddit/Ingame,
B4rr,Miller,VS,,English/German,All round,http://www.reddit.com/user/B4rr,Reddit/Ingame,
itzhaki,Miller,VS,Iron Pulse (IP),English/Hebew,All round + leading,http://www.reddit.com/user/itzhaki,Reddit/Ingame,
Oottzz,Miller,TR,,English/German,Support classes/game mechanics,http://www.reddit.com/user/Oottzz,Reddit/Ingame,
LOBAN4,Miller,TR,,"English,German",All round (limited on Empire specific stuff),http://www.reddit.com/user/LOBAN4,Reddit,
"satrianivai1988,satrianivai1988VS and satrianivai1988NC",Miller,TR/VS/NC,,English/Dutch,All round,http://www.reddit.com/user/satrianivai,Reddit/Ingame,Twitter @satrianivai1988
vsae,Miller,TR,,English/Russion,Infantry,http://www.reddit.com/user/vsae,Reddit/Ingame,
VALCHKYRIE,Miller,TR,2nd combined arms core,French/English,"Infantry, ground vehicles",http://www.reddit.com/user/VALCHKYRIE,Reddit/Ingame,
Fatal12,Miller,TR,INI ELITE,English,Air specialist,http://www.reddit.com/user/Fatal20,Reddit/Ingame,
"ANGEHTR (angeh cobalt vs, angehnc -cobalt",Miller,TR +VS/NC alts,YBUS,English,Infantry/tanks/squad play/leadership,http://www.reddit.com/user/angehbabe,Reddit/Ingame,ts.eleb.eu
cracanut,Miller,NC,REBR,English/Dutch,"All round, galaxys",http://www.reddit.com/user/cracanut,Reddit/Ingame,
DarthSebious,Miller,NC,,English,All round,http://www.reddit.com/user/Darthsebious,Reddit/Ingame,
JujuAT,Miller,NC,Jianji,"German (preferred), English","Weekend mentoring only, Infiltrator",http://www.reddit.com/user/JujuAT,Reddit/Ingame,
Halmine,Miller,NC,,English/Finnish,All round,http://www.reddit.com/user/Halmine,Reddit/Ingame,
JesNC,Miller,NC,WASP,English/German,All round,http://www.reddit.com/user/mkabla,Reddit/Ingame,
Amerikako,Miller,NC,LYF,English/Hebew,All round,http://www.reddit.com/user/Amerikako,Reddit/Ingame,
battlebrot,Miller,NC,,English/German,All round,http://www.reddit.com/user/battlebrot,Reddit/Ingame,
Baptist,Miller,NC,Consortium,English,All round,http://www.reddit.com/user/CONZ_Baptist,Reddit/Ingame,
/**
 * jQuery-csv (jQuery Plugin)
 * version: 0.71 (2012-11-19)
 *
 * This document is licensed as free software under the terms of the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 *
 * Acknowledgements:
 * The original design and influence to implement this library as a jquery
 * plugin is influenced by jquery-json (http://code.google.com/p/jquery-json/).
 * If you're looking to use native JSON.Stringify but want additional backwards
 * compatibility for browsers that don't support it, I highly recommend you
 * check it out.
 *
 * A special thanks goes out to rwk@acm.org for providing a lot of valuable
 * feedback to the project including the core for the new FSM
 * (Finite State Machine) parsers. If you're looking for a stable TSV parser
 * be sure to take a look at jquery-tsv (http://code.google.com/p/jquery-tsv/).

 * For legal purposes I'll include the "NO WARRANTY EXPRESSED OR IMPLIED.
 * USE AT YOUR OWN RISK.". Which, in 'layman's terms' means, by using this
 * library you are accepting responsibility if it breaks your code.
 *
 * Legal jargon aside, I will do my best to provide a useful and stable core
 * that can effectively be built on.
 *
 * Copyrighted 2012 by Evan Plaice.
 */

RegExp.escape= function(s) {
    return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};

(function( $ ) {
  'use strict'
  /**
   * jQuery.csv.defaults
   * Encapsulates the method paramater defaults for the CSV plugin module.
   */

  $.csv = {
    defaults: {
      separator:',',
      delimiter:'"',
      headers:true
    },

    hooks: {
      castToScalar: function(value, state) {
        var hasDot = /\./;
        if (isNaN(value)) {
          return value;
        } else {
          if (hasDot.test(value)) {
            return parseFloat(value);
          } else {
            var integer = parseInt(value);
            if(isNaN(integer)) {
              return null;
            } else {
              return integer;
            }
          }
        }
      }
    },

    parsers: {
      parse: function(csv, options) {
        // cache settings
        var separator = options.separator;
        var delimiter = options.delimiter;

        // set initial state if it's missing
        if(!options.state.rowNum) {
          options.state.rowNum = 1;
        }
        if(!options.state.colNum) {
          options.state.colNum = 1;
        }

        // clear initial state
        var data = [];
        var entry = [];
        var state = 0;
        var value = ''
        var exit = false;

        function endOfEntry() {
          // reset the state
          state = 0;
          value = '';

          // if 'start' hasn't been met, don't output
          if(options.start && options.state.rowNum < options.start) {
            // update global state
            entry = [];
            options.state.rowNum++;
            options.state.colNum = 1;
            return;
          }
          
          if(options.onParseEntry === undefined) {
            // onParseEntry hook not set
            data.push(entry);
          } else {
            var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook
            // false skips the row, configurable through a hook
            if(hookVal !== false) {
              data.push(hookVal);
            }
          }
          //console.log('entry:' + entry);
          
          // cleanup
          entry = [];

          // if 'end' is met, stop parsing
          if(options.end && options.state.rowNum >= options.end) {
            exit = true;
          }
          
          // update global state
          options.state.rowNum++;
          options.state.colNum = 1;
        }

        function endOfValue() {
          if(options.onParseValue === undefined) {
            // onParseValue hook not set
            entry.push(value);
          } else {
            var hook = options.onParseValue(value, options.state); // onParseValue Hook
            // false skips the row, configurable through a hook
            if(hook !== false) {
              entry.push(hook);
            }
          }
          //console.log('value:' + value);
          // reset the state
          value = '';
          state = 0;
          // update global state
          options.state.colNum++;
        }

        // escape regex-specific control chars
        var escSeparator = RegExp.escape(separator);
        var escDelimiter = RegExp.escape(delimiter);

        // compile the regEx str using the custom delimiter/separator
        var match = /(D|S|\n|\r|[^DS\r\n]+)/;
        var matchSrc = match.source;
        matchSrc = matchSrc.replace(/S/g, escSeparator);
        matchSrc = matchSrc.replace(/D/g, escDelimiter);
        match = RegExp(matchSrc, 'gm');

        // put on your fancy pants...
        // process control chars individually, use look-ahead on non-control chars
        csv.replace(match, function (m0) {
          if(exit) {
            return;
          }
          switch (state) {
            // the start of a value
            case 0:
              // null last value
              if (m0 === separator) {
                value += '';
                endOfValue();
                break;
              }
              // opening delimiter
              if (m0 === delimiter) {
                state = 1;
                break;
              }
              // null last value
              if (m0 === '\n') {
                endOfValue();
                endOfEntry();
                break;
              }
              // phantom carriage return
              if (/^\r$/.test(m0)) {
                break;
              }
              // un-delimited value
              value += m0;
              state = 3;
              break;

            // delimited input
            case 1:
              // second delimiter? check further
              if (m0 === delimiter) {
                state = 2;
                break;
              }
              // delimited data
              value += m0;
              state = 1;
              break;

            // delimiter found in delimited input
            case 2:
              // escaped delimiter?
              if (m0 === delimiter) {
                value += m0;
                state = 1;
                break;
              }
              // null value
              if (m0 === separator) {
                endOfValue();
                break;
              }
              // end of entry
              if (m0 === '\n') {
                endOfValue();
                endOfEntry();
                break;
              }
              // phantom carriage return
              if (/^\r$/.test(m0)) {
                break;
              }
              // broken paser?
              throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');

            // un-delimited input
            case 3:
              // null last value
              if (m0 === separator) {
                endOfValue();
                break;
              }
              // end of entry
              if (m0 === '\n') {
                endOfValue();
                endOfEntry();
                break;
              }
              // phantom carriage return
              if (/^\r$/.test(m0)) {
                break;
              }
              if (m0 === delimiter) {
              // non-compliant data
                throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
              }
              // broken parser?
              throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
            default:
              // shenanigans
              throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
          }
          //console.log('val:' + m0 + ' state:' + state);
        });

        // submit the last entry
        // ignore null last line
        if(entry.length !== 0) {
          endOfValue();
          endOfEntry();
        }

        return data;
      },

      // a csv-specific line splitter
      splitLines: function(csv, options) {
        // cache settings
        var separator = options.separator;
        var delimiter = options.delimiter;

        // set initial state if it's missing
        if(!options.state.rowNum) {
          options.state.rowNum = 1;
        }

        // clear initial state
        var entries = [];
        var state = 0;
        var entry = '';
        var exit = false;

        function endOfLine() {          
          // reset the state
          state = 0;
          
          // if 'start' hasn't been met, don't output
          if(options.start && options.state.rowNum < options.start) {
            // update global state
            entry = '';
            options.state.rowNum++;
            return;
          }
          
          if(options.onParseEntry === undefined) {
            // onParseEntry hook not set
            entries.push(entry);
          } else {
            var hookVal = options.onParseEntry(entry, options.state); // onParseEntry Hook
            // false skips the row, configurable through a hook
            if(hookVal !== false) {
              entries.push(hookVal);
            }
          }

          // cleanup
          entry = '';

          // if 'end' is met, stop parsing
          if(options.end && options.state.rowNum >= options.end) {
            exit = true;
          }
          
          // update global state
          options.state.rowNum++;
        }

        // escape regex-specific control chars
        var escSeparator = RegExp.escape(separator);
        var escDelimiter = RegExp.escape(delimiter);

        // compile the regEx str using the custom delimiter/separator
        var match = /(D|S|\n|\r|[^DS\r\n]+)/;
        var matchSrc = match.source;
        matchSrc = matchSrc.replace(/S/g, escSeparator);
        matchSrc = matchSrc.replace(/D/g, escDelimiter);
        match = RegExp(matchSrc, 'gm');
        
        // put on your fancy pants...
        // process control chars individually, use look-ahead on non-control chars
        csv.replace(match, function (m0) {
          if(exit) {
            return;
          }
          switch (state) {
            // the start of a value/entry
            case 0:
              // null value
              if (m0 === separator) {
                entry += m0;
                state = 0;
                break;
              }
              // opening delimiter
              if (m0 === delimiter) {
                entry += m0;
                state = 1;
                break;
              }
              // end of line
              if (m0 === '\n') {
                endOfLine();
                break;
              }
              // phantom carriage return
              if (/^\r$/.test(m0)) {
                break;
              }
              // un-delimit value
              entry += m0;
              state = 3;
              break;

            // delimited input
            case 1:
              // second delimiter? check further
              if (m0 === delimiter) {
                entry += m0;
                state = 2;
                break;
              }
              // delimited data
              entry += m0;
              state = 1;
              break;

            // delimiter found in delimited input
            case 2:
              // escaped delimiter?
              var prevChar = entry.substr(entry.length - 1);
              if (m0 === delimiter && prevChar === delimiter) {
                entry += m0;
                state = 1;
                break;
              }
              // end of value
              if (m0 === separator) {
                entry += m0;
                state = 0;
                break;
              }
              // end of line
              if (m0 === '\n') {
                endOfLine();
                break;
              }
              // phantom carriage return
              if (m0 === '\r') {
                break;
              }
              // broken paser?
              throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']');

            // un-delimited input
            case 3:
              // null value
              if (m0 === separator) {
                entry += m0;
                state = 0;
                break;
              }
              // end of line
              if (m0 === '\n') {
                endOfLine();
                break;
              }
              // phantom carriage return
              if (m0 === '\r') {
                break;
              }
              // non-compliant data
              if (m0 === delimiter) {
                throw new Error('CSVDataError: Illegal quote [Row:' + options.state.rowNum + ']');
              }
              // broken parser?
              throw new Error('CSVDataError: Illegal state [Row:' + options.state.rowNum + ']');
            default:
              // shenanigans
              throw new Error('CSVDataError: Unknown state [Row:' + options.state.rowNum + ']');
          }
          //console.log('val:' + m0 + ' state:' + state);
        });

        // submit the last entry
        // ignore null last line
        if(entry !== '') {
          endOfLine();
        }

        return entries;
      },

      // a csv entry parser
      parseEntry: function(csv, options) {
        // cache settings
        var separator = options.separator;
        var delimiter = options.delimiter;
        
        // set initial state if it's missing
        if(!options.state.rowNum) {
          options.state.rowNum = 1;
        }
        if(!options.state.colNum) {
          options.state.colNum = 1;
        }

        // clear initial state
        var entry = [];
        var state = 0;
        var value = '';

        function endOfValue() {
          if(options.onParseValue === undefined) {
            // onParseValue hook not set
            entry.push(value);
          } else {
            var hook = options.onParseValue(value, options.state); // onParseValue Hook
            // false skips the value, configurable through a hook
            if(hook !== false) {
              entry.push(hook);
            }
          }
          // reset the state
          value = '';
          state = 0;
          // update global state
          options.state.colNum++;
        }

        // checked for a cached regEx first
        if(!options.match) {
          // escape regex-specific control chars
          var escSeparator = RegExp.escape(separator);
          var escDelimiter = RegExp.escape(delimiter);
          
          // compile the regEx str using the custom delimiter/separator
          var match = /(D|S|\n|\r|[^DS\r\n]+)/;
          var matchSrc = match.source;
          matchSrc = matchSrc.replace(/S/g, escSeparator);
          matchSrc = matchSrc.replace(/D/g, escDelimiter);
          options.match = RegExp(matchSrc, 'gm');
        }

        // put on your fancy pants...
        // process control chars individually, use look-ahead on non-control chars
        csv.replace(options.match, function (m0) {
          switch (state) {
            // the start of a value
            case 0:
              // null last value
              if (m0 === separator) {
                value += '';
                endOfValue();
                break;
              }
              // opening delimiter
              if (m0 === delimiter) {
                state = 1;
                break;
              }
              // skip un-delimited new-lines
              if (m0 === '\n' || m0 === '\r') {
                break;
              }
              // un-delimited value
              value += m0;
              state = 3;
              break;

            // delimited input
            case 1:
              // second delimiter? check further
              if (m0 === delimiter) {
                state = 2;
                break;
              }
              // delimited data
              value += m0;
              state = 1;
              break;

            // delimiter found in delimited input
            case 2:
              // escaped delimiter?
              if (m0 === delimiter) {
                value += m0;
                state = 1;
                break;
              }
              // null value
              if (m0 === separator) {
                endOfValue();
                break;
              }
              // skip un-delimited new-lines
              if (m0 === '\n' || m0 === '\r') {
                break;
              }
              // broken paser?
              throw new Error('CSVDataError: Illegal State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');

            // un-delimited input
            case 3:
              // null last value
              if (m0 === separator) {
                endOfValue();
                break;
              }
              // skip un-delimited new-lines
              if (m0 === '\n' || m0 === '\r') {
                break;
              }
              // non-compliant data
              if (m0 === delimiter) {
                throw new Error('CSVDataError: Illegal Quote [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
              }
              // broken parser?
              throw new Error('CSVDataError: Illegal Data [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
            default:
              // shenanigans
              throw new Error('CSVDataError: Unknown State [Row:' + options.state.rowNum + '][Col:' + options.state.colNum + ']');
          }
          //console.log('val:' + m0 + ' state:' + state);
        });

        // submit the last value
        endOfValue();

        return entry;
      }
    },

    /**
     * $.csv.toArray(csv)
     * Converts a CSV entry string to a javascript array.
     *
     * @param {Array} csv The string containing the CSV data.
     * @param {Object} [options] An object containing user-defined options.
     * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
     * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
     *
     * This method deals with simple CSV strings only. It's useful if you only
     * need to parse a single entry. If you need to parse more than one line,
     * use $.csv2Array instead.
     */
    toArray: function(csv, options, callback) {
      var options = (options !== undefined ? options : {});
      var config = {};
      config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false);
      config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
      config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
      var state = (options.state !== undefined ? options.state : {});

      // setup
      var options = {
        delimiter: config.delimiter,
        separator: config.separator,
        onParseEntry: options.onParseEntry,
        onParseValue: options.onParseValue,
        state: state
      }

      var entry = $.csv.parsers.parseEntry(csv, options);

      // push the value to a callback if one is defined
      if(!config.callback) {
        return entry;
      } else {
        config.callback('', entry);
      }
    },

    /**
     * $.csv.toArrays(csv)
     * Converts a CSV string to a javascript array.
     *
     * @param {String} csv The string containing the raw CSV data.
     * @param {Object} [options] An object containing user-defined options.
     * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
     * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
     *
     * This method deals with multi-line CSV. The breakdown is simple. The first
     * dimension of the array represents the line (or entry/row) while the second
     * dimension contains the values (or values/columns).
     */
    toArrays: function(csv, options, callback) {
      var options = (options !== undefined ? options : {});
      var config = {};
      config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false);
      config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
      config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
      
      // setup
      var data = [];
      var options = {
        delimiter: config.delimiter,
        separator: config.separator,
        onParseEntry: options.onParseEntry,
        onParseValue: options.onParseValue,
        start: options.start,
        end: options.end,
        state: {
          rowNum: 1,
          colNum: 1
        }
      };

      // break the data down to lines
      data = $.csv.parsers.parse(csv, options);

      // push the value to a callback if one is defined
      if(!config.callback) {
        return data;
      } else {
        config.callback('', data);
      }
    },

    /**
     * $.csv.toObjects(csv)
     * Converts a CSV string to a javascript object.
     * @param {String} csv The string containing the raw CSV data.
     * @param {Object} [options] An object containing user-defined options.
     * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
     * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
     * @param {Boolean} [headers] Indicates whether the data contains a header line. Defaults to true.
     *
     * This method deals with multi-line CSV strings. Where the headers line is
     * used as the key for each value per entry.
     */
    toObjects: function(csv, options, callback) {
      var options = (options !== undefined ? options : {});
      var config = {};
      config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false);
      config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
      config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
      config.headers = 'headers' in options ? options.headers : $.csv.defaults.headers;
      options.start = 'start' in options ? options.start : 1;
      
      // account for headers
      if(config.headers) {
        options.start++;
      }
      if(options.end && config.headers) {
        options.end++;
      }
      
      // setup
      var lines = [];
      var data = [];
      
      var options = {
        delimiter: config.delimiter,
        separator: config.separator,
        onParseEntry: options.onParseEntry,
        onParseValue: options.onParseValue,
        start: options.start,
        end: options.end,
        state: {
          rowNum: 1,
          colNum: 1
        },
        match: false
      };

      // fetch the headers
      var headerOptions = {
        delimiter: config.delimiter,
        separator: config.separator,
        start: 1,
        end: 1,
        state: {
          rowNum:1,
          colNum:1
        }
      }
      var headerLine = $.csv.parsers.splitLines(csv, headerOptions);
      var headers = $.csv.toArray(headerLine[0], options);

      // fetch the data
      var lines = $.csv.parsers.splitLines(csv, options);
      
      // reset the state for re-use
      options.state.colNum = 1;
      if(headers){
        options.state.rowNum = 2;
      } else {
        options.state.rowNum = 1;
      }
      
      // convert data to objects
      for(var i=0, len=lines.length; i<len; i++) {
        var entry = $.csv.toArray(lines[i], options);
        var object = {};
        for(var j in headers) {
          object[headers[j]] = entry[j];
        }
        data.push(object);
        
        // update row state
        options.state.rowNum++;
      }

      // push the value to a callback if one is defined
      if(!config.callback) {
        return data;
      } else {
        config.callback('', data);
      }
    },

     /**
     * $.csv.fromArrays(arrays)
     * Converts a javascript array to a CSV String.
     *
     * @param {Array} array An array containing an array of CSV entries.
     * @param {Object} [options] An object containing user-defined options.
     * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
     * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
     *
     * This method generates a CSV file from an array of arrays (representing entries).
     */
    fromArrays: function(arrays, options, callback) {
      var options = (options !== undefined ? options : {});
      var config = {};
      config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false);
      config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
      config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
      config.escaper = 'escaper' in options ? options.escaper : $.csv.defaults.escaper;
      config.experimental = 'experimental' in options ? options.experimental : false;

      if(!config.experimental) {
        throw new Error('not implemented');
      }

      var output = [];
      for(i in arrays) {
        output.push(arrays[i]);
      }

      // push the value to a callback if one is defined
      if(!config.callback) {
        return output;
      } else {
        config.callback('', output);
      }
    },

    /**
     * $.csv.fromObjects(objects)
     * Converts a javascript dictionary to a CSV string.
     * @param {Object} objects An array of objects containing the data.
     * @param {Object} [options] An object containing user-defined options.
     * @param {Character} [separator] An override for the separator character. Defaults to a comma(,).
     * @param {Character} [delimiter] An override for the delimiter character. Defaults to a double-quote(").
     *
     * This method generates a CSV file from an array of objects (name:value pairs).
     * It starts by detecting the headers and adding them as the first line of
     * the CSV file, followed by a structured dump of the data.
     */
    fromObjects2CSV: function(objects, options, callback) {
      var options = (options !== undefined ? options : {});
      var config = {};
      config.callback = ((callback !== undefined && typeof(callback) === 'function') ? callback : false);
      config.separator = 'separator' in options ? options.separator : $.csv.defaults.separator;
      config.delimiter = 'delimiter' in options ? options.delimiter : $.csv.defaults.delimiter;
      config.experimental = 'experimental' in options ? options.experimental : false;

      if(!config.experimental) {
        throw new Error('not implemented');
      }

      var output = [];
      for(i in objects) {
        output.push(arrays[i]);
      }

      // push the value to a callback if one is defined
      if(!config.callback) {
        return output;
      } else {
        config.callback('', output);
      }
    }
  };

  // Maintenance code to maintain backward-compatibility
  // Will be removed in release 1.0
  $.csvEntry2Array = $.csv.toArray;
  $.csv2Array = $.csv.toArrays;
  $.csv2Dictionary = $.csv.toObjects;

})( jQuery );