【发布时间】:2021-06-09 04:47:34
【问题描述】:
我正在使用 Lucene Syntax (a AND b, c OR d) 作为搜索查询,我需要翻译此搜索查询。为了将 Lucene 语法转换为 JavaScript 对象,我使用了Lucene Parser npm module (https://www.npmjs.com/package/lucene)。
对于 AND 查询,我的翻译需要像这样发生: 查询:凯悦和威斯汀 翻译后的 Lucene 对象:
{
"left":{
"field":"<implicit>",
"fieldLocation":null,
"term":"Hyatt",
"quoted":false,
"regex":false,
"termLocation":{
"start":{
"offset":0,
"line":1,
"column":1
},
"end":{
"offset":6,
"line":1,
"column":7
}
},
"similarity":null,
"boost":null,
"prefix":null
},
"operator":"AND",
"right":{
"field":"<implicit>",
"fieldLocation":null,
"term":"Westin",
"quoted":false,
"regex":false,
"termLocation":{
"start":{
"offset":10,
"line":1,
"column":11
},
"end":{
"offset":16,
"line":1,
"column":17
}
},
"similarity":null,
"boost":null,
"prefix":null
}
}
AND 的翻译搜索查询: 对于 AND 查询,我得到以下翻译:
[
[
{
"col":"*",
"test":"Contains",
"value":"Hyatt"
},
{
"col":"*",
"test":"Contains",
"value":"Westin"
}
]
]
OR 的翻译搜索查询: 对于 OR 查询,我得到以下翻译:
[
[
{
"col":"*",
"test":"Contains",
"value":"Hyatt"
}
],
[
{
"col":"*",
"test":"Contains",
"value":"Westin"
}
]
]
对于这个翻译,我编写了函数(readExpression),它递归遍历输入(翻译的 Lucene 对象):
result = [];
isFieldImplicit(node: Node): boolean {
return node.field === '<implicit>';
}
isFreeTextSearch(searchText: string): boolean {
// user RegExp for pattern matching of field.f1:abc pattern... i.e. alpha-num.alpha-num:any-char
return !this.isContainingLuceneReservedCharacters(searchText);
}
isAndOperator(expression): boolean {
return expression && expression.operator && expression.operator === 'AND';
}
isOrOperator(expression): boolean {
return expression && expression.operator && expression.operator === 'OR';
}
isQueryContainingQuotes(searchText: string): boolean {
const matches = searchText.match(/"/g);
const firstLastCharacterQuoteMatch = new RegExp(
/((?<![\\])['"])((?:.(?!(?<![\\])\1))*.?)\1/
);
const isFirstAndLastCharacterQuote = firstLastCharacterQuoteMatch.test(
searchText
);
if (!matches) {
return false;
}
if (/""/.test(searchText) && isFirstAndLastCharacterQuote) {
return true;
}
return isFirstAndLastCharacterQuote;
}
isQueryEndingWithStar(searchText: string): boolean {
return searchText.endsWith('*');
}
doesQueryContainsStar(searchText: string): boolean {
return searchText.indexOf('*') !== -1;
}
getTestFromQuery(keyword: string): string {
if (this.isQueryContainingQuotes(keyword)) {
return WHERE_CLAUSE_TESTS.CONTAINS;
} else if (this.isQueryEndingWithStar(keyword)) {
return WHERE_CLAUSE_TESTS.CONTAINS_ANY;
} else {
return WHERE_CLAUSE_TESTS.CONTAINS_ANY;
}
}
generateFreeTextClause(node: Node): WhereClause[] {
const searchText = node.quoted ? `"${node.term}"` : node.term;
const clause = [
{
col: '*',
test: this.getTestFromQuery(searchText as string),
value: (node.term as string).replace('*', '')
} as WhereClause
];
return clause;
}
generateFieldSearchClause(node: Node): WhereClause[] {
const searchText = node.quoted ? `"${node.term}"` : node.term;
const clause = [
{
col: node.field.split('.')[1],
test: this.getTestFromQuery(searchText as string),
value: (node.term as string).replace('*', '')
} as WhereClause
];
return clause;
}
readExpression(expression): any {
let left, right;
if (expression && expression.field) {
return expression;
}
if (this.isOrOperator(expression)) {
left = this.readExpression(expression.left);
right = this.readExpression(expression.right);
if (left) {
if (this.isFieldImplicit(left)) {
this.results.push(this.generateFreeTextClause(left));
} else {
this.results.push(this.generateFieldSearchClause(left));
}
}
if (right) {
if (this.isFieldImplicit(right)) {
this.results.push(this.generateFreeTextClause(right));
} else {
this.results.push(this.generateFieldSearchClause(right));
}
}
console.log(this.results);
return this.results;
}
if (this.isAndOperator(expression)) {
left = this.readExpression(expression.left);
right = this.readExpression(expression.right);
if (left) {
if (this.isFieldImplicit(left)) {
this.results.push(this.generateFreeTextClause(left)[0]);
} else {
this.results.push(this.generateFieldSearchClause(left)[0]);
}
}
if (right) {
if (this.isFieldImplicit(right)) {
this.results.push(this.generateFreeTextClause(right)[0]);
} else {
this.results.push(this.generateFieldSearchClause(right)[0]);
}
}
console.log(this.results);
return this.results;
}
}
它适用于 1 级 AND 和 OR,但一旦我有 2 级,它就会失败。 查询:(Hyatt AND Westin)OR Orchid
我期待以下翻译:
[
[
{
"col":"*",
"test":"Contains",
"value":"Hyatt"
},
{
"col":"*",
"test":"Contains",
"value":"Westin"
}
],
[
{
"col":"*",
"test":"Contains",
"value":"Orchid"
}
]
]
在递归调用中,OR 的叶节点被忽略并且没有注入到结果数组中。欢迎提出任何建议。
【问题讨论】:
-
我建议这包含太多和太少。这是一堵巨大的代码墙,要尝试通读,看起来输入格式中的几乎所有内容都不是问题所必需的。另一方面,您向我们展示了它工作的示例输入和输出,但仅输出不工作的情况。您能否创建一个更简洁的minimal reproducible example,包括失败的案例,但跳过大部分不相关的字段?并缩小有问题的代码以仅处理特定条件下的 AND/OR?最后,你的输出真的应该忽略 AND 和 OR 之间的区别吗?
-
这并不是说这是一个坏问题,但如果你简化,你可能会得到更多的帮助。
标签: javascript recursion functional-programming lucene