@iebh/polyglot 4.5.0 → 4.6.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/.ignore +1 -1
- package/.vscode/launch.json +36 -0
- package/LICENSE +20 -20
- package/README.md +282 -262
- package/lib/data/fieldCodesObject.js +1 -3
- package/lib/data/fieldCodesParse.js +0 -2
- package/lib/data/meshObject.js +1 -3
- package/lib/data/meshTranslationsObject.js +1 -3
- package/lib/data/meshTranslationsParse.js +0 -2
- package/lib/index.js +42 -59
- package/lib/modules/engines/generic.js +30 -90
- package/lib/modules/engines.js +18 -18
- package/lib/modules/global.js +11 -12
- package/lib/modules/parse.js +270 -320
- package/lib/modules/tools.js +99 -134
- package/package.json +59 -54
package/lib/modules/parse.js
CHANGED
|
@@ -4,38 +4,31 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.parse = void 0;
|
|
7
|
-
|
|
8
7
|
var _global = _interopRequireDefault(require("./global.js"));
|
|
9
|
-
|
|
10
8
|
var _tools = _interopRequireDefault(require("./tools.js"));
|
|
11
|
-
|
|
12
9
|
var _lodash = _interopRequireDefault(require("lodash"));
|
|
13
|
-
|
|
14
10
|
var _fieldCodesParse = _interopRequireDefault(require("../data/fieldCodesParse.js"));
|
|
15
|
-
|
|
16
11
|
var _meshTranslationsParse = _interopRequireDefault(require("../data/meshTranslationsParse.js"));
|
|
17
|
-
|
|
18
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
19
|
-
|
|
20
13
|
// Parsing Objects
|
|
14
|
+
|
|
21
15
|
// Escape all regular expression chars except for pipe
|
|
22
16
|
function escapeRegExp(string) {
|
|
23
17
|
return string.replace(/[.*+?^${}()[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
24
18
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Parse a given string into a lexical object tree
|
|
27
|
-
* This tree can then be recompiled via each engines compile()
|
|
28
|
-
* @param {string} query The query string to compile. This can be multiline
|
|
29
|
-
* @param {Object} [options] Optional options to use when parsing
|
|
30
|
-
* @param {boolean} [options.groupLines=false] Wrap lines inside their own groups (only applies if multiple lines are present)
|
|
31
|
-
* @param {boolean} [options.groupLinesAlways=true] Group lines even if there is only one apparent line (i.e. enclose single line queries within brackets)
|
|
32
|
-
* @param {boolean} [options.transposeLines=true] Insert all line references where needed (e.g. `1 - 3/OR`)
|
|
33
|
-
* @param {boolean} [options.removeNumbering=true] Remove any number prefixes from lines - this is a classic copy/paste error from certain online search engines (e.g. `1. Term` -> `term`)
|
|
34
|
-
* @param {boolean} [options.preserveNewlines=true] Preserve newlines in the output as 'raw' tree nodes
|
|
35
|
-
* @return {array} Array representing the parsed tree nodes
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Parse a given string into a lexical object tree
|
|
22
|
+
* This tree can then be recompiled via each engines compile()
|
|
23
|
+
* @param {string} query The query string to compile. This can be multiline
|
|
24
|
+
* @param {Object} [options] Optional options to use when parsing
|
|
25
|
+
* @param {boolean} [options.groupLines=false] Wrap lines inside their own groups (only applies if multiple lines are present)
|
|
26
|
+
* @param {boolean} [options.groupLinesAlways=true] Group lines even if there is only one apparent line (i.e. enclose single line queries within brackets)
|
|
27
|
+
* @param {boolean} [options.transposeLines=true] Insert all line references where needed (e.g. `1 - 3/OR`)
|
|
28
|
+
* @param {boolean} [options.removeNumbering=true] Remove any number prefixes from lines - this is a classic copy/paste error from certain online search engines (e.g. `1. Term` -> `term`)
|
|
29
|
+
* @param {boolean} [options.preserveNewlines=true] Preserve newlines in the output as 'raw' tree nodes
|
|
30
|
+
* @return {array} Array representing the parsed tree nodes
|
|
31
|
+
*/
|
|
39
32
|
var parse = function parse(query, options) {
|
|
40
33
|
var settings = _lodash["default"].defaults(options, {
|
|
41
34
|
groupLines: false,
|
|
@@ -44,31 +37,24 @@ var parse = function parse(query, options) {
|
|
|
44
37
|
preserveNewlines: true,
|
|
45
38
|
transposeLines: true
|
|
46
39
|
});
|
|
47
|
-
|
|
48
40
|
_global["default"].variables.no_field_tag = []; // Empty array of offsets each time the query is parsed
|
|
49
41
|
|
|
50
42
|
var q = query + ''; // Clone query
|
|
51
|
-
|
|
52
43
|
var tree = {
|
|
53
44
|
nodes: []
|
|
54
45
|
}; // Tree is the full parsed tree
|
|
55
|
-
|
|
56
46
|
var branchStack = [tree]; // Stack for where we are within the tree (will get pushed when a new group is encountered)
|
|
57
|
-
|
|
58
47
|
var branch = tree; // Branch is the parent of leaf (branch always equals last element of branchStack)
|
|
59
|
-
|
|
60
48
|
var lastGroup; // Optional reference to the previously created group (used to pin things)
|
|
61
|
-
|
|
62
49
|
var leaf = branch.nodes; // Leaf is the currently active leaf node (usually branch.nodes)
|
|
63
|
-
|
|
64
50
|
var afterWhitespace = true; // Set to true when the current character is following whitespace, a newline or the very start of the query
|
|
65
|
-
|
|
66
51
|
var lineRefs = {}; // Object lookup for line references (usually something like `1. Foo`), only populated if `transposeLines` is true
|
|
67
|
-
// Operate in line-by-line mode? {{{
|
|
68
52
|
|
|
53
|
+
// Operate in line-by-line mode? {{{
|
|
69
54
|
if (settings.transposeLines || settings.groupLines || settings.removeNumbering) {
|
|
70
|
-
var lines = q.split('\n');
|
|
55
|
+
var lines = q.split('\n');
|
|
71
56
|
|
|
57
|
+
// Remove numbering {{{
|
|
72
58
|
if (settings.removeNumbering) {
|
|
73
59
|
var match;
|
|
74
60
|
lines = lines.map(function (line) {
|
|
@@ -78,41 +64,38 @@ var parse = function parse(query, options) {
|
|
|
78
64
|
return line;
|
|
79
65
|
}
|
|
80
66
|
});
|
|
81
|
-
}
|
|
82
|
-
//
|
|
83
|
-
|
|
67
|
+
}
|
|
68
|
+
// }}}
|
|
84
69
|
|
|
70
|
+
// Group line content {{{
|
|
85
71
|
if (settings.groupLines && (settings.groupLinesAlways || lines.length > 1)) {
|
|
86
72
|
// Wrap lines provided they are not blank and are not just 'and', 'or', 'not' by themselves or a comment
|
|
87
73
|
lines = lines.map(function (line) {
|
|
88
74
|
return _lodash["default"].trim(line) && !/^\s*(and|or|not)\s*$/i.test(line) && !/^\s*#/.test(line) ? '(' + line + ')' : line;
|
|
89
75
|
});
|
|
90
|
-
}
|
|
91
|
-
|
|
76
|
+
}
|
|
77
|
+
// }}}
|
|
92
78
|
|
|
93
79
|
q = lines.join('\n'); // Join up lines again
|
|
94
|
-
}
|
|
95
|
-
//
|
|
80
|
+
}
|
|
81
|
+
// }}}
|
|
96
82
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
*
|
|
83
|
+
// Utility functions {{{
|
|
84
|
+
/**
|
|
85
|
+
* Trim previous leaf content if it has any text
|
|
86
|
+
* The leaf will be removed completely if it is now blank
|
|
100
87
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
88
|
function trimLastLeaf() {
|
|
104
89
|
if (leaf && _lodash["default"].includes(['phrase', 'raw'], leaf.type) && / $/.test(leaf.content)) {
|
|
105
90
|
leaf.content = leaf.content.substr(0, leaf.content.length - 1);
|
|
106
91
|
if (!leaf.content) branch.nodes.pop();
|
|
107
92
|
}
|
|
108
93
|
}
|
|
109
|
-
|
|
110
94
|
;
|
|
111
|
-
/**
|
|
112
|
-
* End the previous line branch and create a new one
|
|
113
|
-
* this function is run every time a new raw node is inserted
|
|
95
|
+
/**
|
|
96
|
+
* End the previous line branch and create a new one
|
|
97
|
+
* this function is run every time a new raw node is inserted
|
|
114
98
|
*/
|
|
115
|
-
|
|
116
99
|
function newLine(currentNumber) {
|
|
117
100
|
lastGroup = branch;
|
|
118
101
|
branch = branchStack.pop();
|
|
@@ -128,10 +111,10 @@ var parse = function parse(query, options) {
|
|
|
128
111
|
branch = newGroup;
|
|
129
112
|
leaf = branch.nodes;
|
|
130
113
|
}
|
|
114
|
+
;
|
|
115
|
+
// }}}
|
|
131
116
|
|
|
132
|
-
; // }}}
|
|
133
117
|
// Create a group for the first line
|
|
134
|
-
|
|
135
118
|
var newGroup = {
|
|
136
119
|
type: 'line',
|
|
137
120
|
number: 1,
|
|
@@ -142,20 +125,19 @@ var parse = function parse(query, options) {
|
|
|
142
125
|
branchStack.push(branch);
|
|
143
126
|
branch = newGroup;
|
|
144
127
|
leaf = branch.nodes;
|
|
145
|
-
var lineNumber = 1;
|
|
146
|
-
|
|
147
|
-
var userLineNumber = false; // Variable to store byte offset of string at current point
|
|
128
|
+
var lineNumber = 1;
|
|
148
129
|
|
|
149
|
-
|
|
130
|
+
// Variable to store whether there is user entered line numbering
|
|
131
|
+
var userLineNumber = false;
|
|
132
|
+
// Variable to store byte offset of string at current point
|
|
133
|
+
var offset = 0;
|
|
150
134
|
|
|
135
|
+
// Create string of field codes seperated by pipe operator
|
|
151
136
|
var fieldCodes = escapeRegExp(Object.keys(_fieldCodesParse["default"]).join("|"));
|
|
152
137
|
var meshTranslations = escapeRegExp(Object.keys(_meshTranslationsParse["default"]).join("|"));
|
|
153
|
-
|
|
154
138
|
while (q.length) {
|
|
155
139
|
var cropString = true; // Whether to remove one charcater from the beginning of the string (set to false if the lexical match handles this behaviour itself)
|
|
156
|
-
|
|
157
140
|
var match;
|
|
158
|
-
|
|
159
141
|
if (/^\(/.test(q)) {
|
|
160
142
|
var newGroup = {
|
|
161
143
|
type: 'group',
|
|
@@ -167,13 +149,12 @@ var parse = function parse(query, options) {
|
|
|
167
149
|
leaf = branch.nodes;
|
|
168
150
|
} else if (/^\)/.test(q)) {
|
|
169
151
|
lastGroup = branch;
|
|
170
|
-
|
|
171
152
|
if (branchStack.length > 0) {
|
|
172
153
|
branch = branchStack.pop();
|
|
173
|
-
} else {
|
|
154
|
+
} else {
|
|
155
|
+
// TODO: Code for popover message
|
|
174
156
|
// branch.msg = "Extra closing bracket removed after term"
|
|
175
157
|
}
|
|
176
|
-
|
|
177
158
|
leaf = branch.nodes;
|
|
178
159
|
} else if (match = /^([0-9]+)\s*[‐\-]\s*([0-9]+)(?:\/(AND|OR|NOT))/i.exec(q)) {
|
|
179
160
|
// 1-7/OR
|
|
@@ -208,7 +189,6 @@ var parse = function parse(query, options) {
|
|
|
208
189
|
});
|
|
209
190
|
offset += match[1].length;
|
|
210
191
|
q = q.substr(match[1].length); // NOTE we only move by the digits, not the whole expression - so we can still handle the AND/OR correctly
|
|
211
|
-
|
|
212
192
|
cropString = false;
|
|
213
193
|
} else {
|
|
214
194
|
leaf.content += match[1];
|
|
@@ -218,27 +198,23 @@ var parse = function parse(query, options) {
|
|
|
218
198
|
} else if (match = /^((AND|OR|NOT) +#?([0-9]+))($(?![\r\n])|\s+|\))/i.exec(q)) {
|
|
219
199
|
// AND 2...
|
|
220
200
|
trimLastLeaf();
|
|
221
|
-
|
|
222
201
|
switch (match[2].toLowerCase()) {
|
|
223
202
|
case "and":
|
|
224
203
|
branch.nodes.push({
|
|
225
204
|
type: 'joinAnd'
|
|
226
205
|
});
|
|
227
206
|
break;
|
|
228
|
-
|
|
229
207
|
case "or":
|
|
230
208
|
branch.nodes.push({
|
|
231
209
|
type: 'joinOr'
|
|
232
210
|
});
|
|
233
211
|
break;
|
|
234
|
-
|
|
235
212
|
case "not":
|
|
236
213
|
branch.nodes.push({
|
|
237
214
|
type: 'joinNot'
|
|
238
215
|
});
|
|
239
216
|
break;
|
|
240
217
|
}
|
|
241
|
-
|
|
242
218
|
leaf = undefined;
|
|
243
219
|
cropString = false;
|
|
244
220
|
branch.nodes.push({
|
|
@@ -316,265 +292,243 @@ var parse = function parse(query, options) {
|
|
|
316
292
|
offset += match[0].length;
|
|
317
293
|
q = q.substr(match[0].length);
|
|
318
294
|
cropString = false;
|
|
319
|
-
}
|
|
295
|
+
}
|
|
296
|
+
// MESHTRANSLATIONS {{{
|
|
320
297
|
else if (afterWhitespace && (match = new RegExp("^(".concat(meshTranslations, ")"), "i").exec(q.toLowerCase().replace(/"/g, '')))) {
|
|
298
|
+
branch.nodes.push({
|
|
299
|
+
type: 'meshTranslation',
|
|
300
|
+
field: _meshTranslationsParse["default"][match[1]]
|
|
301
|
+
});
|
|
302
|
+
match = /.+?[\.\[]\S+[\.\]]/.exec(q);
|
|
303
|
+
offset += match[0].length;
|
|
304
|
+
q = q.substr(match[0].length);
|
|
305
|
+
cropString = false;
|
|
306
|
+
}
|
|
307
|
+
/// }}}
|
|
308
|
+
// MESH {{{
|
|
309
|
+
else if (match = /^\[(mesh terms|mesh|mh)(:NoExp|:no exp)?\]/i.exec(q)) {
|
|
310
|
+
// Mesh term - PubMed syntax
|
|
311
|
+
leaf.type = 'mesh';
|
|
312
|
+
leaf.field = match[2] ? 'Mesh search (Not exploded)' : 'Mesh search (exploded)';
|
|
313
|
+
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
314
|
+
offset += match[0].length;
|
|
315
|
+
q = q.substr(match[0].length);
|
|
316
|
+
cropString = false;
|
|
317
|
+
} else if (match = /^([^\s\/]*)\/?([^\s\)\/]+)?\[(majr|mesh major topic)(:NoExp|:no exp)?\]/i.exec(q)) {
|
|
318
|
+
// Major Mesh term - PubMed syntax (No Quotes)
|
|
319
|
+
var newLeaf = {};
|
|
320
|
+
newLeaf.type = 'mesh';
|
|
321
|
+
newLeaf.content = match[1];
|
|
322
|
+
if (match[2]) {
|
|
323
|
+
newLeaf.heading = match[2];
|
|
324
|
+
}
|
|
325
|
+
newLeaf.field = match[4] ? 'MeSH Major Topic search (Not exploded)' : 'MeSH Major Topic search (exploded)';
|
|
326
|
+
if (/^["“”].*["“”]$/.test(newLeaf.content)) newLeaf.content = newLeaf.content.substr(1, newLeaf.content.length - 2); // Remove wrapping '"' characters
|
|
327
|
+
branch.nodes.push(newLeaf);
|
|
328
|
+
offset += match[0].length;
|
|
329
|
+
q = q.substr(match[0].length);
|
|
330
|
+
cropString = false;
|
|
331
|
+
} else if (match = /^"([^\\[]*)"\/?([^\s\)\/]+)?\[(majr|mesh major topic)(:NoExp|:no exp)?\]/i.exec(q)) {
|
|
332
|
+
// Major Mesh term - PubMed syntax (With Quotes)
|
|
333
|
+
var newLeaf = {};
|
|
334
|
+
newLeaf.type = 'mesh';
|
|
335
|
+
newLeaf.content = match[1];
|
|
336
|
+
if (match[2]) {
|
|
337
|
+
newLeaf.heading = match[2];
|
|
338
|
+
}
|
|
339
|
+
newLeaf.field = match[4] ? 'MeSH Major Topic search (Not exploded)' : 'MeSH Major Topic search (exploded)';
|
|
340
|
+
if (/^["“”].*["“”]$/.test(newLeaf.content)) newLeaf.content = newLeaf.content.substr(1, newLeaf.content.length - 2); // Remove wrapping '"' characters
|
|
341
|
+
branch.nodes.push(newLeaf);
|
|
342
|
+
offset += match[0].length;
|
|
343
|
+
q = q.substr(match[0].length);
|
|
344
|
+
cropString = false;
|
|
345
|
+
} else if (match = /^\[(mesh subheading|sh)\]/i.exec(q)) {
|
|
346
|
+
// Mesh subheading search - PubMed syntax
|
|
347
|
+
leaf.type = 'mesh';
|
|
348
|
+
leaf.field = 'MeSH subheading search';
|
|
349
|
+
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
350
|
+
offset += match[0].length;
|
|
351
|
+
q = q.substr(match[0].length);
|
|
352
|
+
cropString = false;
|
|
353
|
+
} else if (match = /^\.(fs)\./i.exec(q)) {
|
|
354
|
+
// Mesh subheading search - Ovid syntax
|
|
355
|
+
var useLeaf;
|
|
356
|
+
if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
357
|
+
useLeaf = leaf;
|
|
358
|
+
} else if (_lodash["default"].isArray(leaf) && lastGroup) {
|
|
359
|
+
useLeaf = lastGroup;
|
|
360
|
+
}
|
|
361
|
+
leaf.type = 'mesh';
|
|
362
|
+
useLeaf.field = 'MeSH subheading search';
|
|
363
|
+
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
364
|
+
offset += match[0].length;
|
|
365
|
+
q = q.substr(match[0].length);
|
|
366
|
+
cropString = false;
|
|
367
|
+
} else if (match = /^\.(xm|sh)\./i.exec(q)) {
|
|
368
|
+
// Mesh search (field codes) - Ovid syntax
|
|
369
|
+
var useLeaf;
|
|
370
|
+
if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
371
|
+
useLeaf = leaf;
|
|
372
|
+
} else if (_lodash["default"].isArray(leaf) && lastGroup) {
|
|
373
|
+
useLeaf = lastGroup;
|
|
374
|
+
}
|
|
375
|
+
leaf.type = 'mesh';
|
|
376
|
+
useLeaf.field = match[1] === "xm" ? 'Mesh search (exploded)' : 'Mesh search (Not exploded)';
|
|
377
|
+
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
378
|
+
offset += match[0].length;
|
|
379
|
+
q = q.substr(match[0].length);
|
|
380
|
+
cropString = false;
|
|
381
|
+
} else if ((match = /^(exp "([^*]*?)"\/)\s*/i.exec(q)) || (match = /^(exp ([^*]*?)\/([^\s\)]+)?)\s*/i.exec(q))) {
|
|
382
|
+
// Mesh term - Ovid syntax (exploded)
|
|
383
|
+
var newLeaf = {
|
|
384
|
+
type: 'mesh',
|
|
385
|
+
field: 'Mesh search (exploded)',
|
|
386
|
+
content: match[2]
|
|
387
|
+
};
|
|
388
|
+
if (match[3]) {
|
|
389
|
+
newLeaf.heading = match[3];
|
|
390
|
+
}
|
|
391
|
+
branch.nodes.push(newLeaf);
|
|
392
|
+
offset += match[1].length;
|
|
393
|
+
q = q.substr(match[1].length);
|
|
394
|
+
cropString = false;
|
|
395
|
+
afterWhitespace = true;
|
|
396
|
+
} else if ((match = /^(exp \*"([^*]*?)"\/)\s*/i.exec(q)) || (match = /^(exp \*([^*]*?)\/([^\s\)]+)?)\s*/i.exec(q))) {
|
|
397
|
+
// Major Mesh term - Ovid syntax
|
|
398
|
+
var newLeaf = {
|
|
399
|
+
type: 'mesh',
|
|
400
|
+
field: 'MeSH Major Topic search (exploded)',
|
|
401
|
+
content: match[2]
|
|
402
|
+
};
|
|
403
|
+
if (match[3]) {
|
|
404
|
+
newLeaf.heading = match[3];
|
|
405
|
+
}
|
|
406
|
+
branch.nodes.push(newLeaf);
|
|
407
|
+
offset += match[1].length;
|
|
408
|
+
q = q.substr(match[1].length);
|
|
409
|
+
cropString = false;
|
|
410
|
+
afterWhitespace = true;
|
|
411
|
+
} else if (/^\//.test(q) && leaf && leaf.type && leaf.type == 'phrase') {
|
|
412
|
+
// Mesh term - Ovid syntax (non-exploded)
|
|
413
|
+
leaf.type = 'mesh';
|
|
414
|
+
// Major Mesh
|
|
415
|
+
if (leaf.content[0] == "*") {
|
|
416
|
+
leaf.content = leaf.content.substr(1);
|
|
417
|
+
leaf.field = 'MeSH Major Topic search (Not exploded)';
|
|
418
|
+
} else leaf.field = 'Mesh search (Not exploded)';
|
|
419
|
+
}
|
|
420
|
+
// }}}
|
|
421
|
+
else if (match = /^<(.*?)>/.exec(q)) {
|
|
422
|
+
branch.nodes.push({
|
|
423
|
+
type: 'template',
|
|
424
|
+
content: match[1].toLowerCase()
|
|
425
|
+
});
|
|
426
|
+
offset += match[0].length;
|
|
427
|
+
q = q.substr(match[0].length);
|
|
428
|
+
cropString = false;
|
|
429
|
+
} else if (match = /^(\n)+/.exec(q)) {
|
|
430
|
+
if (settings.preserveNewlines) {
|
|
431
|
+
var number_newline = match[0].length;
|
|
321
432
|
branch.nodes.push({
|
|
322
|
-
type: '
|
|
323
|
-
|
|
433
|
+
type: 'raw',
|
|
434
|
+
content: '\n'.repeat(number_newline)
|
|
324
435
|
});
|
|
325
|
-
|
|
436
|
+
leaf = undefined;
|
|
437
|
+
}
|
|
438
|
+
lineNumber += match[0].length;
|
|
439
|
+
if (branchStack.length > 0 && branchStack[branchStack.length - 1].nodes.every(function (node) {
|
|
440
|
+
return node.type !== "group";
|
|
441
|
+
})) {
|
|
442
|
+
// If we are currently inside a group don't add a newline group
|
|
443
|
+
newLine(lineNumber);
|
|
444
|
+
}
|
|
445
|
+
offset += match[0].length;
|
|
446
|
+
q = q.substr(match[0].length);
|
|
447
|
+
cropString = false;
|
|
448
|
+
afterWhitespace = true;
|
|
449
|
+
} else if (match = /^(\r)+/.exec(q)) {
|
|
450
|
+
offset += match[0].length;
|
|
451
|
+
q = q.substr(match[0].length);
|
|
452
|
+
cropString = false;
|
|
453
|
+
afterWhitespace = true;
|
|
454
|
+
}
|
|
455
|
+
// Match field codes {{{
|
|
456
|
+
else if (match = new RegExp("^(".concat(fieldCodes, ") *(\\[mp=[^\\]\\n]*\\])?"), "i").exec(q)) {
|
|
457
|
+
// Field specifier - PubMed syntax
|
|
458
|
+
// Figure out the leaf to use (usually the last one) or the previously used group
|
|
459
|
+
var useLeaf;
|
|
460
|
+
if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
461
|
+
useLeaf = leaf;
|
|
462
|
+
} else if (_lodash["default"].isArray(leaf) && lastGroup) {
|
|
463
|
+
useLeaf = lastGroup;
|
|
464
|
+
}
|
|
465
|
+
useLeaf.field = _fieldCodesParse["default"][match[1].toLowerCase()];
|
|
466
|
+
if (match[2]) {
|
|
326
467
|
offset += match[0].length;
|
|
327
468
|
q = q.substr(match[0].length);
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
newLeaf.type = 'mesh';
|
|
361
|
-
newLeaf.content = match[1];
|
|
362
|
-
|
|
363
|
-
if (match[2]) {
|
|
364
|
-
newLeaf.heading = match[2];
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
newLeaf.field = match[4] ? 'MeSH Major Topic search (Not exploded)' : 'MeSH Major Topic search (exploded)';
|
|
368
|
-
if (/^["“”].*["“”]$/.test(newLeaf.content)) newLeaf.content = newLeaf.content.substr(1, newLeaf.content.length - 2); // Remove wrapping '"' characters
|
|
369
|
-
|
|
370
|
-
branch.nodes.push(newLeaf);
|
|
371
|
-
offset += match[0].length;
|
|
372
|
-
q = q.substr(match[0].length);
|
|
373
|
-
cropString = false;
|
|
374
|
-
} else if (match = /^\[(mesh subheading|sh)\]/i.exec(q)) {
|
|
375
|
-
// Mesh subheading search - PubMed syntax
|
|
376
|
-
leaf.type = 'mesh';
|
|
377
|
-
leaf.field = 'MeSH subheading search';
|
|
378
|
-
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
379
|
-
|
|
380
|
-
offset += match[0].length;
|
|
381
|
-
q = q.substr(match[0].length);
|
|
382
|
-
cropString = false;
|
|
383
|
-
} else if (match = /^\.(fs)\./i.exec(q)) {
|
|
384
|
-
// Mesh subheading search - Ovid syntax
|
|
385
|
-
var useLeaf;
|
|
386
|
-
|
|
387
|
-
if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
388
|
-
useLeaf = leaf;
|
|
389
|
-
} else if (_lodash["default"].isArray(leaf) && lastGroup) {
|
|
390
|
-
useLeaf = lastGroup;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
leaf.type = 'mesh';
|
|
394
|
-
useLeaf.field = 'MeSH subheading search';
|
|
395
|
-
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
396
|
-
|
|
469
|
+
} else {
|
|
470
|
+
offset += match[1].length;
|
|
471
|
+
q = q.substr(match[1].length);
|
|
472
|
+
}
|
|
473
|
+
cropString = false;
|
|
474
|
+
}
|
|
475
|
+
// }}}
|
|
476
|
+
/// Comment {{{
|
|
477
|
+
else if (match = /^#([^\)\d\n][^\)\n]+)/.exec(q)) {
|
|
478
|
+
trimLastLeaf();
|
|
479
|
+
branch.nodes.push({
|
|
480
|
+
type: 'comment',
|
|
481
|
+
content: match[1]
|
|
482
|
+
});
|
|
483
|
+
leaf = undefined;
|
|
484
|
+
offset += match[0].length;
|
|
485
|
+
q = q.substr(match[0].length);
|
|
486
|
+
cropString = false;
|
|
487
|
+
}
|
|
488
|
+
// }}}
|
|
489
|
+
else {
|
|
490
|
+
var nextChar = q.substr(0, 1);
|
|
491
|
+
if ((_lodash["default"].isUndefined(leaf) || _lodash["default"].isArray(leaf)) && nextChar != ' ') {
|
|
492
|
+
// Leaf pointing to array entity - probably not created fallback leaf to append to
|
|
493
|
+
if (/^["“”]$/.test(nextChar) && (match = /^["“”](.*?)["“”]/.exec(q))) {
|
|
494
|
+
// First character is a speachmark - slurp until we see the next one
|
|
495
|
+
leaf = {
|
|
496
|
+
type: 'phrase',
|
|
497
|
+
content: match[1],
|
|
498
|
+
offset: offset
|
|
499
|
+
};
|
|
500
|
+
branch.nodes.push(leaf);
|
|
397
501
|
offset += match[0].length;
|
|
398
502
|
q = q.substr(match[0].length);
|
|
399
503
|
cropString = false;
|
|
400
|
-
} else if (match =
|
|
401
|
-
//
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
leaf.type = 'mesh';
|
|
411
|
-
useLeaf.field = match[1] === "xm" ? 'Mesh search (exploded)' : 'Mesh search (Not exploded)';
|
|
412
|
-
if (/^["“”].*["“”]$/.test(leaf.content)) leaf.content = leaf.content.substr(1, leaf.content.length - 2); // Remove wrapping '"' characters
|
|
413
|
-
|
|
504
|
+
} else if (match = /^[^\s:/[.)]+/.exec(q)) {
|
|
505
|
+
// Slurp the phrase until the space or any character which indicates the end of a phrase
|
|
506
|
+
leaf = {
|
|
507
|
+
type: 'phrase',
|
|
508
|
+
content: match[0],
|
|
509
|
+
offset: offset
|
|
510
|
+
};
|
|
511
|
+
branch.nodes.push(leaf);
|
|
414
512
|
offset += match[0].length;
|
|
415
513
|
q = q.substr(match[0].length);
|
|
416
514
|
cropString = false;
|
|
417
|
-
} else
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
type: '
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
if (match[3]) {
|
|
426
|
-
newLeaf.heading = match[3];
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
branch.nodes.push(newLeaf);
|
|
430
|
-
offset += match[1].length;
|
|
431
|
-
q = q.substr(match[1].length);
|
|
432
|
-
cropString = false;
|
|
433
|
-
afterWhitespace = true;
|
|
434
|
-
} else if ((match = /^(exp \*"([^*]*?)"\/)\s*/i.exec(q)) || (match = /^(exp \*([^*]*?)\/([^\s\)]+)?)\s*/i.exec(q))) {
|
|
435
|
-
// Major Mesh term - Ovid syntax
|
|
436
|
-
var newLeaf = {
|
|
437
|
-
type: 'mesh',
|
|
438
|
-
field: 'MeSH Major Topic search (exploded)',
|
|
439
|
-
content: match[2]
|
|
515
|
+
} else {
|
|
516
|
+
// All other first chars - just dump into a buffer and let it fill slowly
|
|
517
|
+
leaf = {
|
|
518
|
+
type: 'phrase',
|
|
519
|
+
content: nextChar,
|
|
520
|
+
offset: offset
|
|
440
521
|
};
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
q = q.substr(match[1].length);
|
|
449
|
-
cropString = false;
|
|
450
|
-
afterWhitespace = true;
|
|
451
|
-
} else if (/^\//.test(q) && leaf && leaf.type && leaf.type == 'phrase') {
|
|
452
|
-
// Mesh term - Ovid syntax (non-exploded)
|
|
453
|
-
leaf.type = 'mesh'; // Major Mesh
|
|
454
|
-
|
|
455
|
-
if (leaf.content[0] == "*") {
|
|
456
|
-
leaf.content = leaf.content.substr(1);
|
|
457
|
-
leaf.field = 'MeSH Major Topic search (Not exploded)';
|
|
458
|
-
} else leaf.field = 'Mesh search (Not exploded)';
|
|
459
|
-
} // }}}
|
|
460
|
-
else if (match = /^<(.*?)>/.exec(q)) {
|
|
461
|
-
branch.nodes.push({
|
|
462
|
-
type: 'template',
|
|
463
|
-
content: match[1].toLowerCase()
|
|
464
|
-
});
|
|
465
|
-
offset += match[0].length;
|
|
466
|
-
q = q.substr(match[0].length);
|
|
467
|
-
cropString = false;
|
|
468
|
-
} else if (match = /^(\n)+/.exec(q)) {
|
|
469
|
-
if (settings.preserveNewlines) {
|
|
470
|
-
var number_newline = match[0].length;
|
|
471
|
-
branch.nodes.push({
|
|
472
|
-
type: 'raw',
|
|
473
|
-
content: '\n'.repeat(number_newline)
|
|
474
|
-
});
|
|
475
|
-
leaf = undefined;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
lineNumber += match[0].length;
|
|
479
|
-
|
|
480
|
-
if (branchStack.length > 0 && branchStack[branchStack.length - 1].nodes.every(function (node) {
|
|
481
|
-
return node.type !== "group";
|
|
482
|
-
})) {
|
|
483
|
-
// If we are currently inside a group don't add a newline group
|
|
484
|
-
newLine(lineNumber);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
offset += match[0].length;
|
|
488
|
-
q = q.substr(match[0].length);
|
|
489
|
-
cropString = false;
|
|
490
|
-
afterWhitespace = true;
|
|
491
|
-
} else if (match = /^(\r)+/.exec(q)) {
|
|
492
|
-
offset += match[0].length;
|
|
493
|
-
q = q.substr(match[0].length);
|
|
494
|
-
cropString = false;
|
|
495
|
-
afterWhitespace = true;
|
|
496
|
-
} // Match field codes {{{
|
|
497
|
-
else if (match = new RegExp("^(".concat(fieldCodes, ") *(\\[mp=[^\\]\\n]*\\])?"), "i").exec(q)) {
|
|
498
|
-
// Field specifier - PubMed syntax
|
|
499
|
-
// Figure out the leaf to use (usually the last one) or the previously used group
|
|
500
|
-
var useLeaf;
|
|
501
|
-
|
|
502
|
-
if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
503
|
-
useLeaf = leaf;
|
|
504
|
-
} else if (_lodash["default"].isArray(leaf) && lastGroup) {
|
|
505
|
-
useLeaf = lastGroup;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
useLeaf.field = _fieldCodesParse["default"][match[1].toLowerCase()];
|
|
509
|
-
|
|
510
|
-
if (match[2]) {
|
|
511
|
-
offset += match[0].length;
|
|
512
|
-
q = q.substr(match[0].length);
|
|
513
|
-
} else {
|
|
514
|
-
offset += match[1].length;
|
|
515
|
-
q = q.substr(match[1].length);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
cropString = false;
|
|
519
|
-
} // }}}
|
|
520
|
-
/// Comment {{{
|
|
521
|
-
else if (match = /^#([^\)\d\n][^\)\n]+)/.exec(q)) {
|
|
522
|
-
trimLastLeaf();
|
|
523
|
-
branch.nodes.push({
|
|
524
|
-
type: 'comment',
|
|
525
|
-
content: match[1]
|
|
526
|
-
});
|
|
527
|
-
leaf = undefined;
|
|
528
|
-
offset += match[0].length;
|
|
529
|
-
q = q.substr(match[0].length);
|
|
530
|
-
cropString = false;
|
|
531
|
-
} // }}}
|
|
532
|
-
else {
|
|
533
|
-
var nextChar = q.substr(0, 1);
|
|
534
|
-
|
|
535
|
-
if ((_lodash["default"].isUndefined(leaf) || _lodash["default"].isArray(leaf)) && nextChar != ' ') {
|
|
536
|
-
// Leaf pointing to array entity - probably not created fallback leaf to append to
|
|
537
|
-
if (/^["“”]$/.test(nextChar) && (match = /^["“”](.*?)["“”]/.exec(q))) {
|
|
538
|
-
// First character is a speachmark - slurp until we see the next one
|
|
539
|
-
leaf = {
|
|
540
|
-
type: 'phrase',
|
|
541
|
-
content: match[1],
|
|
542
|
-
offset: offset
|
|
543
|
-
};
|
|
544
|
-
branch.nodes.push(leaf);
|
|
545
|
-
offset += match[0].length;
|
|
546
|
-
q = q.substr(match[0].length);
|
|
547
|
-
cropString = false;
|
|
548
|
-
} else if (match = /^[^\s:/[.)]+/.exec(q)) {
|
|
549
|
-
// Slurp the phrase until the space or any character which indicates the end of a phrase
|
|
550
|
-
leaf = {
|
|
551
|
-
type: 'phrase',
|
|
552
|
-
content: match[0],
|
|
553
|
-
offset: offset
|
|
554
|
-
};
|
|
555
|
-
branch.nodes.push(leaf);
|
|
556
|
-
offset += match[0].length;
|
|
557
|
-
q = q.substr(match[0].length);
|
|
558
|
-
cropString = false;
|
|
559
|
-
} else {
|
|
560
|
-
// All other first chars - just dump into a buffer and let it fill slowly
|
|
561
|
-
leaf = {
|
|
562
|
-
type: 'phrase',
|
|
563
|
-
content: nextChar,
|
|
564
|
-
offset: offset
|
|
565
|
-
};
|
|
566
|
-
branch.nodes.push(leaf);
|
|
567
|
-
}
|
|
568
|
-
} else if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
569
|
-
leaf.content += nextChar;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
afterWhitespace = nextChar == ' '; // Is the nextChar whitespace? Then set the flag
|
|
573
|
-
}
|
|
522
|
+
branch.nodes.push(leaf);
|
|
523
|
+
}
|
|
524
|
+
} else if (_lodash["default"].isObject(leaf) && leaf.type == 'phrase') {
|
|
525
|
+
leaf.content += nextChar;
|
|
526
|
+
}
|
|
527
|
+
afterWhitespace = nextChar == ' '; // Is the nextChar whitespace? Then set the flag
|
|
528
|
+
}
|
|
574
529
|
|
|
575
530
|
if (cropString) {
|
|
576
531
|
offset += 1; // Increment offset by 1
|
|
577
|
-
|
|
578
532
|
q = q.substr(1); // Crop 1 character
|
|
579
533
|
}
|
|
580
534
|
}
|
|
@@ -582,33 +536,31 @@ var parse = function parse(query, options) {
|
|
|
582
536
|
if (settings.transposeLines) {
|
|
583
537
|
_tools["default"].visit(tree.nodes, ['ref'], function (node, path) {
|
|
584
538
|
var reference;
|
|
585
|
-
var line;
|
|
586
|
-
|
|
539
|
+
var line;
|
|
540
|
+
// Find the matching line
|
|
587
541
|
for (reference in node.ref) {
|
|
588
542
|
var found = false;
|
|
589
|
-
|
|
590
543
|
for (line in tree.nodes) {
|
|
591
544
|
// If custom numbering is used only use nodes that are numbered by the user
|
|
592
545
|
if (userLineNumber) {
|
|
593
546
|
if (tree.nodes[line].number == node.ref[reference] && tree.nodes[line].isNumbered) {
|
|
594
547
|
// Copy the nodes from that line into the reference nodes
|
|
595
|
-
node.nodes.push(Array.from(tree.nodes[line].nodes));
|
|
596
|
-
|
|
548
|
+
node.nodes.push(Array.from(tree.nodes[line].nodes));
|
|
549
|
+
// Pop the raw node
|
|
597
550
|
node.nodes[reference].pop();
|
|
598
551
|
found = true;
|
|
599
552
|
}
|
|
600
553
|
} else {
|
|
601
554
|
if (tree.nodes[line].number == node.ref[reference]) {
|
|
602
555
|
// Copy the nodes from that line into the reference nodes
|
|
603
|
-
node.nodes.push(Array.from(tree.nodes[line].nodes));
|
|
604
|
-
|
|
556
|
+
node.nodes.push(Array.from(tree.nodes[line].nodes));
|
|
557
|
+
// Pop the raw node
|
|
605
558
|
node.nodes[reference].pop();
|
|
606
559
|
found = true;
|
|
607
560
|
}
|
|
608
561
|
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
562
|
+
}
|
|
563
|
+
// Line not found, push error message
|
|
612
564
|
if (!found) {
|
|
613
565
|
node.nodes.push([{
|
|
614
566
|
type: "phrase",
|
|
@@ -618,8 +570,6 @@ var parse = function parse(query, options) {
|
|
|
618
570
|
}
|
|
619
571
|
});
|
|
620
572
|
}
|
|
621
|
-
|
|
622
573
|
return tree.nodes;
|
|
623
574
|
};
|
|
624
|
-
|
|
625
575
|
exports.parse = parse;
|