mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-12-05 00:57:59 +01:00
3590be1429
* match_query: unit test and rewrite for TypeScript * match_query: use new type for parse errors * match_query: avoid exceptional control flow in date parsing
90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
import { MatcherFactory } from './matcher';
|
|
|
|
import { numberFields, dateFields, literalFields, termSpaceToImageField, defaultField } from './fields';
|
|
import { FieldName, FieldMatcher, RangeEqualQualifier, TermType, AstMatcher } from './types';
|
|
|
|
type RangeInfo = [FieldName, RangeEqualQualifier, TermType];
|
|
|
|
function normalizeTerm(term: string, wildcardable: boolean) {
|
|
if (!wildcardable) {
|
|
return term.replace('\\"', '"');
|
|
}
|
|
return term.replace(/\\([^*?])/g, '$1');
|
|
}
|
|
|
|
function parseRangeField(field: string): RangeInfo | null {
|
|
if (numberFields.indexOf(field) !== -1) {
|
|
return [field, 'eq', 'number'];
|
|
}
|
|
|
|
if (dateFields.indexOf(field) !== -1) {
|
|
return [field, 'eq', 'date'];
|
|
}
|
|
|
|
const qual = /^(\w+)\.([lg]te?|eq)$/.exec(field);
|
|
|
|
if (qual) {
|
|
const fieldName: FieldName = qual[1];
|
|
const rangeQual = qual[2] as RangeEqualQualifier;
|
|
|
|
if (numberFields.indexOf(fieldName) !== -1) {
|
|
return [fieldName, rangeQual, 'number'];
|
|
}
|
|
|
|
if (dateFields.indexOf(fieldName) !== -1) {
|
|
return [fieldName, rangeQual, 'date'];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function makeTermMatcher(term: string, fuzz: number, factory: MatcherFactory): [FieldName, FieldMatcher] {
|
|
let rangeParsing, candidateTermSpace, termCandidate;
|
|
let localTerm = term;
|
|
const wildcardable = fuzz === 0 && !/^"([^"]|\\")+"$/.test(localTerm);
|
|
|
|
if (!wildcardable && !fuzz) {
|
|
// Remove quotes around quoted literal term
|
|
localTerm = localTerm.substring(1, localTerm.length - 1);
|
|
}
|
|
|
|
localTerm = normalizeTerm(localTerm, wildcardable);
|
|
|
|
// N.B.: For the purposes of this parser, boosting effects are ignored.
|
|
const matchArr = localTerm.split(':');
|
|
|
|
if (matchArr.length > 1) {
|
|
candidateTermSpace = matchArr[0];
|
|
termCandidate = matchArr.slice(1).join(':');
|
|
rangeParsing = parseRangeField(candidateTermSpace);
|
|
|
|
if (rangeParsing) {
|
|
const [fieldName, rangeType, fieldType] = rangeParsing;
|
|
|
|
if (fieldType === 'date') {
|
|
return [fieldName, factory.makeDateMatcher(termCandidate, rangeType)];
|
|
}
|
|
|
|
return [fieldName, factory.makeNumberMatcher(parseFloat(termCandidate), fuzz, rangeType)];
|
|
}
|
|
else if (literalFields.indexOf(candidateTermSpace) !== -1) {
|
|
return [candidateTermSpace, factory.makeLiteralMatcher(termCandidate, fuzz, wildcardable)];
|
|
}
|
|
else if (candidateTermSpace === 'my') {
|
|
return [candidateTermSpace, factory.makeUserMatcher(termCandidate)];
|
|
}
|
|
}
|
|
|
|
return [defaultField, factory.makeLiteralMatcher(localTerm, fuzz, wildcardable)];
|
|
}
|
|
|
|
export function getAstMatcherForTerm(term: string, fuzz: number, factory: MatcherFactory): AstMatcher {
|
|
const [fieldName, matcher] = makeTermMatcher(term, fuzz, factory);
|
|
|
|
return (e: HTMLElement) => {
|
|
const value = e.getAttribute(termSpaceToImageField[fieldName]) || '';
|
|
const documentId = parseInt(e.getAttribute(termSpaceToImageField.id) || '0', 10);
|
|
return matcher(value, fieldName, documentId);
|
|
};
|
|
}
|