loadPgn method Null safety

dynamic loadPgn(
  1. String pgn,
  2. [Map? options]
)

Load the moves of a game stored in Portable Game Notation. options is an optional parameter that contains a 'newline_char' which is a string representation of a RegExp (and should not be pre-escaped) and defaults to '\r?\n'). Returns true if the PGN was parsed successfully, otherwise false.

Implementation

loadPgn(String pgn, [Map? options]) {
  mask(str) {
    return str.replaceAll(new RegExp(r"\\"), '\\');
  }

  /* convert a move from Standard Algebraic Notation (SAN) to 0x88
     * coordinates
    */
  moveFromSan(move) {
    var moves = generateMoves();
    for (var i = 0, len = moves.length; i < len; i++) {
      /* strip off any trailing move decorations: e.g Nf3+?! */
      if (move.replaceAll(new RegExp(r"[+#?!=]+$"), '') ==
          moveToSan(moves[i]).replaceAll(new RegExp(r"[+#?!=]+$"), '')) {
        return moves[i];
      }
    }
    return null;
  }

  getMoveObj(move) {
    return moveFromSan(trim(move));
  }

  /*has_keys(object) {
      bool has_keys = false;
      for (var key in object) {
        has_keys = true;
      }
      return has_keys;
    }*/

  parsePgnHeader(header, [Map? options]) {
    var newlineChar = (options != null && options.containsKey("newline_char"))
        ? options['newline_char']
        : '\r?\n';
    var headerObj = {};
    var headers = header.split(newlineChar);
    String key = '';
    String value = '';

    for (var i = 0; i < headers.length; i++) {
      RegExp keyMatch = new RegExp(r"^\[([A-Z][A-Za-z]*)\s.*\]$");
      var temp = keyMatch.firstMatch(headers[i]);
      if (temp != null) {
        key = temp[1]!;
      }
      //print(key);
      RegExp valueMatch = new RegExp(r'^\[[A-Za-z]+\s"(.*)"\]$');
      temp = valueMatch.firstMatch(headers[i]);
      if (temp != null) {
        value = temp[1]!;
      }
      //print(value);
      if (trim(key).length > 0) {
        headerObj[key] = value;
      }
    }

    return headerObj;
  }

  var newlineChar = (options != null && options.containsKey("newline_char"))
      ? options["newline_char"]
      : '\r?\n';
  //var regex = new RegExp(r'^(\[.*\]).*' + r'1\.'); //+ r"1\."); //+ mask(newline_char));

  int indexOfMoveStart = pgn.indexOf(new RegExp(newlineChar + r"1\."));

  /* get header part of the PGN file */
  String? headerString;
  if (indexOfMoveStart != -1) {
    headerString = pgn.substring(0, indexOfMoveStart).trim();
  }

  /* no info part given, begins with moves */
  if (headerString == null || headerString[0] != '[') {
    headerString = '';
  }

  reset();

  /* parse PGN header */
  var headers = parsePgnHeader(headerString, options);
  for (var key in headers.keys) {
    setHeader([key, headers[key]]);
  }

  /* delete header to get the moves */
  var ms = pgn
      .replaceAll(headerString, '')
      .replaceAll(new RegExp(mask(newlineChar)), ' ');

  /* delete comments */
  ms = ms.replaceAll(new RegExp(r"(\{[^}]+\})+?"), '');

  /* delete move numbers */
  ms = ms.replaceAll(new RegExp(r"\d+\."), '');

  /* trim and get array of moves */
  var moves = trim(ms).split(new RegExp(r"\s+"));

  /* delete empty entries */
  moves = moves.join(',').replaceAll(new RegExp(r",,+"), ',').split(',');
  var move;

  for (var halfMove = 0; halfMove < moves.length - 1; halfMove++) {
    move = getMoveObj(moves[halfMove]);

    /* move not possible! (don't clear the board to examine to show the
       * latest valid position)
       */
    if (move == null) {
      return false;
    } else {
      makeMove(move);
    }
  }

  /* examine last move */
  move = moves[moves.length - 1];
  if (POSSIBLE_RESULTS.contains(move)) {
    if (!header.containsKey("Result")) {
      setHeader(['Result', move]);
    }
  } else {
    var moveObj = getMoveObj(move);
    if (moveObj == null) {
      return false;
    } else {
      makeMove(moveObj);
    }
  }
  return true;
}