sdf-parser 5.0.2 → 6.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/README.md +9 -30
- package/lib/index.js +141 -132
- package/package.json +4 -6
- package/src/__tests__/iterator.test.js +120 -0
- package/src/__tests__/test.sdf.gz +0 -0
- package/src/index.js +1 -1
- package/src/iterator.js +54 -0
- package/src/parse.js +37 -86
- package/src/util/getMolecule.js +55 -0
- package/src/__tests__/stream.test.js +0 -98
- package/src/stream.js +0 -45
package/README.md
CHANGED
|
@@ -57,39 +57,18 @@ var result = parse(sdf, {
|
|
|
57
57
|
});
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
##
|
|
60
|
+
## Iterator
|
|
61
61
|
|
|
62
62
|
This API is only available on Node.js.
|
|
63
63
|
|
|
64
|
-
### molecules(options)
|
|
65
|
-
|
|
66
|
-
Transform an input text stream to a stream of molecule objects.
|
|
67
|
-
|
|
68
|
-
#### options
|
|
69
|
-
|
|
70
|
-
- `fullResult`: true to emit the full result of `parse` instead of just the molecules.
|
|
71
|
-
- All other options from the `parse` function.
|
|
72
|
-
|
|
73
|
-
```js
|
|
74
|
-
const { stream } = require('sdf-parser');
|
|
75
|
-
fs.createReadStream('test.sdf')
|
|
76
|
-
.pipe(stream.molecules())
|
|
77
|
-
.on('data', (molecule) => {
|
|
78
|
-
console.log(molecule.molfile);
|
|
79
|
-
});
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### entries()
|
|
83
|
-
|
|
84
|
-
Transform an input text stream to a stream of sdf entries.
|
|
85
|
-
|
|
86
64
|
```js
|
|
87
|
-
const {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
65
|
+
const { iterator } = require('sdf-parser');
|
|
66
|
+
const readStream = createReadStream(join(__dirname, 'test.sdf.gz'));
|
|
67
|
+
const stream = readStream.pipe(createGunzip());
|
|
68
|
+
const results = [];
|
|
69
|
+
for await (const entry of iterator(stream)) {
|
|
70
|
+
results.push(entry);
|
|
71
|
+
}
|
|
93
72
|
```
|
|
94
73
|
|
|
95
74
|
## License
|
|
@@ -98,7 +77,7 @@ fs.createReadStream('test.sdf')
|
|
|
98
77
|
|
|
99
78
|
[npm-image]: https://img.shields.io/npm/v/sdf-parser.svg?style=flat-square
|
|
100
79
|
[npm-url]: https://www.npmjs.com/package/sdf-parser
|
|
101
|
-
[travis-image]: https://img.shields.io/travis/cheminfo/sdf-parser/
|
|
80
|
+
[travis-image]: https://img.shields.io/travis/cheminfo/sdf-parser/main.svg?style=flat-square
|
|
102
81
|
[travis-url]: https://travis-ci.org/cheminfo/sdf-parser
|
|
103
82
|
[download-image]: https://img.shields.io/npm/dm/sdf-parser.svg?style=flat-square
|
|
104
83
|
[download-url]: https://www.npmjs.com/package/sdf-parser
|
package/lib/index.js
CHANGED
|
@@ -3,17 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var ensureString = require('ensure-string');
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var through2 = require('through2');
|
|
9
|
-
var filter = require('through2-filter');
|
|
10
|
-
|
|
11
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
-
|
|
13
|
-
var pipeline__default = /*#__PURE__*/_interopDefaultLegacy(pipeline);
|
|
14
|
-
var split2__default = /*#__PURE__*/_interopDefaultLegacy(split2);
|
|
15
|
-
var through2__default = /*#__PURE__*/_interopDefaultLegacy(through2);
|
|
16
|
-
var filter__default = /*#__PURE__*/_interopDefaultLegacy(filter);
|
|
6
|
+
var readline = require('readline');
|
|
7
|
+
var dynamicTyping = require('dynamic-typing');
|
|
17
8
|
|
|
18
9
|
function getEntriesBoundaries(string, substring, eol) {
|
|
19
10
|
const res = [];
|
|
@@ -37,46 +28,106 @@ function getEntriesBoundaries(string, substring, eol) {
|
|
|
37
28
|
return res;
|
|
38
29
|
}
|
|
39
30
|
|
|
31
|
+
function getMolecule$1(sdfPart, labels, currentLabels, options) {
|
|
32
|
+
let parts = sdfPart.split(`${options.eol}>`);
|
|
33
|
+
if (parts.length === 0 || parts[0].length <= 5) return;
|
|
34
|
+
let molecule = {};
|
|
35
|
+
molecule.molfile = parts[0] + options.eol;
|
|
36
|
+
for (let j = 1; j < parts.length; j++) {
|
|
37
|
+
let lines = parts[j].split(options.eol);
|
|
38
|
+
let from = lines[0].indexOf('<');
|
|
39
|
+
let to = lines[0].indexOf('>');
|
|
40
|
+
let label = lines[0].substring(from + 1, to);
|
|
41
|
+
currentLabels.push(label);
|
|
42
|
+
if (!labels[label]) {
|
|
43
|
+
labels[label] = {
|
|
44
|
+
counter: 0,
|
|
45
|
+
isNumeric: options.dynamicTyping,
|
|
46
|
+
keep: false,
|
|
47
|
+
};
|
|
48
|
+
if (
|
|
49
|
+
(!options.exclude || options.exclude.indexOf(label) === -1) &&
|
|
50
|
+
(!options.include || options.include.indexOf(label) > -1)
|
|
51
|
+
) {
|
|
52
|
+
labels[label].keep = true;
|
|
53
|
+
if (options.modifiers[label]) {
|
|
54
|
+
labels[label].modifier = options.modifiers[label];
|
|
55
|
+
}
|
|
56
|
+
if (options.forEach[label]) {
|
|
57
|
+
labels[label].forEach = options.forEach[label];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (labels[label].keep) {
|
|
62
|
+
for (let k = 1; k < lines.length - 1; k++) {
|
|
63
|
+
if (molecule[label]) {
|
|
64
|
+
molecule[label] += options.eol + lines[k];
|
|
65
|
+
} else {
|
|
66
|
+
molecule[label] = lines[k];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (labels[label].modifier) {
|
|
70
|
+
let modifiedValue = labels[label].modifier(molecule[label]);
|
|
71
|
+
if (modifiedValue === undefined || modifiedValue === null) {
|
|
72
|
+
delete molecule[label];
|
|
73
|
+
} else {
|
|
74
|
+
molecule[label] = modifiedValue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (labels[label].isNumeric) {
|
|
78
|
+
if (!isFinite(molecule[label]) || molecule[label].match(/^0[0-9]/)) {
|
|
79
|
+
labels[label].isNumeric = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return molecule;
|
|
85
|
+
}
|
|
86
|
+
|
|
40
87
|
/**
|
|
41
88
|
* Parse a SDF file
|
|
42
89
|
* @param {string|ArrayBuffer|Uint8Array} sdf SDF file to parse
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {
|
|
45
|
-
* @param {
|
|
90
|
+
* @param {object} [options={}]
|
|
91
|
+
* @param {string[]} [options.include] List of fields to include
|
|
92
|
+
* @param {string[]} [options.exclude] List of fields to exclude
|
|
93
|
+
* @param {Function} [options.filter] Callback allowing to filter the molecules
|
|
46
94
|
* @param {boolean} [options.dynamicTyping] Dynamically type the data
|
|
47
95
|
* @param {object} [options.modifiers] Object containing callbacks to apply on some specific fields
|
|
48
96
|
* @param {boolean} [options.mixedEOL=false] Set to true if you know there is a mixture between \r\n and \n
|
|
97
|
+
* @param {string} [options.eol] Specify the end of line character. Default will be the one found in the file
|
|
49
98
|
*/
|
|
50
99
|
function parse(sdf, options = {}) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
modifiers = {},
|
|
56
|
-
forEach = {},
|
|
57
|
-
dynamicTyping = true,
|
|
58
|
-
} = options;
|
|
100
|
+
options = { ...options };
|
|
101
|
+
if (options.modifiers === undefined) options.modifiers = {};
|
|
102
|
+
if (options.forEach === undefined) options.forEach = {};
|
|
103
|
+
if (options.dynamicTyping === undefined) options.dynamicTyping = true;
|
|
59
104
|
|
|
60
105
|
sdf = ensureString.ensureString(sdf);
|
|
61
106
|
if (typeof sdf !== 'string') {
|
|
62
107
|
throw new TypeError('Parameter "sdf" must be a string');
|
|
63
108
|
}
|
|
64
109
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
if (options.eol === undefined) {
|
|
111
|
+
options.eol = '\n';
|
|
112
|
+
if (options.mixedEOL) {
|
|
113
|
+
sdf = sdf.replace(/\r\n/g, '\n');
|
|
114
|
+
sdf = sdf.replace(/\r/g, '\n');
|
|
115
|
+
} else {
|
|
116
|
+
// we will find the delimiter in order to be much faster and not use regular expression
|
|
117
|
+
let header = sdf.substr(0, 1000);
|
|
118
|
+
if (header.indexOf('\r\n') > -1) {
|
|
119
|
+
options.eol = '\r\n';
|
|
120
|
+
} else if (header.indexOf('\r') > -1) {
|
|
121
|
+
options.eol = '\r';
|
|
122
|
+
}
|
|
76
123
|
}
|
|
77
124
|
}
|
|
78
125
|
|
|
79
|
-
let entriesBoundaries = getEntriesBoundaries(
|
|
126
|
+
let entriesBoundaries = getEntriesBoundaries(
|
|
127
|
+
sdf,
|
|
128
|
+
`${options.eol}$$$$`,
|
|
129
|
+
options.eol,
|
|
130
|
+
);
|
|
80
131
|
let molecules = [];
|
|
81
132
|
let labels = {};
|
|
82
133
|
|
|
@@ -84,72 +135,18 @@ function parse(sdf, options = {}) {
|
|
|
84
135
|
|
|
85
136
|
for (let i = 0; i < entriesBoundaries.length; i++) {
|
|
86
137
|
let sdfPart = sdf.substring(...entriesBoundaries[i]);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
let label = lines[0].substring(from + 1, to);
|
|
97
|
-
currentLabels.push(label);
|
|
98
|
-
if (!labels[label]) {
|
|
99
|
-
labels[label] = {
|
|
100
|
-
counter: 0,
|
|
101
|
-
isNumeric: dynamicTyping,
|
|
102
|
-
keep: false,
|
|
103
|
-
};
|
|
104
|
-
if (
|
|
105
|
-
(!exclude || exclude.indexOf(label) === -1) &&
|
|
106
|
-
(!include || include.indexOf(label) > -1)
|
|
107
|
-
) {
|
|
108
|
-
labels[label].keep = true;
|
|
109
|
-
if (modifiers[label]) {
|
|
110
|
-
labels[label].modifier = modifiers[label];
|
|
111
|
-
}
|
|
112
|
-
if (forEach[label]) {
|
|
113
|
-
labels[label].forEach = forEach[label];
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (labels[label].keep) {
|
|
118
|
-
for (let k = 1; k < lines.length - 1; k++) {
|
|
119
|
-
if (molecule[label]) {
|
|
120
|
-
molecule[label] += eol + lines[k];
|
|
121
|
-
} else {
|
|
122
|
-
molecule[label] = lines[k];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (labels[label].modifier) {
|
|
126
|
-
let modifiedValue = labels[label].modifier(molecule[label]);
|
|
127
|
-
if (modifiedValue === undefined || modifiedValue === null) {
|
|
128
|
-
delete molecule[label];
|
|
129
|
-
} else {
|
|
130
|
-
molecule[label] = modifiedValue;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (labels[label].isNumeric) {
|
|
134
|
-
if (
|
|
135
|
-
!isFinite(molecule[label]) ||
|
|
136
|
-
molecule[label].match(/^0[0-9]/)
|
|
137
|
-
) {
|
|
138
|
-
labels[label].isNumeric = false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (!filter || filter(molecule)) {
|
|
144
|
-
molecules.push(molecule);
|
|
145
|
-
// only now we can increase the counter
|
|
146
|
-
for (let j = 0; j < currentLabels.length; j++) {
|
|
147
|
-
labels[currentLabels[j]].counter++;
|
|
148
|
-
}
|
|
138
|
+
|
|
139
|
+
let currentLabels = [];
|
|
140
|
+
const molecule = getMolecule$1(sdfPart, labels, currentLabels, options);
|
|
141
|
+
if (!molecule) continue;
|
|
142
|
+
if (!options.filter || options.filter(molecule)) {
|
|
143
|
+
molecules.push(molecule);
|
|
144
|
+
// only now we can increase the counter
|
|
145
|
+
for (let j = 0; j < currentLabels.length; j++) {
|
|
146
|
+
labels[currentLabels[j]].counter++;
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
149
|
}
|
|
152
|
-
|
|
153
150
|
// all numeric fields should be converted to numbers
|
|
154
151
|
for (let label in labels) {
|
|
155
152
|
let currentLabel = labels[label];
|
|
@@ -195,45 +192,57 @@ function parse(sdf, options = {}) {
|
|
|
195
192
|
};
|
|
196
193
|
}
|
|
197
194
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
195
|
+
/**
|
|
196
|
+
* Parse a SDF file
|
|
197
|
+
* @param {string|ArrayBuffer|Uint8Array} sdf SDF file to parse
|
|
198
|
+
* @param {object} [options={}]
|
|
199
|
+
* @param {Function} [options.filter] Callback allowing to filter the molecules
|
|
200
|
+
* @param {boolean} [options.dynamicTyping] Dynamically type the data
|
|
201
|
+
*/
|
|
203
202
|
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
203
|
+
async function* iterator(readStream, options = {}) {
|
|
204
|
+
const lines = readline.createInterface(readStream);
|
|
205
|
+
const currentLines = [];
|
|
206
|
+
options = { ...options };
|
|
207
|
+
if (options.dynamicTyping === undefined) options.dynamicTyping = true;
|
|
208
|
+
|
|
209
|
+
options.eol = '\n';
|
|
210
|
+
for await (let line of lines) {
|
|
211
|
+
if (line.startsWith('$$$$')) {
|
|
212
|
+
const molecule = getMolecule(currentLines.join(options.eol), options);
|
|
213
|
+
if (!options.filter || options.filter(molecule)) {
|
|
214
|
+
yield molecule;
|
|
215
|
+
}
|
|
216
|
+
currentLines.length = 0;
|
|
217
|
+
} else {
|
|
218
|
+
currentLines.push(line);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
214
221
|
}
|
|
215
222
|
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
callback(e);
|
|
223
|
+
function getMolecule(sdfPart, options) {
|
|
224
|
+
let parts = sdfPart.split(`${options.eol}>`);
|
|
225
|
+
if (parts.length === 0 || parts[0].length <= 5) return;
|
|
226
|
+
let molecule = {};
|
|
227
|
+
molecule.molfile = parts[0] + options.eol;
|
|
228
|
+
for (let j = 1; j < parts.length; j++) {
|
|
229
|
+
let lines = parts[j].split(options.eol);
|
|
230
|
+
let from = lines[0].indexOf('<');
|
|
231
|
+
let to = lines[0].indexOf('>');
|
|
232
|
+
let label = lines[0].substring(from + 1, to);
|
|
233
|
+
for (let k = 1; k < lines.length - 1; k++) {
|
|
234
|
+
if (molecule[label]) {
|
|
235
|
+
molecule[label] += options.eol + lines[k];
|
|
236
|
+
} else {
|
|
237
|
+
molecule[label] = lines[k];
|
|
232
238
|
}
|
|
233
|
-
}
|
|
234
|
-
|
|
239
|
+
}
|
|
240
|
+
if (options.dynamicTyping) {
|
|
241
|
+
molecule[label] = dynamicTyping.parseString(molecule[label]);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return molecule;
|
|
235
245
|
}
|
|
236
246
|
|
|
237
|
-
exports.
|
|
238
|
-
exports.molecules = molecules;
|
|
247
|
+
exports.iterator = iterator;
|
|
239
248
|
exports.parse = parse;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdf-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "SDF parser",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -49,15 +49,13 @@
|
|
|
49
49
|
"cheminfo-build": "^1.1.11",
|
|
50
50
|
"eslint": "^8.22.0",
|
|
51
51
|
"eslint-config-cheminfo": "^8.0.2",
|
|
52
|
+
"filelist-utils": "^0.6.0",
|
|
52
53
|
"jest": "^28.1.3",
|
|
53
54
|
"openchemlib": "^8.0.1",
|
|
54
55
|
"prettier": "^2.7.1"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"split2": "^4.1.0",
|
|
60
|
-
"through2": "^4.0.2",
|
|
61
|
-
"through2-filter": "^3.0.0"
|
|
58
|
+
"dynamic-typing": "^1.0.0",
|
|
59
|
+
"ensure-string": "^1.2.0"
|
|
62
60
|
}
|
|
63
61
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createReadStream } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { createGunzip } from 'zlib';
|
|
4
|
+
|
|
5
|
+
import { fileListFromPath } from 'filelist-utils';
|
|
6
|
+
|
|
7
|
+
import { iterator } from '../iterator';
|
|
8
|
+
|
|
9
|
+
test('iterator', async () => {
|
|
10
|
+
const fileList = (await fileListFromPath(join(__dirname, '.'))).filter(
|
|
11
|
+
(file) => file.name === 'test.sdf',
|
|
12
|
+
);
|
|
13
|
+
const results = [];
|
|
14
|
+
for await (const entry of iterator(fileList[0].stream())) {
|
|
15
|
+
results.push(entry);
|
|
16
|
+
}
|
|
17
|
+
expect(results).toHaveLength(128);
|
|
18
|
+
expect(results[0]).toMatchInlineSnapshot(`
|
|
19
|
+
Object {
|
|
20
|
+
"CLogP": 2.7,
|
|
21
|
+
"Code": 100380824,
|
|
22
|
+
"Number of H-Acceptors": 3,
|
|
23
|
+
"Number of H-Donors": 1,
|
|
24
|
+
"Number of Rotatable bonds": 1,
|
|
25
|
+
"molfile": "
|
|
26
|
+
-ISIS- 04231216572D
|
|
27
|
+
|
|
28
|
+
15 16 0 0 0 0 0 0 0 0999 V2000
|
|
29
|
+
2.4792 1.7000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
30
|
+
2.4292 0.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
31
|
+
0.4042 1.1208 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
32
|
+
1.2167 2.1833 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
33
|
+
1.1542 -0.0000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
|
|
34
|
+
-0.9208 1.1208 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
35
|
+
3.4792 -0.4500 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
36
|
+
0.8792 3.4458 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
37
|
+
-1.6000 -0.0292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
38
|
+
-0.9625 -1.1792 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
39
|
+
-1.6208 -2.3292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
40
|
+
-0.9125 -3.4375 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0
|
|
41
|
+
-3.5958 -1.1792 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
42
|
+
-2.9208 -0.0292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
43
|
+
-3.0333 -2.3292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
44
|
+
2 1 1 0 0 0 0
|
|
45
|
+
3 4 1 0 0 0 0
|
|
46
|
+
4 1 1 0 0 0 0
|
|
47
|
+
5 2 1 0 0 0 0
|
|
48
|
+
6 3 2 0 0 0 0
|
|
49
|
+
7 2 2 0 0 0 0
|
|
50
|
+
8 4 2 0 0 0 0
|
|
51
|
+
9 6 1 0 0 0 0
|
|
52
|
+
10 9 2 0 0 0 0
|
|
53
|
+
11 10 1 0 0 0 0
|
|
54
|
+
12 11 1 0 0 0 0
|
|
55
|
+
13 14 2 0 0 0 0
|
|
56
|
+
14 9 1 0 0 0 0
|
|
57
|
+
15 13 1 0 0 0 0
|
|
58
|
+
3 5 1 0 0 0 0
|
|
59
|
+
15 11 2 0 0 0 0
|
|
60
|
+
M END
|
|
61
|
+
",
|
|
62
|
+
}
|
|
63
|
+
`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('iterator on stream', async () => {
|
|
67
|
+
const readStream = createReadStream(join(__dirname, 'test.sdf.gz'));
|
|
68
|
+
const stream = readStream.pipe(createGunzip());
|
|
69
|
+
const results = [];
|
|
70
|
+
for await (const entry of iterator(stream)) {
|
|
71
|
+
results.push(entry);
|
|
72
|
+
}
|
|
73
|
+
expect(results).toHaveLength(128);
|
|
74
|
+
expect(results[0]).toMatchInlineSnapshot(`
|
|
75
|
+
Object {
|
|
76
|
+
"CLogP": 2.7,
|
|
77
|
+
"Code": 100380824,
|
|
78
|
+
"Number of H-Acceptors": 3,
|
|
79
|
+
"Number of H-Donors": 1,
|
|
80
|
+
"Number of Rotatable bonds": 1,
|
|
81
|
+
"molfile": "
|
|
82
|
+
-ISIS- 04231216572D
|
|
83
|
+
|
|
84
|
+
15 16 0 0 0 0 0 0 0 0999 V2000
|
|
85
|
+
2.4792 1.7000 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0
|
|
86
|
+
2.4292 0.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
87
|
+
0.4042 1.1208 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
88
|
+
1.2167 2.1833 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
89
|
+
1.1542 -0.0000 0.0000 S 0 0 0 0 0 0 0 0 0 0 0 0
|
|
90
|
+
-0.9208 1.1208 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
91
|
+
3.4792 -0.4500 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
92
|
+
0.8792 3.4458 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
93
|
+
-1.6000 -0.0292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
94
|
+
-0.9625 -1.1792 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
95
|
+
-1.6208 -2.3292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
96
|
+
-0.9125 -3.4375 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0
|
|
97
|
+
-3.5958 -1.1792 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
98
|
+
-2.9208 -0.0292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
99
|
+
-3.0333 -2.3292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
|
|
100
|
+
2 1 1 0 0 0 0
|
|
101
|
+
3 4 1 0 0 0 0
|
|
102
|
+
4 1 1 0 0 0 0
|
|
103
|
+
5 2 1 0 0 0 0
|
|
104
|
+
6 3 2 0 0 0 0
|
|
105
|
+
7 2 2 0 0 0 0
|
|
106
|
+
8 4 2 0 0 0 0
|
|
107
|
+
9 6 1 0 0 0 0
|
|
108
|
+
10 9 2 0 0 0 0
|
|
109
|
+
11 10 1 0 0 0 0
|
|
110
|
+
12 11 1 0 0 0 0
|
|
111
|
+
13 14 2 0 0 0 0
|
|
112
|
+
14 9 1 0 0 0 0
|
|
113
|
+
15 13 1 0 0 0 0
|
|
114
|
+
3 5 1 0 0 0 0
|
|
115
|
+
15 11 2 0 0 0 0
|
|
116
|
+
M END
|
|
117
|
+
",
|
|
118
|
+
}
|
|
119
|
+
`);
|
|
120
|
+
});
|
|
Binary file
|
package/src/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './parse';
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './iterator';
|
package/src/iterator.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
|
|
3
|
+
import { parseString } from 'dynamic-typing';
|
|
4
|
+
/**
|
|
5
|
+
* Parse a SDF file
|
|
6
|
+
* @param {string|ArrayBuffer|Uint8Array} sdf SDF file to parse
|
|
7
|
+
* @param {object} [options={}]
|
|
8
|
+
* @param {Function} [options.filter] Callback allowing to filter the molecules
|
|
9
|
+
* @param {boolean} [options.dynamicTyping] Dynamically type the data
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export async function* iterator(readStream, options = {}) {
|
|
13
|
+
const lines = createInterface(readStream);
|
|
14
|
+
const currentLines = [];
|
|
15
|
+
options = { ...options };
|
|
16
|
+
if (options.dynamicTyping === undefined) options.dynamicTyping = true;
|
|
17
|
+
|
|
18
|
+
options.eol = '\n';
|
|
19
|
+
for await (let line of lines) {
|
|
20
|
+
if (line.startsWith('$$$$')) {
|
|
21
|
+
const molecule = getMolecule(currentLines.join(options.eol), options);
|
|
22
|
+
if (!options.filter || options.filter(molecule)) {
|
|
23
|
+
yield molecule;
|
|
24
|
+
}
|
|
25
|
+
currentLines.length = 0;
|
|
26
|
+
} else {
|
|
27
|
+
currentLines.push(line);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getMolecule(sdfPart, options) {
|
|
33
|
+
let parts = sdfPart.split(`${options.eol}>`);
|
|
34
|
+
if (parts.length === 0 || parts[0].length <= 5) return;
|
|
35
|
+
let molecule = {};
|
|
36
|
+
molecule.molfile = parts[0] + options.eol;
|
|
37
|
+
for (let j = 1; j < parts.length; j++) {
|
|
38
|
+
let lines = parts[j].split(options.eol);
|
|
39
|
+
let from = lines[0].indexOf('<');
|
|
40
|
+
let to = lines[0].indexOf('>');
|
|
41
|
+
let label = lines[0].substring(from + 1, to);
|
|
42
|
+
for (let k = 1; k < lines.length - 1; k++) {
|
|
43
|
+
if (molecule[label]) {
|
|
44
|
+
molecule[label] += options.eol + lines[k];
|
|
45
|
+
} else {
|
|
46
|
+
molecule[label] = lines[k];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (options.dynamicTyping) {
|
|
50
|
+
molecule[label] = parseString(molecule[label]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return molecule;
|
|
54
|
+
}
|
package/src/parse.js
CHANGED
|
@@ -1,46 +1,51 @@
|
|
|
1
1
|
import { ensureString } from 'ensure-string';
|
|
2
2
|
|
|
3
3
|
import { getEntriesBoundaries } from './getEntriesBoundaries';
|
|
4
|
+
import { getMolecule } from './util/getMolecule';
|
|
4
5
|
/**
|
|
5
6
|
* Parse a SDF file
|
|
6
7
|
* @param {string|ArrayBuffer|Uint8Array} sdf SDF file to parse
|
|
7
|
-
* @param {
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {
|
|
8
|
+
* @param {object} [options={}]
|
|
9
|
+
* @param {string[]} [options.include] List of fields to include
|
|
10
|
+
* @param {string[]} [options.exclude] List of fields to exclude
|
|
11
|
+
* @param {Function} [options.filter] Callback allowing to filter the molecules
|
|
10
12
|
* @param {boolean} [options.dynamicTyping] Dynamically type the data
|
|
11
13
|
* @param {object} [options.modifiers] Object containing callbacks to apply on some specific fields
|
|
12
14
|
* @param {boolean} [options.mixedEOL=false] Set to true if you know there is a mixture between \r\n and \n
|
|
15
|
+
* @param {string} [options.eol] Specify the end of line character. Default will be the one found in the file
|
|
13
16
|
*/
|
|
14
17
|
export function parse(sdf, options = {}) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
modifiers = {},
|
|
20
|
-
forEach = {},
|
|
21
|
-
dynamicTyping = true,
|
|
22
|
-
} = options;
|
|
18
|
+
options = { ...options };
|
|
19
|
+
if (options.modifiers === undefined) options.modifiers = {};
|
|
20
|
+
if (options.forEach === undefined) options.forEach = {};
|
|
21
|
+
if (options.dynamicTyping === undefined) options.dynamicTyping = true;
|
|
23
22
|
|
|
24
23
|
sdf = ensureString(sdf);
|
|
25
24
|
if (typeof sdf !== 'string') {
|
|
26
25
|
throw new TypeError('Parameter "sdf" must be a string');
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
if (options.eol === undefined) {
|
|
29
|
+
options.eol = '\n';
|
|
30
|
+
if (options.mixedEOL) {
|
|
31
|
+
sdf = sdf.replace(/\r\n/g, '\n');
|
|
32
|
+
sdf = sdf.replace(/\r/g, '\n');
|
|
33
|
+
} else {
|
|
34
|
+
// we will find the delimiter in order to be much faster and not use regular expression
|
|
35
|
+
let header = sdf.substr(0, 1000);
|
|
36
|
+
if (header.indexOf('\r\n') > -1) {
|
|
37
|
+
options.eol = '\r\n';
|
|
38
|
+
} else if (header.indexOf('\r') > -1) {
|
|
39
|
+
options.eol = '\r';
|
|
40
|
+
}
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
let entriesBoundaries = getEntriesBoundaries(
|
|
44
|
+
let entriesBoundaries = getEntriesBoundaries(
|
|
45
|
+
sdf,
|
|
46
|
+
`${options.eol}$$$$`,
|
|
47
|
+
options.eol,
|
|
48
|
+
);
|
|
44
49
|
let molecules = [];
|
|
45
50
|
let labels = {};
|
|
46
51
|
|
|
@@ -48,72 +53,18 @@ export function parse(sdf, options = {}) {
|
|
|
48
53
|
|
|
49
54
|
for (let i = 0; i < entriesBoundaries.length; i++) {
|
|
50
55
|
let sdfPart = sdf.substring(...entriesBoundaries[i]);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let label = lines[0].substring(from + 1, to);
|
|
61
|
-
currentLabels.push(label);
|
|
62
|
-
if (!labels[label]) {
|
|
63
|
-
labels[label] = {
|
|
64
|
-
counter: 0,
|
|
65
|
-
isNumeric: dynamicTyping,
|
|
66
|
-
keep: false,
|
|
67
|
-
};
|
|
68
|
-
if (
|
|
69
|
-
(!exclude || exclude.indexOf(label) === -1) &&
|
|
70
|
-
(!include || include.indexOf(label) > -1)
|
|
71
|
-
) {
|
|
72
|
-
labels[label].keep = true;
|
|
73
|
-
if (modifiers[label]) {
|
|
74
|
-
labels[label].modifier = modifiers[label];
|
|
75
|
-
}
|
|
76
|
-
if (forEach[label]) {
|
|
77
|
-
labels[label].forEach = forEach[label];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (labels[label].keep) {
|
|
82
|
-
for (let k = 1; k < lines.length - 1; k++) {
|
|
83
|
-
if (molecule[label]) {
|
|
84
|
-
molecule[label] += eol + lines[k];
|
|
85
|
-
} else {
|
|
86
|
-
molecule[label] = lines[k];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (labels[label].modifier) {
|
|
90
|
-
let modifiedValue = labels[label].modifier(molecule[label]);
|
|
91
|
-
if (modifiedValue === undefined || modifiedValue === null) {
|
|
92
|
-
delete molecule[label];
|
|
93
|
-
} else {
|
|
94
|
-
molecule[label] = modifiedValue;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (labels[label].isNumeric) {
|
|
98
|
-
if (
|
|
99
|
-
!isFinite(molecule[label]) ||
|
|
100
|
-
molecule[label].match(/^0[0-9]/)
|
|
101
|
-
) {
|
|
102
|
-
labels[label].isNumeric = false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (!filter || filter(molecule)) {
|
|
108
|
-
molecules.push(molecule);
|
|
109
|
-
// only now we can increase the counter
|
|
110
|
-
for (let j = 0; j < currentLabels.length; j++) {
|
|
111
|
-
labels[currentLabels[j]].counter++;
|
|
112
|
-
}
|
|
56
|
+
|
|
57
|
+
let currentLabels = [];
|
|
58
|
+
const molecule = getMolecule(sdfPart, labels, currentLabels, options);
|
|
59
|
+
if (!molecule) continue;
|
|
60
|
+
if (!options.filter || options.filter(molecule)) {
|
|
61
|
+
molecules.push(molecule);
|
|
62
|
+
// only now we can increase the counter
|
|
63
|
+
for (let j = 0; j < currentLabels.length; j++) {
|
|
64
|
+
labels[currentLabels[j]].counter++;
|
|
113
65
|
}
|
|
114
66
|
}
|
|
115
67
|
}
|
|
116
|
-
|
|
117
68
|
// all numeric fields should be converted to numbers
|
|
118
69
|
for (let label in labels) {
|
|
119
70
|
let currentLabel = labels[label];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function getMolecule(sdfPart, labels, currentLabels, options) {
|
|
2
|
+
let parts = sdfPart.split(`${options.eol}>`);
|
|
3
|
+
if (parts.length === 0 || parts[0].length <= 5) return;
|
|
4
|
+
let molecule = {};
|
|
5
|
+
molecule.molfile = parts[0] + options.eol;
|
|
6
|
+
for (let j = 1; j < parts.length; j++) {
|
|
7
|
+
let lines = parts[j].split(options.eol);
|
|
8
|
+
let from = lines[0].indexOf('<');
|
|
9
|
+
let to = lines[0].indexOf('>');
|
|
10
|
+
let label = lines[0].substring(from + 1, to);
|
|
11
|
+
currentLabels.push(label);
|
|
12
|
+
if (!labels[label]) {
|
|
13
|
+
labels[label] = {
|
|
14
|
+
counter: 0,
|
|
15
|
+
isNumeric: options.dynamicTyping,
|
|
16
|
+
keep: false,
|
|
17
|
+
};
|
|
18
|
+
if (
|
|
19
|
+
(!options.exclude || options.exclude.indexOf(label) === -1) &&
|
|
20
|
+
(!options.include || options.include.indexOf(label) > -1)
|
|
21
|
+
) {
|
|
22
|
+
labels[label].keep = true;
|
|
23
|
+
if (options.modifiers[label]) {
|
|
24
|
+
labels[label].modifier = options.modifiers[label];
|
|
25
|
+
}
|
|
26
|
+
if (options.forEach[label]) {
|
|
27
|
+
labels[label].forEach = options.forEach[label];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (labels[label].keep) {
|
|
32
|
+
for (let k = 1; k < lines.length - 1; k++) {
|
|
33
|
+
if (molecule[label]) {
|
|
34
|
+
molecule[label] += options.eol + lines[k];
|
|
35
|
+
} else {
|
|
36
|
+
molecule[label] = lines[k];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (labels[label].modifier) {
|
|
40
|
+
let modifiedValue = labels[label].modifier(molecule[label]);
|
|
41
|
+
if (modifiedValue === undefined || modifiedValue === null) {
|
|
42
|
+
delete molecule[label];
|
|
43
|
+
} else {
|
|
44
|
+
molecule[label] = modifiedValue;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (labels[label].isNumeric) {
|
|
48
|
+
if (!isFinite(molecule[label]) || molecule[label].match(/^0[0-9]/)) {
|
|
49
|
+
labels[label].isNumeric = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return molecule;
|
|
55
|
+
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
|
|
3
|
-
import callbackStream from 'callback-stream';
|
|
4
|
-
import OCL from 'openchemlib/minimal';
|
|
5
|
-
|
|
6
|
-
import { entries, molecules } from '..';
|
|
7
|
-
|
|
8
|
-
const cbStream = callbackStream.bind(null, { objectMode: true });
|
|
9
|
-
|
|
10
|
-
describe('stream', () => {
|
|
11
|
-
it('entries', () =>
|
|
12
|
-
new Promise((resolve) => {
|
|
13
|
-
fs.createReadStream(`${__dirname}/test.sdf`)
|
|
14
|
-
.pipe(entries())
|
|
15
|
-
.pipe(
|
|
16
|
-
cbStream((err, data) => {
|
|
17
|
-
expect(err).toBeNull();
|
|
18
|
-
expect(data).toHaveLength(128);
|
|
19
|
-
expect(data[0]).toContain('-ISIS- 04231216572D');
|
|
20
|
-
const mol = OCL.Molecule.fromMolfile(data[5]);
|
|
21
|
-
expect(mol.toMolfile()).toContain(
|
|
22
|
-
'17 18 0 0 0 0 0 0 0 0999 V2000',
|
|
23
|
-
);
|
|
24
|
-
resolve();
|
|
25
|
-
}),
|
|
26
|
-
);
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
it('molecules', () =>
|
|
30
|
-
new Promise((resolve) => {
|
|
31
|
-
fs.createReadStream(`${__dirname}/test.sdf`)
|
|
32
|
-
.pipe(molecules())
|
|
33
|
-
.pipe(
|
|
34
|
-
cbStream((err, data) => {
|
|
35
|
-
expect(err).toBeNull();
|
|
36
|
-
expect(data).toHaveLength(128);
|
|
37
|
-
expect(data[0]).toMatchObject({
|
|
38
|
-
Code: '0100380824',
|
|
39
|
-
CLogP: 2.7,
|
|
40
|
-
});
|
|
41
|
-
expect(data[0].molfile).toContain('-ISIS- 04231216572D');
|
|
42
|
-
resolve();
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
it('molecules - full result', () =>
|
|
48
|
-
new Promise((resolve) => {
|
|
49
|
-
fs.createReadStream(`${__dirname}/test.sdf`)
|
|
50
|
-
.pipe(molecules({ fullResult: true }))
|
|
51
|
-
.pipe(
|
|
52
|
-
cbStream((err, data) => {
|
|
53
|
-
expect(err).toBeNull();
|
|
54
|
-
expect(data).toHaveLength(128);
|
|
55
|
-
expect(data[0]).toMatchObject({
|
|
56
|
-
labels: [
|
|
57
|
-
'Code',
|
|
58
|
-
'Number of H-Donors',
|
|
59
|
-
'Number of H-Acceptors',
|
|
60
|
-
'Number of Rotatable bonds',
|
|
61
|
-
'CLogP',
|
|
62
|
-
],
|
|
63
|
-
});
|
|
64
|
-
expect(data[0].molecules).toHaveLength(1);
|
|
65
|
-
resolve();
|
|
66
|
-
}),
|
|
67
|
-
);
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
it('molecules with filter', () =>
|
|
71
|
-
new Promise((resolve) => {
|
|
72
|
-
fs.createReadStream(`${__dirname}/test.sdf`)
|
|
73
|
-
.pipe(
|
|
74
|
-
molecules({
|
|
75
|
-
filter: (entry) => entry.Code === '0100380869',
|
|
76
|
-
}),
|
|
77
|
-
)
|
|
78
|
-
.pipe(
|
|
79
|
-
cbStream((err, data) => {
|
|
80
|
-
expect(err).toBeNull();
|
|
81
|
-
expect(data).toHaveLength(1);
|
|
82
|
-
resolve();
|
|
83
|
-
}),
|
|
84
|
-
);
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
it('async iteration', async () => {
|
|
88
|
-
const stream = fs
|
|
89
|
-
.createReadStream(`${__dirname}/test.sdf`)
|
|
90
|
-
.pipe(molecules());
|
|
91
|
-
let count = 0;
|
|
92
|
-
for await (const molecule of stream) {
|
|
93
|
-
count++;
|
|
94
|
-
expect(molecule.molfile.toString()).toContain('0999 V2000');
|
|
95
|
-
}
|
|
96
|
-
expect(count).toBe(128);
|
|
97
|
-
});
|
|
98
|
-
});
|
package/src/stream.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import pipeline from 'pumpify';
|
|
2
|
-
import split2 from 'split2';
|
|
3
|
-
import through2 from 'through2';
|
|
4
|
-
import filter from 'through2-filter';
|
|
5
|
-
|
|
6
|
-
import { parse } from './parse';
|
|
7
|
-
|
|
8
|
-
const filterStream = filter.bind(null, { objectMode: true });
|
|
9
|
-
|
|
10
|
-
function filterCb(chunk) {
|
|
11
|
-
return chunk.length > 1 && chunk.trim().length > 1;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function entries() {
|
|
15
|
-
return pipeline.obj(
|
|
16
|
-
split2(/\r?\n\${4}.*\r?\n/),
|
|
17
|
-
filterStream(filterCb),
|
|
18
|
-
through2({ objectMode: true }, function process(value, encoding, callback) {
|
|
19
|
-
const eol = value.includes('\r\n') ? '\r\n' : '\n';
|
|
20
|
-
this.push(`${value + eol}$$$$${eol}`);
|
|
21
|
-
callback();
|
|
22
|
-
}),
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function molecules(options) {
|
|
27
|
-
return pipeline.obj(
|
|
28
|
-
entries(),
|
|
29
|
-
through2({ objectMode: true }, function process(value, encoding, callback) {
|
|
30
|
-
try {
|
|
31
|
-
const parsed = parse(value, options);
|
|
32
|
-
if (parsed.molecules.length === 1) {
|
|
33
|
-
if (options && options.fullResult) {
|
|
34
|
-
this.push(parsed);
|
|
35
|
-
} else {
|
|
36
|
-
this.push(parsed.molecules[0]);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
callback();
|
|
40
|
-
} catch (e) {
|
|
41
|
-
callback(e);
|
|
42
|
-
}
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
}
|