-
Notifications
You must be signed in to change notification settings - Fork 0
/
filter.ts
123 lines (106 loc) · 4.57 KB
/
filter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
interface FilterToken {
raw: string,
type?: 'objectIndex' | 'arrayIndex' | 'slice' | 'arrayIterate' | 'pipe',
optional?: boolean,
selector?: string,
arrayIndex?: number,
start?: number,
end?: number
}
const tokenRegex = /\.(\[([\s\S]*?)\]|[a-zA-Z]*)?(\?)?|\|/g
function tokenizeFilter(filter: string): FilterToken[]{
/*
. {raw: '.', selector: '*', type: 'objectIndex'}
.foo {raw: '.foo', selector: 'foo', type: 'objectIndex'}
.foo? {raw: '.foo?', selector: 'foo', optional: true, type: 'objectIndex'}
.["foo"] {raw: '.["foo"]', selector: 'foo', type: 'objectIndex'}
.[2] {raw: '.[2]', arrayIndex: 2, type: 'arrayIndex'}
.[10:15] {raw: '.[10:15]', start: 10, end: 15, type: 'slice'},
.[] {raw: '.[]', type: 'arrayIterate'}
.[]? {raw: '.[]?', type: 'arrayIterate', optional: true}
| {raw: '|', type: 'pipe'}
*/
var matches = filter.match(tokenRegex)
if(matches)
return matches.map((match: string): FilterToken => {
if(match == "|") return {raw: match, type: 'pipe'}
let token:FilterToken = {raw: match};
if(match[1] == "[") //Capture between
{
var betweenArr = match.match(/\[([\s\S]*?)\]/)
if(!betweenArr) throw new Error("Error while parsing filter")
const between = betweenArr[1]
if(between[0] == "\""){
token.type = 'objectIndex'
token.selector = between.slice(1,-1)
if(match.endsWith("?")) token.optional = true
}
else if(between == ""){
token.type = 'arrayIterate'
if(match.endsWith("?")) token.optional = true
}
else if(between.includes(":")){
var startFinish = between.split(":")
token.type = "slice"
token.start = parseInt(startFinish[0])
token.end = parseInt(startFinish[1])
}
else {
token.type = 'arrayIndex'
token.arrayIndex = parseInt(between)
}
return token
}else { //Its direct
token.type = 'objectIndex'
var selectorMatch = match.match(/[a-zA-Z]+/)
token.selector = selectorMatch ? selectorMatch[0] : "*"
if((selectorMatch != null) && match.endsWith("?")) token.optional = true
}
return token
})
return []
}
interface FilterResult {
newContext: any,
iterateContext?: boolean
}
function runFilter(context: any, filter: FilterToken): FilterResult{
//'slice' | 'arrayIterate'
if(filter.type == 'pipe' || (filter.type == 'objectIndex' && filter.selector == "*")) return {newContext: context}
if(filter.type == 'objectIndex'){
if(!filter.selector) throw new Error("No selector was provided")
var newContext = context[filter.selector]
if(newContext) return {newContext}
if(filter.optional) return {newContext: null}
throw new Error("Object does not exist on current context")
}
if(filter.type == 'arrayIndex'){
if(!filter.arrayIndex) throw new Error("No array index was provided")
var newContext = context[filter.arrayIndex]
if(newContext) return {newContext}
if(filter.optional) return {newContext: null}
throw new Error("Object does not exist on current context")
}
if(filter.type == 'arrayIterate'){
return {newContext: context, iterateContext: true}
}
if(filter.type == 'slice'){
return {newContext: (context as Array<any> | string).slice(filter.start, filter.end)}
}
return {newContext: null}
}
function evaluateFilters(context: any, filters: FilterToken[]): any{
if(filters.length == 0) return context
var filterz = filters.slice()
var currentFilter: FilterToken = filterz.shift() as FilterToken
var result = runFilter(context, currentFilter)
if(result.iterateContext)
return result.newContext.map((ctx:any) => {
evaluateFilters(ctx, filterz)
})
return evaluateFilters(result.newContext, filterz)
}
export function filter(object: any, filter: string): any{
var filters = tokenizeFilter(filter)
return evaluateFilters(object, filters)
}