Source

parsers/SDF.ts

/*
 * @param {!Array.<string>} lines
 * @param {ParserOptionsSpec} options
 * @returns {!Array.<Array<Object>>}
 */
var parseV2000 = function (lines, options) {
  var atoms: any[][] & Record<string, any> = [[]];
  var noH = false;
  if (typeof options.keepH !== "undefined") noH = !options.keepH;

  while (lines.length > 0) {
    if (lines.length < 4) break;
    var atomCount = parseInt(lines[3].substr(0, 3));
    if (isNaN(atomCount) || atomCount <= 0) break;
    var bondCount = parseInt(lines[3].substr(3, 3));
    var offset = 4;
    if (lines.length < 4 + atomCount + bondCount) break;

    // serial is atom's index in file; index is atoms index in 'atoms'
    var serialToIndex: number[] = [];
    var start = atoms[atoms.length - 1].length;
    var end = start + atomCount;
    var i, line;
    for (i = start; i < end; i++, offset++) {
      line = lines[offset];
      var atom: Record<string, any> = {};
      var elem = line.substr(31, 3).replace(/ /g, "");
      atom.atom = atom.elem =
        elem[0].toUpperCase() + elem.substr(1).toLowerCase();

      if (atom.elem !== "H" || !noH) {
        atom.serial = i;
        serialToIndex[i] = atoms[atoms.length - 1].length;
        atom.x = parseFloat(line.substr(0, 10));
        atom.y = parseFloat(line.substr(10, 10));
        atom.z = parseFloat(line.substr(20, 10));
        atom.hetflag = true;
        atom.bonds = [];
        atom.bondOrder = [];
        atom.properties = {};
        atom.index = atoms[atoms.length - 1].length;
        atoms[atoms.length - 1].push(atom);
      }
    }

    for (i = 0; i < bondCount; i++, offset++) {
      line = lines[offset];
      var from = serialToIndex[parseInt(line.substr(0, 3)) - 1 + start];
      var to = serialToIndex[parseInt(line.substr(3, 3)) - 1 + start];
      var order = parseInt(line.substr(6, 3));
      if (typeof from != "undefined" && typeof to != "undefined") {
        atoms[atoms.length - 1][from].bonds.push(to);
        atoms[atoms.length - 1][from].bondOrder.push(order);
        atoms[atoms.length - 1][to].bonds.push(from);
        atoms[atoms.length - 1][to].bondOrder.push(order);
      }
    }
    if (options.multimodel) {
      if (!options.onemol) atoms.push([]);
      while (lines[offset] !== "$$$$") offset++;
      lines.splice(0, ++offset);
    } else {
      break;
    }
  }
  return atoms;
};

/*
 * @param {!Array.<string>} lines
 * @param {ParserOptionsSpec} options
 * @returns {!Array.<!Array<!Object>>}
 */
var parseV3000 = function (lines, options) {
  var atoms: any[][] & Record<string, any> = [[]];
  var noH = false;
  if (typeof options.keepH !== "undefined") noH = !options.keepH;

  while (lines.length > 0) {
    if (lines.length < 8) break;

    if (!lines[4].startsWith("M  V30 BEGIN CTAB")) break;
    if (!lines[5].startsWith("M  V30 COUNTS") || lines[5].length < 14) break;

    var counts = lines[5].substr(13).match(/\S+/g);

    if (counts.length < 2) break;

    var atomCount = parseInt(counts[0]);
    if (isNaN(atomCount) || atomCount <= 0) break;
    var bondCount = parseInt(counts[1]);
    var offset = 7;

    if (lines.length < 8 + atomCount + bondCount)
      //header, bgn+end CTAB, counts, END
      break;

    // serial is atom's index in file; index is atoms index in 'atoms'
    var serialToIndex: number[] = [];
    var start = atoms[atoms.length - 1].length;
    var end = start + atomCount;
    var i, line;
    for (i = start; i < end; i++, offset++) {
      line = lines[offset];
      var atomParts = line.substr(6).match(/\S+/g);
      if (atomParts.length > 4) {
        var atom: Record<string, any> = {};
        var elem = atomParts[1].replace(/ /g, "");
        atom.atom = atom.elem =
          elem[0].toUpperCase() + elem.substr(1).toLowerCase();

        if (atom.elem !== "H" || !noH) {
          atom.serial = i;
          serialToIndex[i] = atoms[atoms.length - 1].length;
          atom.x = parseFloat(atomParts[2]);
          atom.y = parseFloat(atomParts[3]);
          atom.z = parseFloat(atomParts[4]);
          atom.hetflag = true;
          atom.bonds = [];
          atom.bondOrder = [];
          atom.properties = {};
          atom.index = atoms[atoms.length - 1].length;
          atoms[atoms.length - 1].push(atom);
        }
      }
    }

    if (lines[offset] === "M  V30 END ATOM") offset++;
    else break;

    if (bondCount !== 0 && lines[offset] === "M  V30 BEGIN BOND") offset++;
    else break;

    for (i = 0; i < bondCount; i++, offset++) {
      line = lines[offset];
      var bondParts = line.substr(6).match(/\S+/g);
      if (bondParts.length > 3) {
        var from = serialToIndex[parseInt(bondParts[2]) - 1 + start];
        var to = serialToIndex[parseInt(bondParts[3]) - 1 + start];
        var order = parseInt(bondParts[1]);
        if (typeof from != "undefined" && typeof to != "undefined") {
          atoms[atoms.length - 1][from].bonds.push(to);
          atoms[atoms.length - 1][from].bondOrder.push(order);
          atoms[atoms.length - 1][to].bonds.push(from);
          atoms[atoms.length - 1][to].bondOrder.push(order);
        }
      }
    }
    if (options.multimodel) {
      if (!options.onemol) {
        atoms.push([]);
      }
      while (lines[offset] !== "$$$$") {
        offset++;
      }
      lines.splice(0, ++offset);
    } else {
      break;
    }
  }
  return atoms;
};


/**
 * @param {string}
 *            str
 * @param {ParserOptionsSpec}
 *            options
 * @category Parsers

 */
export function SDF(str, options) {
  var molformat = "V2000";
  var lines = str.split(/\r?\n|\r/);
  if (lines.length > 3 && lines[3].length > 38) {
    molformat = lines[3].substr(34, 5);
  }
  if (molformat === "V2000") {
    return parseV2000(lines, options);
  } else if (molformat === "V3000") {
    return parseV3000(lines, options);
  }
  return [[]];
}