Notes/src/services/search/parser.js

159 lines
4.5 KiB
JavaScript
Raw Normal View History

2020-05-21 11:46:01 +02:00
"use strict";
2020-05-19 00:00:35 +02:00
const AndExp = require('./expressions/and');
const OrExp = require('./expressions/or');
const NotExp = require('./expressions/not');
const ChildOfExp = require('./expressions/child_of');
const PropertyComparisonExp = require('./expressions/property_comparison');
2020-05-20 00:03:33 +02:00
const AttributeExistsExp = require('./expressions/attribute_exists');
const LabelComparisonExp = require('./expressions/label_comparison');
2020-05-19 00:00:35 +02:00
const NoteCacheFulltextExp = require('./expressions/note_cache_fulltext');
const NoteContentFulltextExp = require('./expressions/note_content_fulltext');
2020-05-20 23:20:39 +02:00
const comparatorBuilder = require('./comparator_builder');
2020-05-19 00:00:35 +02:00
2020-05-21 11:46:01 +02:00
function getFulltext(tokens, parsingContext) {
parsingContext.highlightedTokens.push(...tokens);
2020-05-21 11:18:15 +02:00
2020-05-20 23:20:39 +02:00
if (tokens.length === 0) {
return null;
}
2020-05-21 11:46:01 +02:00
else if (parsingContext.includeNoteContent) {
2020-05-20 23:20:39 +02:00
return new OrExp([
new NoteCacheFulltextExp(tokens),
new NoteContentFulltextExp(tokens)
]);
2020-05-19 00:00:35 +02:00
}
else {
2020-05-20 23:20:39 +02:00
return new NoteCacheFulltextExp(tokens);
2020-05-19 00:00:35 +02:00
}
}
function isOperator(str) {
2020-05-20 23:20:39 +02:00
return str.match(/^[=<>*]+$/);
2020-05-19 00:00:35 +02:00
}
2020-05-21 11:46:01 +02:00
function getExpression(tokens, parsingContext) {
2020-05-20 23:20:39 +02:00
if (tokens.length === 0) {
return null;
}
2020-05-19 00:00:35 +02:00
const expressions = [];
let op = null;
let i;
function parseNoteProperty() {
if (tokens[i] !== '.') {
parsingContext.addError('Expected "." to separate field path');
return;
}
i++;
if (tokens[i] === 'parent') {
i += 1;
return new ChildOfExp(parseNoteProperty());
}
if (tokens[i] === 'title') {
const propertyName = tokens[i];
const operator = tokens[i + 1];
const comparedValue = tokens[i + 2];
const comparator = comparatorBuilder(operator, comparedValue);
if (!comparator) {
parsingContext.addError(`Can't find operator '${operator}'`);
return;
}
i += 3;
return new PropertyComparisonExp(propertyName, comparator);
}
}
for (i = 0; i < tokens.length; i++) {
2020-05-19 00:00:35 +02:00
const token = tokens[i];
if (token === '#' || token === '~') {
2020-05-19 00:00:35 +02:00
continue;
}
if (Array.isArray(token)) {
2020-05-21 11:46:01 +02:00
expressions.push(getExpression(token, parsingContext));
2020-05-19 00:00:35 +02:00
}
else if (token.startsWith('#') || token.startsWith('~')) {
2020-05-19 00:00:35 +02:00
const type = token.startsWith('#') ? 'label' : 'relation';
2020-05-21 11:46:01 +02:00
parsingContext.highlightedTokens.push(token.substr(1));
2020-05-21 11:18:15 +02:00
2020-05-19 00:00:35 +02:00
if (i < tokens.length - 2 && isOperator(tokens[i + 1])) {
2020-05-21 13:45:18 +02:00
let operator = tokens[i + 1];
2020-05-20 23:20:39 +02:00
const comparedValue = tokens[i + 2];
2020-05-21 11:46:01 +02:00
parsingContext.highlightedTokens.push(comparedValue);
2020-05-21 11:18:15 +02:00
2020-05-21 13:45:18 +02:00
if (parsingContext.fuzzyAttributeSearch && operator === '=') {
operator = '*=*';
}
2020-05-20 23:20:39 +02:00
const comparator = comparatorBuilder(operator, comparedValue);
if (!comparator) {
parsingContext.addError(`Can't find operator '${operator}'`);
continue;
2020-05-20 23:20:39 +02:00
}
expressions.push(new LabelComparisonExp(type, token.substr(1), comparator));
2020-05-19 00:00:35 +02:00
i += 2;
}
else {
expressions.push(new AttributeExistsExp(type, token.substr(1), parsingContext.fuzzyAttributeSearch));
2020-05-19 00:00:35 +02:00
}
}
else if (token === 'note') {
i++;
expressions.push(parseNoteProperty(tokens));
continue;
}
else if (['and', 'or'].includes(token)) {
2020-05-19 00:00:35 +02:00
if (!op) {
op = token;
2020-05-19 00:00:35 +02:00
}
else if (op !== token) {
parsingContext.addError('Mixed usage of AND/OR - always use parenthesis to group AND/OR expressions.');
2020-05-19 00:00:35 +02:00
}
}
else if (isOperator(token)) {
parsingContext.addError(`Misplaced or incomplete expression "${token}"`);
2020-05-19 00:00:35 +02:00
}
else {
parsingContext.addError(`Unrecognized expression "${token}"`);
2020-05-19 00:00:35 +02:00
}
if (!op && expressions.length > 1) {
op = 'and';
}
}
2020-05-20 00:03:33 +02:00
2020-05-20 23:20:39 +02:00
if (op === null || op === 'and') {
return AndExp.of(expressions);
}
else if (op === 'or') {
return OrExp.of(expressions);
}
2020-05-19 00:00:35 +02:00
}
2020-05-21 11:46:01 +02:00
function parse({fulltextTokens, expressionTokens, parsingContext}) {
2020-05-19 00:00:35 +02:00
return AndExp.of([
2020-05-21 11:46:01 +02:00
getFulltext(fulltextTokens, parsingContext),
getExpression(expressionTokens, parsingContext)
2020-05-19 00:00:35 +02:00
]);
}
2020-05-20 00:03:33 +02:00
module.exports = parse;