/**
 * TeamsFileLoadController: loads a whole file and adds its lines as teams
 *
 * Inner workings: An HTML5 FileReader reads all contents as ANSI (ISO-8859-1).
 * It then converts possible UTF-8 byte into utf-8 code points, leaving all
 * other characters in place. This enables the use of both latin-1 and utf-8 as
 * input file encoding.
 *
 * All other encodings are discouraged. DOS-style line endings are filtered by
 * "new TeamModel()". MAC-style line endings can still be a problem.
 *
 * TODO rewrite the whole thing and extract a lot of methods
 *
 * @return TeamsFileLoadController
 * @author Erik E. Lorenz <erik@tuvero.de>
 * @license MIT License
 * @see LICENSE
 */
define(['lib/extend', 'ui/fileloadcontroller', 'ui/toast', 'ui/strings',
    'ui/state', 'ui/playermodel', 'ui/teammodel', 'presets', //
    'ui/unicodehelper'], function(extend, FileLoadController, Toast, Strings,
    State, PlayerModel, TeamModel, Presets, UnicodeHelper) {

  /**
   * Constructor
   *
   * @param $button
   *          Optional. A button element which, when clicked, starts the file
   *          selection
   */
  function TeamsFileLoadController($button) {
    TeamsFileLoadController.superconstructor.call(this, $button);
  }
  extend(TeamsFileLoadController, FileLoadController);

  /**
   * Implemented function: file read success. Start parsing.
   *
   * @param fileContents
   *          file contents
   */
  TeamsFileLoadController.prototype.readFile = function(fileContents) {
    return TeamsFileLoadController.load(fileContents);
  };

  /**
   * Implemented function: unread current file. Nothing to do here, since there
   * should be no registered teams
   */
  TeamsFileLoadController.prototype.unreadFile = function() {
    // Nothing to unread
  };

  /**
   * reads names from a string and adds the players accordingly. Ignores
   * #-escaped lines
   *
   * @return true on success, undefined or false on failure
   */
  TeamsFileLoadController.parseCSVString = function(str, teamsizeModel) {
    var lines;

    lines = str.split('\n');

    lines = lines.filter(function(line) {
      return line.trim().length !== 0 && !line.trim().match(/^#/);
    }).map(function(line) {
      var match, name, names = [];

      while (line.length > 0) {
        match = line.match(/^\s*"([^"]+|"")*"\s*(,|;|$)/) || '';
        match = match || line.match(/^\s*[^,;]*\s*(,|;|$)/);

        name = (match && match[0]) || line;
        line = line.substr(name.length);
        names.push(name);
      }

      return names;
    });

    lines = lines.map(function(names) {
      return names.map(function(name) {
        // remove commas and whitespaces
        name = name.replace(/,$/, '').trim();
        if (name.match(/^".*"$/)) {
          name = name.replace(/^"(.*)"$/, '$1');
          name = name.replace(/""/g, '"');
        }
        return name;
      });
    });

    lines = lines.filter(function(names) {
      return names.join('').length !== 0;
    });

    return lines;
  };

  /**
   * Read teamsize from teams array
   *
   * @param teams
   *          a 2d teams array
   * @return the team size, or 0 on failure.
   */
  TeamsFileLoadController.readTeamsize = function(teams) {
    var teamsizes, teamsize;

    if (teams.length === 0) {
      return 0;
    }

    teamsizes = teams.map(function(team) {
      return team.length;
    });

    teamsize = teamsizes[0];

    if (teamsizes.some(function(size) {
      return size !== teamsize;
    })) {
      return 0;
    }

    return teamsize;
  };

  /**
   * load the teams from a csv string and write them to State
   *
   * @param csvString
   * @return true on success, false otherwise
   */
  TeamsFileLoadController.load = function(csvString) {
    var teams, teamsize;

    csvString = UnicodeHelper.latin2utf8(csvString);

    if (State.teams.length !== 0) {
      new Toast(Strings.teamsnotempty);
      return false;
    }

    teams = TeamsFileLoadController.parseCSVString(csvString, State.teamsize);
    teamsize = TeamsFileLoadController.readTeamsize(teams);

    // validate team size
    if (teamsize >= Presets.registration.minteamsize
        && teamsize <= Presets.registration.maxteamsize) {
      State.teamsize.set(teamsize);
    } else {
      new Toast(Strings.invalidteamsize);
      return false;
    }

    // enter new teams
    teams.forEach(function(names) {
      var players = names.map(function(name) {
        return new PlayerModel(name);
      });

      State.teams.push(new TeamModel(players));
    });

    new Toast(Strings.loaded);

    return true;
  };

  return TeamsFileLoadController;
});

@@ -683,16 +683,26 @@
 define(%5B
+'tuvero', 
 'lib/ext
@@ -856,16 +856,24 @@
 unction(
+tuvero, 
 extend, 
@@ -2012,1009 +2012,43 @@
 (str
-, teamsizeModel) %7B%0A    var lines;%0A%0A    lines = str.split('%5Cn');%0A%0A    lines = lines.filter(function(line) %7B%0A      return line.trim().length !== 0 && !line.trim().match(/%5E#/);%0A    %7D).map(function(line) %7B%0A      var match, name, names = %5B%5D;%0A%0A      while (line.length %3E 0) %7B%0A        match = line.match(/%5E%5Cs*%22(%5B%5E%22%5D+%7C%22%22)*%22%5Cs*(,%7C;%7C$)/) %7C%7C '';%0A        match = match %7C%7C line.match(/%5E%5Cs*%5B%5E,;%5D*%5Cs*(,%7C;%7C$)/);%0A%0A        name = (match && match%5B0%5D) %7C%7C line;%0A        line = line.substr(name.length);%0A        names.push(name);%0A      %7D%0A%0A      return names;%0A    %7D);%0A%0A    lines = lines.map(function(names) %7B%0A      return names.map(function(name) %7B%0A        // remove commas and whitespaces%0A        name = name.replace(/,$/, '').trim();%0A        if (name.match(/%5E%22.*%22$/)) %7B%0A          name = name.replace(/%5E%22(.*)%22$/, '$1');%0A          name = name.replace(/%22%22/g, '%22');%0A        %7D%0A        return name;%0A      %7D);%0A    %7D);%0A%0A    lines = lines.filter(function(names) %7B%0A      return names.join('').length !== 0;%0A    %7D);%0A%0A    return lines
+) %7B%0A    return tuvero.csv.read(str)
 ;%0A  

Replace flaky csv parser with tuvero.csv.read()