sdf-parser 7.0.4 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/MolfileStream.d.ts +20 -0
- package/lib/MolfileStream.d.ts.map +1 -0
- package/lib/MolfileStream.js +52 -0
- package/lib/MolfileStream.js.map +1 -0
- package/lib/getEntriesBoundaries.d.ts +11 -0
- package/lib/getEntriesBoundaries.d.ts.map +1 -0
- package/lib/getEntriesBoundaries.js +33 -0
- package/lib/getEntriesBoundaries.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -304
- package/lib/index.js.map +1 -0
- package/lib/iterator.d.ts +49 -0
- package/lib/iterator.d.ts.map +1 -0
- package/lib/iterator.js +55 -0
- package/lib/iterator.js.map +1 -0
- package/lib/parse.d.ts +105 -0
- package/lib/parse.d.ts.map +1 -0
- package/lib/parse.js +128 -0
- package/lib/parse.js.map +1 -0
- package/lib/util/getMolecule.d.ts +44 -0
- package/lib/util/getMolecule.d.ts.map +1 -0
- package/lib/util/getMolecule.js +63 -0
- package/lib/util/getMolecule.js.map +1 -0
- package/package.json +27 -21
- package/src/MolfileStream.ts +53 -0
- package/src/getEntriesBoundaries.ts +34 -0
- package/src/index.ts +3 -0
- package/src/iterator.ts +104 -0
- package/src/parse.ts +227 -0
- package/src/util/getMolecule.ts +107 -0
- package/src/MolfileStream.js +0 -35
- package/src/getEntriesBoundaries.js +0 -28
- package/src/index.js +0 -3
- package/src/iterator.js +0 -60
- package/src/parse.js +0 -112
- package/src/util/getMolecule.js +0 -64
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAEhB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACvD;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC;IACnD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,IAAI,EAAE,OAAO,CAAC;IACd,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,4BAA4B;IAC5B,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,YAAiB,GAAG,WAAW,CAgH3E"}
|
package/lib/parse.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ensureString } from 'ensure-string';
|
|
2
|
+
import { getEntriesBoundaries } from "./getEntriesBoundaries.js";
|
|
3
|
+
import { getMolecule } from "./util/getMolecule.js";
|
|
4
|
+
/**
|
|
5
|
+
* Synchronously parse an SDF file into an array of molecule objects.
|
|
6
|
+
* @param sdf - The SDF content as a string, `ArrayBuffer`, or `ArrayBufferView`.
|
|
7
|
+
* @param options - Parsing options.
|
|
8
|
+
* @returns A {@link ParseResult} containing molecules and statistics.
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { readFileSync } from 'node:fs';
|
|
12
|
+
* import { parse } from 'sdf-parser';
|
|
13
|
+
*
|
|
14
|
+
* const sdf = readFileSync('compounds.sdf', 'utf8');
|
|
15
|
+
* const { molecules, statistics } = parse(sdf);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function parse(sdf, options = {}) {
|
|
19
|
+
options = { ...options };
|
|
20
|
+
if (options.modifiers === undefined)
|
|
21
|
+
options.modifiers = {};
|
|
22
|
+
if (options.forEach === undefined)
|
|
23
|
+
options.forEach = {};
|
|
24
|
+
if (options.dynamicTyping === undefined)
|
|
25
|
+
options.dynamicTyping = true;
|
|
26
|
+
// ensureString converts ArrayBuffer/ArrayBufferView to string
|
|
27
|
+
const sdfString = ensureString(sdf);
|
|
28
|
+
if (typeof sdfString !== 'string') {
|
|
29
|
+
throw new TypeError('Parameter "sdf" must be a string');
|
|
30
|
+
}
|
|
31
|
+
if (options.eol === undefined) {
|
|
32
|
+
options.eol = '\n';
|
|
33
|
+
if (options.mixedEOL) {
|
|
34
|
+
// Normalize all line endings to \n
|
|
35
|
+
// We work on a local variable so no issue here
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// Note: new Set(string) creates a Set of individual characters.
|
|
39
|
+
// '\r\n' is two characters so header.has('\r\n') would always be false.
|
|
40
|
+
// This preserves the original detection behaviour.
|
|
41
|
+
const header = new Set(sdfString.slice(0, 1000));
|
|
42
|
+
if (header.has('\r\n')) {
|
|
43
|
+
options.eol = '\r\n';
|
|
44
|
+
}
|
|
45
|
+
else if (header.has('\r')) {
|
|
46
|
+
options.eol = '\r';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let workingSdf = sdfString;
|
|
51
|
+
if (options.mixedEOL) {
|
|
52
|
+
workingSdf = workingSdf.replaceAll('\r\n', '\n');
|
|
53
|
+
workingSdf = workingSdf.replaceAll('\r', '\n');
|
|
54
|
+
}
|
|
55
|
+
const eol = options.eol;
|
|
56
|
+
const modifiers = options.modifiers;
|
|
57
|
+
const forEachMap = options.forEach;
|
|
58
|
+
const dynamicTyping = options.dynamicTyping;
|
|
59
|
+
const entriesBoundaries = getEntriesBoundaries(workingSdf, `${eol}$$$$`, eol);
|
|
60
|
+
const molecules = [];
|
|
61
|
+
const labels = {};
|
|
62
|
+
const start = Date.now();
|
|
63
|
+
for (const boundary of entriesBoundaries) {
|
|
64
|
+
const sdfPart = workingSdf.slice(...boundary);
|
|
65
|
+
if (sdfPart.length < 40)
|
|
66
|
+
continue;
|
|
67
|
+
const currentLabels = [];
|
|
68
|
+
const molecule = getMolecule(sdfPart, labels, currentLabels, {
|
|
69
|
+
eol,
|
|
70
|
+
dynamicTyping,
|
|
71
|
+
modifiers,
|
|
72
|
+
forEach: forEachMap,
|
|
73
|
+
include: options.include,
|
|
74
|
+
exclude: options.exclude,
|
|
75
|
+
});
|
|
76
|
+
if (!molecule)
|
|
77
|
+
continue;
|
|
78
|
+
if (!options.filter || options.filter(molecule)) {
|
|
79
|
+
molecules.push(molecule);
|
|
80
|
+
for (const label of currentLabels) {
|
|
81
|
+
labels[label].counter++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Convert all numeric fields and compute min/max
|
|
86
|
+
for (const label in labels) {
|
|
87
|
+
const currentLabel = labels[label];
|
|
88
|
+
if (currentLabel.isNumeric) {
|
|
89
|
+
currentLabel.minValue = Infinity;
|
|
90
|
+
currentLabel.maxValue = -Infinity;
|
|
91
|
+
for (const molecule of molecules) {
|
|
92
|
+
if (molecule[label]) {
|
|
93
|
+
const value = Number.parseFloat(molecule[label]);
|
|
94
|
+
molecule[label] = value;
|
|
95
|
+
if (value > (currentLabel.maxValue ?? -Infinity)) {
|
|
96
|
+
currentLabel.maxValue = value;
|
|
97
|
+
}
|
|
98
|
+
if (value < (currentLabel.minValue ?? Infinity)) {
|
|
99
|
+
currentLabel.minValue = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const key in labels) {
|
|
106
|
+
labels[key].always = labels[key].counter === molecules.length;
|
|
107
|
+
}
|
|
108
|
+
const statistics = [];
|
|
109
|
+
for (const key in labels) {
|
|
110
|
+
const info = labels[key];
|
|
111
|
+
statistics.push({
|
|
112
|
+
label: key,
|
|
113
|
+
counter: info.counter,
|
|
114
|
+
isNumeric: info.isNumeric,
|
|
115
|
+
keep: info.keep,
|
|
116
|
+
minValue: info.minValue,
|
|
117
|
+
maxValue: info.maxValue,
|
|
118
|
+
always: info.always ?? false,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
time: Date.now() - start,
|
|
123
|
+
molecules,
|
|
124
|
+
labels: Object.keys(labels),
|
|
125
|
+
statistics,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=parse.js.map
|
package/lib/parse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAgGpD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,UAAwB,EAAE;IAC5D,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IACzB,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,CAAC,SAAS,GAAG,EAAE,CAAC;IAC5D,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,EAAE,CAAC;IACxD,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS;QAAE,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAEtE,8DAA8D;IAC9D,MAAM,SAAS,GAAG,YAAY,CAAC,GAAyC,CAAC,CAAC;IAC1E,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;QACnB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,mCAAmC;YACnC,+CAA+C;QACjD,CAAC;aAAM,CAAC;YACN,gEAAgE;YAChE,wEAAwE;YACxE,mDAAmD;YACnD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;YACjD,IAAI,MAAM,CAAC,GAAG,CAAC,MAA2B,CAAC,EAAE,CAAC;gBAC5C,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC;YACvB,CAAC;iBAAM,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5B,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,UAAU,GAAG,SAAS,CAAC;IAC3B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjD,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IACpC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IACnC,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;IAE5C,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,UAAU,EAAE,GAAG,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,MAAM,MAAM,GAA8B,EAAE,CAAC;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEzB,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC;QAC9C,IAAI,OAAO,CAAC,MAAM,GAAG,EAAE;YAAE,SAAS;QAClC,MAAM,aAAa,GAAa,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE;YAC3D,GAAG;YACH,aAAa;YACb,SAAS;YACT,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ;YAAE,SAAS;QACxB,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzB,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;gBAClC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;YAC3B,YAAY,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACjC,YAAY,CAAC,QAAQ,GAAG,CAAC,QAAQ,CAAC;YAClC,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBACpB,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;oBACjD,QAAQ,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;oBACxB,IAAI,KAAK,GAAG,CAAC,YAAY,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;wBACjD,YAAY,CAAC,QAAQ,GAAG,KAAK,CAAC;oBAChC,CAAC;oBACD,IAAI,KAAK,GAAG,CAAC,YAAY,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,CAAC;wBAChD,YAAY,CAAC,QAAQ,GAAG,KAAK,CAAC;oBAChC,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,MAAM,CAAC;IAChE,CAAC;IAED,MAAM,UAAU,GAAqB,EAAE,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,UAAU,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,GAAG;YACV,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;QACxB,SAAS;QACT,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAC3B,UAAU;KACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Molecule } from '../parse.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Internal per-label tracking information used during parsing.
|
|
4
|
+
*/
|
|
5
|
+
export interface LabelInfo {
|
|
6
|
+
/** Number of molecules that contain this label. */
|
|
7
|
+
counter: number;
|
|
8
|
+
/**
|
|
9
|
+
* Whether all seen values for this label are numeric.
|
|
10
|
+
* Starts as `true` when `dynamicTyping` is enabled.
|
|
11
|
+
*/
|
|
12
|
+
isNumeric: boolean;
|
|
13
|
+
/** Whether this label is included in molecule output (not excluded). */
|
|
14
|
+
keep: boolean;
|
|
15
|
+
/** Minimum numeric value (set after all molecules are parsed). */
|
|
16
|
+
minValue?: number;
|
|
17
|
+
/** Maximum numeric value (set after all molecules are parsed). */
|
|
18
|
+
maxValue?: number;
|
|
19
|
+
/** Whether every molecule in the result contains this label. Set after parsing. */
|
|
20
|
+
always?: boolean;
|
|
21
|
+
/** Optional modifier function applied to the raw string value. */
|
|
22
|
+
modifier?: (value: string) => unknown;
|
|
23
|
+
/** Optional callback stored for this label (for statistics). */
|
|
24
|
+
forEach?: (value: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
/** Options consumed by {@link getMolecule} (a resolved subset of ParseOptions). */
|
|
27
|
+
export interface GetMoleculeOptions {
|
|
28
|
+
eol: string;
|
|
29
|
+
dynamicTyping: boolean;
|
|
30
|
+
include?: string[];
|
|
31
|
+
exclude?: string[];
|
|
32
|
+
modifiers: Record<string, (value: string) => unknown>;
|
|
33
|
+
forEach: Record<string, (value: unknown) => void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse a single SDF entry string into a molecule object.
|
|
37
|
+
* @param sdfPart - A single SDF record (everything before the `$$$$` line).
|
|
38
|
+
* @param labels - Shared label tracking object, mutated in place.
|
|
39
|
+
* @param currentLabels - Array to collect label names found in this entry.
|
|
40
|
+
* @param options - Resolved parse options.
|
|
41
|
+
* @returns The molecule object, or `undefined` if the entry is too short.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getMolecule(sdfPart: string, labels: Record<string, LabelInfo>, currentLabels: string[], options: GetMoleculeOptions): Molecule | undefined;
|
|
44
|
+
//# sourceMappingURL=getMolecule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getMolecule.d.ts","sourceRoot":"","sources":["../../src/util/getMolecule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,IAAI,EAAE,OAAO,CAAC;IACd,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,gEAAgE;IAChE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACtD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,EACjC,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,EAAE,kBAAkB,GAC1B,QAAQ,GAAG,SAAS,CAwDtB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a single SDF entry string into a molecule object.
|
|
3
|
+
* @param sdfPart - A single SDF record (everything before the `$$$$` line).
|
|
4
|
+
* @param labels - Shared label tracking object, mutated in place.
|
|
5
|
+
* @param currentLabels - Array to collect label names found in this entry.
|
|
6
|
+
* @param options - Resolved parse options.
|
|
7
|
+
* @returns The molecule object, or `undefined` if the entry is too short.
|
|
8
|
+
*/
|
|
9
|
+
export function getMolecule(sdfPart, labels, currentLabels, options) {
|
|
10
|
+
const { eol, dynamicTyping, include, exclude, modifiers, forEach } = options;
|
|
11
|
+
const parts = sdfPart.split(`${eol}>`);
|
|
12
|
+
if (parts.length === 0 || parts[0].length <= 5)
|
|
13
|
+
return undefined;
|
|
14
|
+
const molecule = { molfile: parts[0] + eol };
|
|
15
|
+
for (let j = 1; j < parts.length; j++) {
|
|
16
|
+
const lines = parts[j].split(eol);
|
|
17
|
+
const from = lines[0].indexOf('<');
|
|
18
|
+
const to = lines[0].indexOf('>');
|
|
19
|
+
const label = lines[0].slice(from + 1, to);
|
|
20
|
+
currentLabels.push(label);
|
|
21
|
+
if (!labels[label]) {
|
|
22
|
+
labels[label] = {
|
|
23
|
+
counter: 0,
|
|
24
|
+
isNumeric: dynamicTyping,
|
|
25
|
+
keep: false,
|
|
26
|
+
};
|
|
27
|
+
if (!exclude?.includes(label) && (!include || include.includes(label))) {
|
|
28
|
+
labels[label].keep = true;
|
|
29
|
+
if (modifiers[label])
|
|
30
|
+
labels[label].modifier = modifiers[label];
|
|
31
|
+
if (forEach[label])
|
|
32
|
+
labels[label].forEach = forEach[label];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (labels[label].keep) {
|
|
36
|
+
for (let k = 1; k < lines.length - 1; k++) {
|
|
37
|
+
if (molecule[label]) {
|
|
38
|
+
molecule[label] = `${molecule[label]}${eol}${lines[k]}`;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
molecule[label] = lines[k];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (labels[label].modifier) {
|
|
45
|
+
const modifiedValue = labels[label].modifier(molecule[label]);
|
|
46
|
+
if (modifiedValue === undefined || modifiedValue === null) {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
48
|
+
delete molecule[label];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
molecule[label] = modifiedValue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (labels[label].isNumeric &&
|
|
55
|
+
(!Number.isFinite(+molecule[label]) ||
|
|
56
|
+
molecule[label].match(/^0[0-9]/))) {
|
|
57
|
+
labels[label].isNumeric = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return molecule;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=getMolecule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getMolecule.js","sourceRoot":"","sources":["../../src/util/getMolecule.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdf-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "SDF parser",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/index.js"
|
|
8
|
+
},
|
|
7
9
|
"files": [
|
|
8
10
|
"lib",
|
|
9
11
|
"src"
|
|
10
12
|
],
|
|
11
13
|
"sideEffects": false,
|
|
12
14
|
"scripts": {
|
|
13
|
-
"build": "cheminfo-build
|
|
14
|
-
"
|
|
15
|
+
"build": "npm run tsc-build && cheminfo-build --entry lib/index.js --root SDFParser",
|
|
16
|
+
"check-types": "tsc --noEmit",
|
|
17
|
+
"clean": "rimraf lib",
|
|
15
18
|
"eslint": "eslint src",
|
|
16
19
|
"eslint-fix": "npm run eslint -- --fix",
|
|
17
|
-
"prepack": "npm run
|
|
20
|
+
"prepack": "npm run tsc",
|
|
18
21
|
"prettier": "prettier --check src",
|
|
19
22
|
"prettier-write": "prettier --write src",
|
|
20
|
-
"test": "npm run test-only && npm run eslint && npm run prettier",
|
|
21
|
-
"test-only": "vitest run --coverage"
|
|
23
|
+
"test": "npm run test-only && npm run check-types && npm run eslint && npm run prettier",
|
|
24
|
+
"test-only": "vitest run --coverage",
|
|
25
|
+
"tsc": "npm run clean && npm run tsc-build",
|
|
26
|
+
"tsc-build": "tsc --project tsconfig.build.json"
|
|
22
27
|
},
|
|
23
28
|
"repository": {
|
|
24
29
|
"type": "git",
|
|
@@ -39,21 +44,22 @@
|
|
|
39
44
|
},
|
|
40
45
|
"homepage": "https://github.com/cheminfo/sdf-parser",
|
|
41
46
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"babel-eslint": "^10.1.0",
|
|
47
|
+
"@types/node": "^25.3.0",
|
|
48
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
49
|
+
"@zakodium/tsconfig": "^1.0.2",
|
|
46
50
|
"callback-stream": "^1.1.0",
|
|
47
|
-
"cheminfo-build": "^1.2
|
|
48
|
-
"eslint": "^9.
|
|
49
|
-
"eslint-config-cheminfo": "^
|
|
50
|
-
"file-collection": "^
|
|
51
|
-
"openchemlib": "^
|
|
52
|
-
"prettier": "^3.
|
|
53
|
-
"
|
|
51
|
+
"cheminfo-build": "^1.3.2",
|
|
52
|
+
"eslint": "^9.39.2",
|
|
53
|
+
"eslint-config-cheminfo-typescript": "^21.1.0",
|
|
54
|
+
"file-collection": "^6.6.0",
|
|
55
|
+
"openchemlib": "^9.20.0",
|
|
56
|
+
"prettier": "^3.8.1",
|
|
57
|
+
"rimraf": "^6.1.3",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vitest": "^4.0.18"
|
|
54
60
|
},
|
|
55
61
|
"dependencies": {
|
|
56
|
-
"dynamic-typing": "^
|
|
57
|
-
"ensure-string": "^
|
|
62
|
+
"dynamic-typing": "^2.0.0",
|
|
63
|
+
"ensure-string": "^2.0.0"
|
|
58
64
|
}
|
|
59
65
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Web Streams API `TransformStream` that splits an incoming text stream on
|
|
3
|
+
* the `$$$$` SDF record delimiter and emits individual molfile strings.
|
|
4
|
+
*
|
|
5
|
+
* Entries shorter than 40 characters are discarded.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const stream = readStream.pipeThrough(new MolfileStream());
|
|
9
|
+
* for await (const molfile of stream) {
|
|
10
|
+
* console.log(molfile);
|
|
11
|
+
* }
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class MolfileStream extends TransformStream<string, string> {
|
|
15
|
+
readonly #buffer: string[] = [];
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
18
|
+
constructor(_options?: { eol?: string }) {
|
|
19
|
+
super({
|
|
20
|
+
transform: (chunk, controller) => {
|
|
21
|
+
this.#buffer.push(chunk);
|
|
22
|
+
const combined = this.#buffer.join('');
|
|
23
|
+
this.#buffer.length = 0;
|
|
24
|
+
|
|
25
|
+
let begin = 0;
|
|
26
|
+
let index = 0;
|
|
27
|
+
while ((index = combined.indexOf('$$$$', index)) !== -1) {
|
|
28
|
+
const endOfDelimiter = combined.indexOf('\n', index);
|
|
29
|
+
if (endOfDelimiter === -1) {
|
|
30
|
+
index = begin;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const eolLength = combined[endOfDelimiter - 1] === '\r' ? 2 : 1;
|
|
34
|
+
// Remove the last eol before enqueuing
|
|
35
|
+
if (index - eolLength - begin > 40) {
|
|
36
|
+
controller.enqueue(combined.slice(begin, index - eolLength));
|
|
37
|
+
}
|
|
38
|
+
index = endOfDelimiter + eolLength;
|
|
39
|
+
begin = index;
|
|
40
|
+
}
|
|
41
|
+
if (begin < combined.length) {
|
|
42
|
+
this.#buffer.push(combined.slice(begin));
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
flush: (controller) => {
|
|
46
|
+
const remaining = this.#buffer.join('');
|
|
47
|
+
if (remaining && remaining.length > 40) {
|
|
48
|
+
controller.enqueue(remaining);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the [start, end] boundaries of each SDF entry in a string.
|
|
3
|
+
*
|
|
4
|
+
* Uses `indexOf` for fast splitting without regex overhead.
|
|
5
|
+
* @param string - The full SDF string.
|
|
6
|
+
* @param substring - The delimiter to search for (e.g. `'\n$$$$'`).
|
|
7
|
+
* @param eol - The end-of-line character used to skip past the delimiter line.
|
|
8
|
+
* @returns An array of `[start, end]` index pairs, one per SDF entry.
|
|
9
|
+
*/
|
|
10
|
+
export function getEntriesBoundaries(
|
|
11
|
+
string: string,
|
|
12
|
+
substring: string,
|
|
13
|
+
eol: string,
|
|
14
|
+
): Array<[number, number]> {
|
|
15
|
+
const res: Array<[number, number]> = [];
|
|
16
|
+
let previous = 0;
|
|
17
|
+
let next = 0;
|
|
18
|
+
while (next !== -1) {
|
|
19
|
+
next = string.indexOf(substring, previous);
|
|
20
|
+
if (next !== -1) {
|
|
21
|
+
res.push([previous, next]);
|
|
22
|
+
const nextMatch = string.indexOf(eol, next + substring.length);
|
|
23
|
+
if (nextMatch === -1) {
|
|
24
|
+
next = -1;
|
|
25
|
+
} else {
|
|
26
|
+
previous = nextMatch + eol.length;
|
|
27
|
+
next = previous;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
res.push([previous, string.length]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return res;
|
|
34
|
+
}
|
package/src/index.ts
ADDED
package/src/iterator.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { parseString } from 'dynamic-typing';
|
|
2
|
+
|
|
3
|
+
import { MolfileStream } from './MolfileStream.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A molecule entry returned by the {@link iterator} async generator.
|
|
7
|
+
* The `molfile` field contains the raw V2000/V3000 molfile block.
|
|
8
|
+
* Additional fields are populated from the SDF `> <field>` sections.
|
|
9
|
+
*/
|
|
10
|
+
export interface IteratorMolecule {
|
|
11
|
+
/** The raw V2000/V3000 molfile block. */
|
|
12
|
+
molfile: string;
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
[label: string]: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for the {@link iterator} async generator.
|
|
19
|
+
*/
|
|
20
|
+
export interface IteratorOptions {
|
|
21
|
+
/**
|
|
22
|
+
* End-of-line character used to split field entries.
|
|
23
|
+
* @default '\n'
|
|
24
|
+
*/
|
|
25
|
+
eol?: string;
|
|
26
|
+
/**
|
|
27
|
+
* When `true`, numeric string values are automatically converted to numbers.
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
dynamicTyping?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* A predicate function to filter molecules. Only molecules for which this
|
|
33
|
+
* function returns `true` are yielded.
|
|
34
|
+
*/
|
|
35
|
+
filter?: (molecule: IteratorMolecule) => boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Asynchronously iterate over molecules from a text-decoded SDF stream.
|
|
40
|
+
* @param readStream - A `ReadableStream<string>` supplying SDF text content.
|
|
41
|
+
* @param options - Iterator options.
|
|
42
|
+
* @yields {IteratorMolecule} Individual molecule objects.
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { openAsBlob } from 'node:fs';
|
|
46
|
+
* import { iterator } from 'sdf-parser';
|
|
47
|
+
*
|
|
48
|
+
* const blob = await openAsBlob('compounds.sdf');
|
|
49
|
+
* const textDecoder = new TextDecoderStream();
|
|
50
|
+
* for await (const molecule of iterator(blob.stream().pipeThrough(textDecoder))) {
|
|
51
|
+
* console.log(molecule.molfile);
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export async function* iterator(
|
|
56
|
+
readStream: ReadableStream<string>,
|
|
57
|
+
options: IteratorOptions = {},
|
|
58
|
+
): AsyncGenerator<IteratorMolecule> {
|
|
59
|
+
const { eol = '\n', dynamicTyping = true } = options;
|
|
60
|
+
const moleculeStream = readStream.pipeThrough(new MolfileStream({ eol }));
|
|
61
|
+
for await (const entry of moleculeStream) {
|
|
62
|
+
const molecule = parseMolecule(entry, { eol, dynamicTyping });
|
|
63
|
+
if (!options.filter || options.filter(molecule)) {
|
|
64
|
+
yield molecule;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface ParseMoleculeOptions {
|
|
70
|
+
eol: string;
|
|
71
|
+
dynamicTyping: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseMolecule(
|
|
75
|
+
sdfPart: string,
|
|
76
|
+
options: ParseMoleculeOptions,
|
|
77
|
+
): IteratorMolecule {
|
|
78
|
+
const { eol, dynamicTyping } = options;
|
|
79
|
+
const parts = sdfPart.split(`${eol}>`);
|
|
80
|
+
const molecule: IteratorMolecule = {
|
|
81
|
+
molfile: parts.length > 0 && parts[0].length > 5 ? parts[0] + eol : '',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
for (let j = 1; j < parts.length; j++) {
|
|
85
|
+
const lines = parts[j].split(eol);
|
|
86
|
+
const from = lines[0].indexOf('<');
|
|
87
|
+
const to = lines[0].indexOf('>');
|
|
88
|
+
const label = lines[0].slice(from + 1, to);
|
|
89
|
+
|
|
90
|
+
for (let k = 1; k < lines.length - 1; k++) {
|
|
91
|
+
if (molecule[label]) {
|
|
92
|
+
molecule[label] = `${molecule[label] as string}${eol}${lines[k]}`;
|
|
93
|
+
} else {
|
|
94
|
+
molecule[label] = lines[k];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (dynamicTyping) {
|
|
99
|
+
molecule[label] = parseString(molecule[label]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return molecule;
|
|
104
|
+
}
|