in packages/ketcher-core/src/domain/serializers/mol/molfile.ts [291:532]
writeCTab2000(rgroups?: Map<any, any>) {
// eslint-disable-line max-statements
/* saver */
this.writeCTab2000Header();
this.mapping = {};
let i = 1;
const atomsIds: number[] = [];
const atomsProps: {
id: number;
value: string;
}[] = [];
this.molecule!.atoms.forEach((atom, id) => {
let label = atom.label;
if (atom.atomList != null) {
label = 'L';
atomsIds.push(id);
} else if (atom.pseudo) {
if (atom.pseudo.length > 3) {
label = 'A';
atomsProps.push({ id, value: `'${atom.pseudo}'` });
}
} else if (atom.alias) {
atomsProps.push({ id, value: atom.alias });
} else if (
!Elements.get(atom.label) &&
['A', 'Q', 'X', '*', 'R#'].indexOf(atom.label) === -1
) {
// search in generics?
label = 'C';
atomsProps.push({ id, value: atom.label });
}
this.writeAtom(atom, label);
this.mapping[id] = i++;
}, this);
this.bondMapping = {};
i = 1;
this.molecule!.bonds.forEach((bond, id) => {
this.bondMapping[id] = i++;
this.writeBond(bond);
}, this);
while (atomsProps.length > 0) {
this.writeAtomProps(atomsProps[0]);
atomsProps.splice(0, 1);
}
const chargeList: NumberTuple[] = [];
const isotopeList: NumberTuple[] = [];
const radicalList: NumberTuple[] = [];
const rglabelList: NumberTuple[] = [];
const rglogicList: string[] = [];
const aplabelList: NumberTuple[] = [];
const rbcountList: NumberTuple[] = [];
const unsaturatedList: NumberTuple[] = [];
const substcountList: NumberTuple[] = [];
this.molecule!.atoms.forEach((atom, id) => {
if (atom.charge !== 0 && atom.charge !== null) {
chargeList.push([id, atom.charge]);
}
if (atom.isotope !== 0 && atom.isotope !== null) {
isotopeList.push([id, atom.isotope]);
}
if (atom.radical !== 0) {
radicalList.push([id, atom.radical]);
}
if (atom.rglabel != null && atom.label === 'R#') {
// TODO need to force rglabel=null when label is not 'R#'
for (let rgi = 0; rgi < 32; rgi++) {
if ((atom.rglabel as any) & (1 << rgi)) {
rglabelList.push([id, rgi + 1]);
}
}
}
if (atom.attachmentPoints != null) {
aplabelList.push([id, atom.attachmentPoints]);
}
if (atom.ringBondCount !== 0) {
rbcountList.push([id, atom.ringBondCount]);
}
if (atom.substitutionCount !== 0) {
substcountList.push([id, atom.substitutionCount]);
}
if (atom.unsaturatedAtom !== 0) {
unsaturatedList.push([id, atom.unsaturatedAtom]);
}
});
if (rgroups) {
rgroups.forEach((rg, rgid) => {
if (rg.resth || rg.ifthen > 0 || rg.range.length > 0) {
const line =
' 1 ' +
utils.paddedNum(rgid, 3) +
' ' +
utils.paddedNum(rg.ifthen, 3) +
' ' +
utils.paddedNum(rg.resth ? 1 : 0, 3) +
' ' +
rg.range;
rglogicList.push(line);
}
});
}
this.writeAtomPropList('M CHG', chargeList);
this.writeAtomPropList('M ISO', isotopeList);
this.writeAtomPropList('M RAD', radicalList);
this.writeAtomPropList('M RGP', rglabelList);
for (let j = 0; j < rglogicList.length; ++j) {
this.write('M LOG' + rglogicList[j] + '\n');
}
this.writeAtomPropList('M APO', aplabelList);
this.writeAtomPropList('M RBC', rbcountList);
this.writeAtomPropList('M SUB', substcountList);
this.writeAtomPropList('M UNS', unsaturatedList);
if (atomsIds.length > 0) {
for (let j = 0; j < atomsIds.length; ++j) {
const atomId = atomsIds[j];
const atomList = this.molecule!.atoms.get(atomId)!.atomList!;
this.write('M ALS');
this.writePaddedNumber(atomId + 1, 4);
this.writePaddedNumber(atomList.ids.length, 3);
this.writeWhiteSpace();
this.write(atomList.notList ? 'T' : 'F');
const labelList = atomList.labelList();
for (let k = 0; k < labelList.length; ++k) {
this.writeWhiteSpace();
this.writePadded(labelList[k], 3);
}
this.writeWhiteSpace();
this.writeCR();
}
}
const sgmap = {};
let cnt = 1;
const sgmapback = {};
const sgorder = this.molecule!.sGroupForest.getSGroupsBFS();
sgorder.forEach((id) => {
sgmapback[cnt] = id;
sgmap[id] = cnt++;
});
for (let sGroupIdInCTab = 1; sGroupIdInCTab < cnt; ++sGroupIdInCTab) {
// each group on its own
const id = sgmapback[sGroupIdInCTab];
const sgroup = this.molecule!.sgroups.get(id)!;
if (SGroup.isQuerySGroup(sgroup)) {
console.warn('Query group does not support in mol format');
continue;
}
this.write('M STY');
this.writePaddedNumber(1, 3);
this.writeWhiteSpace(1);
this.writePaddedNumber(sGroupIdInCTab, 3);
this.writeWhiteSpace(1);
this.writePadded(sgroup.type, 3);
this.writeCR();
// TODO: write subtype, M SST
this.write('M SLB');
this.writePaddedNumber(1, 3);
this.writeWhiteSpace(1);
this.writePaddedNumber(sGroupIdInCTab, 3);
this.writeWhiteSpace(1);
this.writePaddedNumber(sGroupIdInCTab, 3);
this.writeCR();
const parentId = this.molecule!.sGroupForest.parent.get(id)!;
if (parentId >= 0) {
this.write('M SPL');
this.writePaddedNumber(1, 3);
this.writeWhiteSpace(1);
this.writePaddedNumber(sGroupIdInCTab, 3);
this.writeWhiteSpace(1);
this.writePaddedNumber(sgmap[parentId], 3);
this.writeCR();
}
// connectivity
if (sgroup.type === 'SRU' && sgroup.data.connectivity) {
const connectivity = ` ${sGroupIdInCTab.toString().padStart(3)} ${(
sgroup.data.connectivity || ''
).padEnd(3)}`;
this.write('M SCN');
this.writePaddedNumber(1, 3);
this.write(connectivity.toUpperCase());
this.writeCR();
}
if (sgroup.type === 'SRU') {
this.write('M SMT ');
this.writePaddedNumber(sGroupIdInCTab, 3);
this.writeWhiteSpace();
this.write(sgroup.data.subscript || 'n');
this.writeCR();
}
sgroup.getAttachmentPoints().forEach((attachmentPoint) => {
this.writeSGroupAttachmentPointLine(sGroupIdInCTab, attachmentPoint);
});
this.writeCR(
common.saveToMolfile[sgroup.type](
sgroup,
this.molecule,
sgmap,
this.mapping,
this.bondMapping,
),
);
}
// TODO: write M APO
// TODO: write M AAL
// TODO: write M RGP
// TODO: write M LOG
const expandedGroups: number[] = [];
this.molecule!.sgroups.forEach((sg) => {
if (sg.isExpanded() && !SGroup.isQuerySGroup(sg))
expandedGroups.push(sg.id + 1);
});
if (expandedGroups.length) {
const expandedGroupsLine = `M SDS EXP ${
expandedGroups.length
} ${expandedGroups.join(' ')}`;
this.writeCR(expandedGroupsLine);
}
this.writeCR('M END');
}