1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-10-30 23:57:43 +02:00

Implementing negatable search query parsing #309

This commit is contained in:
Patrik J. Braun
2021-05-23 17:56:58 +02:00
parent 20f4e8735a
commit d0153e58d9
5 changed files with 66 additions and 30 deletions

View File

@@ -152,6 +152,7 @@ export class BenchmarkRunner {
minResolution: 'min-resolution',
or: 'or',
orientation: 'orientation',
any_text: 'any-text',
person: 'person',
portrait: 'portrait',
position: 'position',

View File

@@ -6,6 +6,7 @@ import {
MaxResolutionSearch,
MinRatingSearch,
MinResolutionSearch,
NegatableSearchQuery,
OrientationSearch,
ORSearchQuery,
RangeSearch,
@@ -36,6 +37,7 @@ export interface QueryKeywords {
and: string;
from: string;
to: string;
any_text: string;
caption: string;
directory: string;
file_name: string;
@@ -172,45 +174,55 @@ export class SearchQueryParser {
return ret;
}
if (str.startsWith(this.keywords.from + ':')) {
const kwStartsWith = (s: string, kw: string): boolean => {
return s.startsWith(kw + ':') || s.startsWith(kw + '!:');
};
if (kwStartsWith(str, this.keywords.from)) {
return {
type: SearchQueryTypes.from_date,
value: SearchQueryParser.parseDate(str.substring((this.keywords.from + ':').length))
value: SearchQueryParser.parseDate(str.substring(str.indexOf(':') + 1)),
...(str.startsWith(this.keywords.from + '!:') && {negate: true}) // only add if the value is true
} as FromDateSearch;
}
if (str.startsWith(this.keywords.to + ':')) {
if (kwStartsWith(str, this.keywords.to)) {
return {
type: SearchQueryTypes.to_date,
value: SearchQueryParser.parseDate(str.substring((this.keywords.to + ':').length))
value: SearchQueryParser.parseDate(str.substring(str.indexOf(':') + 1)),
...(str.startsWith(this.keywords.to + '!:') && {negate: true})// only add if the value is true
} as ToDateSearch;
}
if (str.startsWith(this.keywords.minRating + ':')) {
if (kwStartsWith(str, this.keywords.minRating)) {
return {
type: SearchQueryTypes.min_rating,
value: parseInt(str.slice((this.keywords.minRating + ':').length), 10)
value: parseInt(str.substring(str.indexOf(':') + 1), 10),
...(str.startsWith(this.keywords.minRating + '!:') && {negate: true}) // only add if the value is true
} as MinRatingSearch;
}
if (str.startsWith(this.keywords.maxRating + ':')) {
if (kwStartsWith(str, this.keywords.maxRating)) {
return {
type: SearchQueryTypes.max_rating,
value: parseInt(str.slice((this.keywords.maxRating + ':').length), 10)
value: parseInt(str.substring(str.indexOf(':') + 1), 10),
...(str.startsWith(this.keywords.maxRating + '!:') && {negate: true}) // only add if the value is true
} as MaxRatingSearch;
}
if (str.startsWith(this.keywords.minResolution + ':')) {
if (kwStartsWith(str, this.keywords.minResolution)) {
return {
type: SearchQueryTypes.min_resolution,
value: parseInt(str.slice((this.keywords.minResolution + ':').length), 10)
value: parseInt(str.substring(str.indexOf(':') + 1), 10),
...(str.startsWith(this.keywords.minResolution + '!:') && {negate: true}) // only add if the value is true
} as MinResolutionSearch;
}
if (str.startsWith(this.keywords.maxResolution + ':')) {
if (kwStartsWith(str, this.keywords.maxResolution)) {
return {
type: SearchQueryTypes.max_resolution,
value: parseInt(str.slice((this.keywords.maxResolution + ':').length), 10)
value: parseInt(str.substring(str.indexOf(':') + 1), 10),
...(str.startsWith(this.keywords.maxResolution + '!:') && {negate: true}) // only add if the value is true
} as MaxResolutionSearch;
}
if (new RegExp('^\\d*-' + this.keywords.kmFrom + ':').test(str)) {
let from = str.slice(new RegExp('^\\d*-' + this.keywords.kmFrom + ':').exec(str)[0].length);
if (new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').test(str)) {
let from = str.slice(new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length);
if ((from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') ||
(from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')) {
from = from.slice(1, from.length - 1);
@@ -218,7 +230,8 @@ export class SearchQueryParser {
return {
type: SearchQueryTypes.distance,
distance: parseInt(new RegExp(/^\d*/).exec(str)[0], 10),
from: {text: from}
from: {text: from},
...(new RegExp('^\\d*-' + this.keywords.kmFrom + '!:').test(str) && {negate: true}) // only add if the value is true
} as DistanceSearch;
}
@@ -233,7 +246,10 @@ export class SearchQueryParser {
const tmp = TextSearchQueryTypes.map(type => ({
key: (this.keywords as any)[SearchQueryTypes[type]] + ':',
queryTemplate: {type, text: ''} as TextSearch
}));
})).concat(TextSearchQueryTypes.map(type => ({
key: (this.keywords as any)[SearchQueryTypes[type]] + '!:',
queryTemplate: {type, text: '', negate: true} as TextSearch
})));
for (const typeTmp of tmp) {
if (str.startsWith(typeTmp.key)) {
const ret: TextSearch = Utils.clone(typeTmp.queryTemplate);
@@ -266,6 +282,7 @@ export class SearchQueryParser {
if (!query || !query.type) {
return '';
}
const colon = (query as NegatableSearchQuery).negate === true ? '!:' : ':';
switch (query.type) {
case SearchQueryTypes.AND:
return '(' + (query as SearchListQuery).list.map(q => this.stringifyOnEntry(q)).join(' ' + this.keywords.and + ' ') + ')';
@@ -290,30 +307,35 @@ export class SearchQueryParser {
if (!(query as FromDateSearch).value) {
return '';
}
return this.keywords.from + ':' +
return this.keywords.from + colon +
SearchQueryParser.stringifyDate((query as FromDateSearch).value);
case SearchQueryTypes.to_date:
if (!(query as ToDateSearch).value) {
return '';
}
return this.keywords.to + ':' +
return this.keywords.to + colon +
SearchQueryParser.stringifyDate((query as ToDateSearch).value);
case SearchQueryTypes.min_rating:
return this.keywords.minRating + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
return this.keywords.minRating + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.max_rating:
return this.keywords.maxRating + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
return this.keywords.maxRating + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.min_resolution:
return this.keywords.minResolution + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
return this.keywords.minResolution + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.max_resolution:
return this.keywords.maxResolution + ':' + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
return this.keywords.maxResolution + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value);
case SearchQueryTypes.distance:
if ((query as DistanceSearch).from.text.indexOf(' ') !== -1) {
return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + ':(' + (query as DistanceSearch).from.text + ')';
return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + colon + '(' + (query as DistanceSearch).from.text + ')';
}
return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + ':' + (query as DistanceSearch).from.text;
return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + colon + (query as DistanceSearch).from.text;
case SearchQueryTypes.any_text:
return SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType);
if (!(query as TextSearch).negate) {
return SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType);
} else {
return (this.keywords as any)[SearchQueryTypes[query.type]] + colon +
SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType);
}
case SearchQueryTypes.person:
case SearchQueryTypes.position:
@@ -324,7 +346,7 @@ export class SearchQueryParser {
if (!(query as TextSearch).text) {
return '';
}
return (this.keywords as any)[SearchQueryTypes[query.type]] + ':' +
return (this.keywords as any)[SearchQueryTypes[query.type]] + colon +
SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType);
default:

View File

@@ -176,10 +176,6 @@ export interface RangeSearch extends NegatableSearchQuery {
value: number;
}
export interface RangeSearchGroup extends ANDSearchQuery {
list: RangeSearch[];
}
export interface FromDateSearch extends RangeSearch {
type: SearchQueryTypes.from_date;
value: number;

View File

@@ -20,6 +20,7 @@ export class SearchQueryParserService {
minResolution: 'min-resolution',
or: 'or',
orientation: 'orientation',
any_text: 'any-text',
person: 'person',
portrait: 'portrait',
position: 'position',

View File

@@ -34,6 +34,7 @@ const queryKeywords: QueryKeywords = {
minResolution: 'min-resolution',
or: 'or',
orientation: 'orientation',
any_text: 'any-text',
person: 'person',
portrait: 'portrait',
position: 'position',
@@ -64,6 +65,14 @@ describe('SearchQueryParser', () => {
matchType: TextSearchQueryMatchTypes.exact_match,
text: 'New York'
} as TextSearch);
check({
type: SearchQueryTypes.position,
matchType: TextSearchQueryMatchTypes.exact_match,
negate: true,
text: 'New York'
} as TextSearch);
check({type: SearchQueryTypes.any_text, text: 'test', negate: true} as TextSearch);
});
it('Date search', () => {
@@ -71,6 +80,8 @@ describe('SearchQueryParser', () => {
check({type: SearchQueryTypes.from_date, value: (new Date(2020, 1, 1)).getTime()} as FromDateSearch);
check({type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 20)).getTime()} as ToDateSearch);
check({type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 1)).getTime()} as ToDateSearch);
check({type: SearchQueryTypes.from_date, value: (new Date(2020, 1, 1)).getTime(), negate: true} as FromDateSearch);
check({type: SearchQueryTypes.to_date, value: (new Date(2020, 1, 1)).getTime(), negate: true} as ToDateSearch);
const parser = new SearchQueryParser(queryKeywords);
// test if date gets simplified on 1st of Jan.
@@ -86,13 +97,18 @@ describe('SearchQueryParser', () => {
it('Rating search', () => {
check({type: SearchQueryTypes.min_rating, value: 10} as MinRatingSearch);
check({type: SearchQueryTypes.max_rating, value: 1} as MaxRatingSearch);
check({type: SearchQueryTypes.min_rating, value: 10, negate: true} as MinRatingSearch);
check({type: SearchQueryTypes.max_rating, value: 1, negate: true} as MaxRatingSearch);
});
it('Resolution search', () => {
check({type: SearchQueryTypes.min_resolution, value: 10} as MinResolutionSearch);
check({type: SearchQueryTypes.max_resolution, value: 5} as MaxResolutionSearch);
check({type: SearchQueryTypes.min_resolution, value: 10, negate: true} as MinResolutionSearch);
check({type: SearchQueryTypes.max_resolution, value: 5, negate: true} as MaxResolutionSearch);
});
it('Distance search', () => {
check({type: SearchQueryTypes.distance, distance: 10, from: {text: 'New York'}} as DistanceSearch);
check({type: SearchQueryTypes.distance, distance: 10, from: {text: 'New York'}, negate: true} as DistanceSearch);
});
it('OrientationSearch search', () => {
check({type: SearchQueryTypes.orientation, landscape: true} as OrientationSearch);