2026-05-15 08:33:17 +02:00
|
|
|
import type { FilterCondition } from '../types'
|
|
|
|
|
|
2026-05-17 16:34:09 +02:00
|
|
|
type ConditionText = string
|
|
|
|
|
type PropertyValue = string
|
|
|
|
|
|
|
|
|
|
function toStringValue(value: unknown): ConditionText {
|
2026-05-15 08:33:17 +02:00
|
|
|
if (value == null) return ''
|
|
|
|
|
if (typeof value === 'string') return value
|
|
|
|
|
return String(value)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 16:34:09 +02:00
|
|
|
function conditionList(value: unknown): ConditionText[] | null {
|
2026-05-15 08:33:17 +02:00
|
|
|
return Array.isArray(value) ? value.map(toStringValue) : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function textMatchResult(op: FilterCondition['op'], matched: boolean): boolean {
|
|
|
|
|
if (op === 'contains' || op === 'equals') return matched
|
|
|
|
|
if (op === 'not_contains' || op === 'not_equals') return !matched
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class PropertyArrayField {
|
2026-05-17 16:34:09 +02:00
|
|
|
private readonly values: PropertyValue[]
|
|
|
|
|
private readonly normalizedValues: Set<PropertyValue>
|
2026-05-15 08:33:17 +02:00
|
|
|
|
2026-05-17 16:34:09 +02:00
|
|
|
constructor(values: PropertyValue[]) {
|
|
|
|
|
this.values = values
|
2026-05-15 08:33:17 +02:00
|
|
|
this.normalizedValues = new Set(values.map((value) => value.toLowerCase()))
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 16:34:09 +02:00
|
|
|
contains(target: ConditionText): boolean {
|
2026-05-15 08:33:17 +02:00
|
|
|
return this.normalizedValues.has(target.toLowerCase())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 16:34:09 +02:00
|
|
|
equals(target: ConditionText): boolean {
|
2026-05-15 08:33:17 +02:00
|
|
|
return this.values.length === 1 && this.contains(target)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-17 16:34:09 +02:00
|
|
|
matchesAny(targets: ConditionText[] | null): boolean {
|
2026-05-15 08:33:17 +02:00
|
|
|
return targets?.some((target) => this.contains(target)) ?? false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matchesRegex(regex: RegExp): boolean {
|
|
|
|
|
return this.values.some((value) => regex.test(value))
|
|
|
|
|
}
|
2026-05-17 16:34:09 +02:00
|
|
|
|
|
|
|
|
isEmpty(): boolean {
|
|
|
|
|
return this.values.length === 0
|
|
|
|
|
}
|
2026-05-15 08:33:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function evaluatePropertyArrayCondition(
|
|
|
|
|
cond: FilterCondition,
|
2026-05-17 16:34:09 +02:00
|
|
|
values: PropertyValue[],
|
|
|
|
|
condVal: ConditionText,
|
2026-05-15 08:33:17 +02:00
|
|
|
regex: RegExp | null,
|
|
|
|
|
): boolean {
|
|
|
|
|
const field = new PropertyArrayField(values)
|
|
|
|
|
if (regex) return textMatchResult(cond.op, field.matchesRegex(regex))
|
2026-05-26 10:11:53 +02:00
|
|
|
const contains = field.contains(condVal)
|
|
|
|
|
const equals = field.equals(condVal)
|
|
|
|
|
const matchesAny = field.matchesAny(conditionList(cond.value))
|
|
|
|
|
const isEmpty = field.isEmpty()
|
|
|
|
|
return new Map<FilterCondition['op'], boolean>([
|
|
|
|
|
['contains', contains],
|
|
|
|
|
['not_contains', !contains],
|
|
|
|
|
['equals', equals],
|
|
|
|
|
['not_equals', !equals],
|
|
|
|
|
['any_of', matchesAny],
|
|
|
|
|
['none_of', !matchesAny],
|
|
|
|
|
['is_empty', isEmpty],
|
|
|
|
|
['is_not_empty', !isEmpty],
|
|
|
|
|
]).get(cond.op) ?? false
|
2026-05-15 08:33:17 +02:00
|
|
|
}
|