From bb0cf67527ec755f15062feffc647432506947fe Mon Sep 17 00:00:00 2001 From: Ross Jekel <4460474+jekelsc@users.noreply.github.com> Date: Fri, 21 Jun 2019 12:44:32 -0700 Subject: [PATCH 1/6] Closes #26 - Enable Filtering on async tables Enables default ag-grid style filtering on async tables by creating a filter function from filterModel data. --- src/TableView.jl | 139 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/src/TableView.jl b/src/TableView.jl index 88028c8..e5edfd2 100644 --- a/src/TableView.jl +++ b/src/TableView.jl @@ -124,8 +124,9 @@ function showtable(table; dark = false, height = :auto, width = "100%", cell_cha sortable = !async, resizable = true, type = types[i] <: Union{Missing, T where T <: Number} ? "numericColumn" : nothing, - filter = async ? false : types[i] <: Union{Missing, T where T <: Dates.Date} ? "agDateColumnFilter" : - types[i] <: Union{Missing, T where T <: Number} ? "agNumberColumnFilter" : true + filter = types[i] <: Union{Missing, T where T <: Dates.Date} ? "agDateColumnFilter" : + types[i] <: Union{Missing, T where T <: Number} ? "agNumberColumnFilter" : true, + filterParams = async ? Dict("applyButton" => true, "clearButton" => true) : nothing ) for (i, n) in enumerate(names)] id = string("grid-", string(uuid1())[1:8]) @@ -158,6 +159,129 @@ function _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id, onimport(w, handler) end +const _mapnumberop = Dict{String, String}( + "equals" => "==", + "notEqual" => "!=", + "lessThan" => "<", + "lessThanOrEqual" => "<=", + "greaterThan" => ">", + "greaterThanOrEqual" => ">=", + ) + +const _mapdateop = Dict{String, String}( + "equals" => "==", + "greaterThan" => ">", + "lessThan" => "<", + "notEqual" => "!=", +) + +const _dateformat = DateFormat("y-m-d") + +function _regex_escape(s::AbstractString) + res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:])" => s"\\\1") + replace(res, "\0" => "\\0") +end + +function _build_expressions(filtermodel) + # Return an array of column expression strings + + function build_number(column, filter) + optype = filter["type"] + filtervalue = filter["filter"] + expression = "true" + + if optype == "inRange" + expression = """($filtervalue <= getproperty(row, Symbol("$column")) <= $(filter["filterTo"]))""" + else + expression = """(getproperty(row, Symbol("$column")) $(_mapnumberop[optype]) $filtervalue)""" + end + + return expression + end + + function build_text(column, filter) + optype = filter["type"] + + # Unfortunately ag-grid's default text filter converts the user's input + # to lowercase. Using regex with ignore case option rather normalizing + # case on the field value. Thus we need to escape the user's input + filtervalue = _regex_escape(filter["filter"]) + + expression = "true" + + if optype == "equals" + expression = "occursin(r\"\"\"^$filtervalue\$\"\"\"i, getproperty(row, Symbol(\"$column\")))" + elseif optype == "notEqual" + expression = "!occursin(r\"\"\"^$filtervalue\$\"\"\"i, getproperty(row, Symbol(\"$column\")))" + elseif optype == "startsWith" + expression = "occursin(r\"\"\"^$filtervalue\"\"\"i, getproperty(row, Symbol(\"$column\")))" + elseif optype == "endsWith" + expression = "occursin(r\"\"\"$filtervalue\$\"\"\"i, getproperty(row, Symbol(\"$column\")))" + elseif optype == "contains" + expression = "occursin(r\"\"\"$filtervalue\"\"\"i, getproperty(row, Symbol(\"$column\")))" + elseif optype == "notContains" + expression = "!occursin(r\"\"\"$filtervalue\"\"\"i, getproperty(row, Symbol(\"$column\")))" + end + + return expression + end + + function build_date(column, filter) + optype = filter["type"] + filtervalue = "Date(\"$(filter["dateFrom"])\", _dateformat)" + expression = "true" + + if optype == "inRange" + filterto = "Date(\"$(filter["dateTo"])\", _dateformat)" + expression = """($filtervalue <= getproperty(row, Symbol("$column")) <= $filterto)""" + else + expression = """(getproperty(row, Symbol("$column")) $(_mapdateop[optype]) $filtervalue)""" + end + + return expression + end + + function build_filter(column, filter) + filtertype = filter["filterType"] + expression = "true" + + if filtertype == "number" + expression = build_number(column, filter) + elseif filtertype == "text" + expression = build_text(column, filter) + elseif filtertype == "date" + expression = build_date(column, filter) + end + + return expression + end + + function build_boolean(column, conditions) + return "(" * + build_filter(column, conditions["condition1"]) * + (conditions["operator"] == "OR" ? "||" : "&&") * + build_filter(column, conditions["condition2"]) * + ")" + end + + return [ + (haskey(value, "filterType") ? build_filter(key, value) : build_boolean(key, value)) + for (key, value) in filtermodel + ] +end + +const _filterfns = Dict{String, Any}() + +function _filterfn(filtermodel) + code = "(row) -> begin $(join(_build_expressions(filtermodel), " && ")) end" + if haskey(_filterfns, code) + return _filterfns[code] + end + + println("For filterModel: $filtermodel") + println("Built code: $code") + return _filterfns[code] = eval(Meta.parse(code)) +end function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id, onCellValueChanged) rowparams = Observable(w, "rowparams", Dict("startRow" => 1, @@ -165,7 +289,16 @@ function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id "successCallback" => @js v -> nothing)) requestedrows = Observable(w, "requestedrows", JSONText("{}")) on(rowparams) do x - requestedrows[] = JSONText(table2json(rows, names, types, requested = [x["startRow"], x["endRow"]])) + filtermodel = x["filterModel"] + if length(filtermodel) > 0 + fltr = _filterfn(filtermodel) + data = Base.Iterators.filter(rows) do row + Base.invokelatest(fltr, row) + end + else + data = rows + end + requestedrows[] = JSONText(table2json(data, names, types, requested = [x["startRow"], x["endRow"]])) end onjs(requestedrows, @js function (val) From 11b011f58606179248c91a938198cd6c2a6067cf Mon Sep 17 00:00:00 2001 From: Ross Jekel <4460474+jekelsc@users.noreply.github.com> Date: Fri, 21 Jun 2019 15:13:08 -0700 Subject: [PATCH 2/6] Closes #26 - Only wrap with invokelatest on the first use Minor adjustment to return the invokelatest wrapped filter on the first use. The world will be ready for the actual function on subsequent uses. --- src/TableView.jl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/TableView.jl b/src/TableView.jl index e5edfd2..425fdb5 100644 --- a/src/TableView.jl +++ b/src/TableView.jl @@ -278,9 +278,9 @@ function _filterfn(filtermodel) return _filterfns[code] end - println("For filterModel: $filtermodel") - println("Built code: $code") - return _filterfns[code] = eval(Meta.parse(code)) + fltr = _filterfns[code] = eval(Meta.parse(code)) + # On the first call, we need to wrap the function to invokelatest + return (row) -> Base.invokelatest(fltr, row) end function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id, onCellValueChanged) @@ -291,10 +291,7 @@ function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id on(rowparams) do x filtermodel = x["filterModel"] if length(filtermodel) > 0 - fltr = _filterfn(filtermodel) - data = Base.Iterators.filter(rows) do row - Base.invokelatest(fltr, row) - end + data = Base.Iterators.filter(_filterfn(filtermodel), rows) else data = rows end From 00b2e1f268900ed4ed3e7251442faa85d8d951a4 Mon Sep 17 00:00:00 2001 From: Ross Jekel <4460474+jekelsc@users.noreply.github.com> Date: Fri, 21 Jun 2019 17:32:18 -0700 Subject: [PATCH 3/6] Closes #26 - Cleanup and partial range handling Added code to sanitize column access and partially specified ranges. Optimize dates via preparsing. --- src/TableView.jl | 59 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/TableView.jl b/src/TableView.jl index 425fdb5..01cf9c4 100644 --- a/src/TableView.jl +++ b/src/TableView.jl @@ -190,10 +190,16 @@ function _build_expressions(filtermodel) filtervalue = filter["filter"] expression = "true" - if optype == "inRange" - expression = """($filtervalue <= getproperty(row, Symbol("$column")) <= $(filter["filterTo"]))""" - else - expression = """(getproperty(row, Symbol("$column")) $(_mapnumberop[optype]) $filtervalue)""" + if filtervalue !== nothing + if optype == "inRange" + filterto = filter["filterTo"] + # Only create a range expression if it is fully specified + if filterto !== nothing + expression = """($filtervalue <= $column <= $filterto)""" + end + else + expression = """($column $(_mapnumberop[optype]) $filtervalue)""" + end end return expression @@ -210,17 +216,17 @@ function _build_expressions(filtermodel) expression = "true" if optype == "equals" - expression = "occursin(r\"\"\"^$filtervalue\$\"\"\"i, getproperty(row, Symbol(\"$column\")))" + expression = "occursin(r\"\"\"^$filtervalue\$\"\"\"i, $column)" elseif optype == "notEqual" - expression = "!occursin(r\"\"\"^$filtervalue\$\"\"\"i, getproperty(row, Symbol(\"$column\")))" + expression = "!occursin(r\"\"\"^$filtervalue\$\"\"\"i, $column)" elseif optype == "startsWith" - expression = "occursin(r\"\"\"^$filtervalue\"\"\"i, getproperty(row, Symbol(\"$column\")))" + expression = "occursin(r\"\"\"^$filtervalue\"\"\"i, $column)" elseif optype == "endsWith" - expression = "occursin(r\"\"\"$filtervalue\$\"\"\"i, getproperty(row, Symbol(\"$column\")))" + expression = "occursin(r\"\"\"$filtervalue\$\"\"\"i, $column)" elseif optype == "contains" - expression = "occursin(r\"\"\"$filtervalue\"\"\"i, getproperty(row, Symbol(\"$column\")))" + expression = "occursin(r\"\"\"$filtervalue\"\"\"i, $column)" elseif optype == "notContains" - expression = "!occursin(r\"\"\"$filtervalue\"\"\"i, getproperty(row, Symbol(\"$column\")))" + expression = "!occursin(r\"\"\"$filtervalue\"\"\"i, $column)" end return expression @@ -228,14 +234,22 @@ function _build_expressions(filtermodel) function build_date(column, filter) optype = filter["type"] - filtervalue = "Date(\"$(filter["dateFrom"])\", _dateformat)" + filtervalue = filter["dateFrom"] expression = "true" - if optype == "inRange" - filterto = "Date(\"$(filter["dateTo"])\", _dateformat)" - expression = """($filtervalue <= getproperty(row, Symbol("$column")) <= $filterto)""" - else - expression = """(getproperty(row, Symbol("$column")) $(_mapdateop[optype]) $filtervalue)""" + if filtervalue !== nothing + filtervalue = "Date$(Dates.yearmonthday(Date(filtervalue, _dateformat)))" + + if optype == "inRange" + filterto = filter["dateTo"] + # Only create a range expression if it is fully specified + if filterto !== nothing + filterto = "Date$(Dates.yearmonthday(Date(filterto, _dateformat)))" + expression = """($filtervalue <= $column <= $filterto)""" + end + else + expression = """($column $(_mapdateop[optype]) $filtervalue)""" + end end return expression @@ -259,14 +273,21 @@ function _build_expressions(filtermodel) function build_boolean(column, conditions) return "(" * build_filter(column, conditions["condition1"]) * - (conditions["operator"] == "OR" ? "||" : "&&") * + (conditions["operator"] == "OR" ? " || " : " && ") * build_filter(column, conditions["condition2"]) * ")" end + function column_access(column) + # Sanitize the column access + return "getproperty(row, Symbol(raw\"\"\"$column\"\"\"))" + end + return [ - (haskey(value, "filterType") ? build_filter(key, value) : build_boolean(key, value)) - for (key, value) in filtermodel + (haskey(cond, "filterType") ? + build_filter(column_access(col), cond) : + build_boolean(column_access(col), cond)) + for (col, cond) in filtermodel ] end From 029ff94a261cba7e348a684f362d9e898e23f352 Mon Sep 17 00:00:00 2001 From: Ross Jekel <4460474+jekelsc@users.noreply.github.com> Date: Sat, 22 Jun 2019 22:22:26 -0700 Subject: [PATCH 4/6] Closes #26 - Better handling of embedded quotes Handle column names and search text with quote characters more defensively. --- src/TableView.jl | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/TableView.jl b/src/TableView.jl index 01cf9c4..09207a8 100644 --- a/src/TableView.jl +++ b/src/TableView.jl @@ -178,7 +178,7 @@ const _mapdateop = Dict{String, String}( const _dateformat = DateFormat("y-m-d") function _regex_escape(s::AbstractString) - res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:])" => s"\\\1") + res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:\"])" => s"\\\1") replace(res, "\0" => "\\0") end @@ -195,10 +195,10 @@ function _build_expressions(filtermodel) filterto = filter["filterTo"] # Only create a range expression if it is fully specified if filterto !== nothing - expression = """($filtervalue <= $column <= $filterto)""" + expression = "($filtervalue <= $column <= $filterto)" end else - expression = """($column $(_mapnumberop[optype]) $filtervalue)""" + expression = "($column $(_mapnumberop[optype]) $filtervalue)" end end @@ -206,13 +206,12 @@ function _build_expressions(filtermodel) end function build_text(column, filter) - optype = filter["type"] - # Unfortunately ag-grid's default text filter converts the user's input # to lowercase. Using regex with ignore case option rather normalizing # case on the field value. Thus we need to escape the user's input filtervalue = _regex_escape(filter["filter"]) + optype = filter["type"] expression = "true" if optype == "equals" @@ -279,16 +278,20 @@ function _build_expressions(filtermodel) end function column_access(column) - # Sanitize the column access - return "getproperty(row, Symbol(raw\"\"\"$column\"\"\"))" + # Sanitize the column access. Even though column names + # are unlikely to inject code, raw strings do not interpolate. + # When using raw string, quote backslashes preceeding quotes first, + # then escape the quotes. This then roundtrips through + # parse/eval. + quoted = replace(replace(column, "\\\"" => "\\\\\""), "\"" => "\\\"") + return "getproperty(row, Symbol(raw\"\"\"$quoted\"\"\"))" end return [ - (haskey(cond, "filterType") ? + haskey(cond, "filterType") ? build_filter(column_access(col), cond) : - build_boolean(column_access(col), cond)) - for (col, cond) in filtermodel - ] + build_boolean(column_access(col), cond) + for (col, cond) in filtermodel] end const _filterfns = Dict{String, Any}() @@ -311,11 +314,8 @@ function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id requestedrows = Observable(w, "requestedrows", JSONText("{}")) on(rowparams) do x filtermodel = x["filterModel"] - if length(filtermodel) > 0 - data = Base.Iterators.filter(_filterfn(filtermodel), rows) - else - data = rows - end + data = length(filtermodel) > 0 ? + Base.Iterators.filter(_filterfn(filtermodel), rows) : rows requestedrows[] = JSONText(table2json(data, names, types, requested = [x["startRow"], x["endRow"]])) end From 68c54f15cde9e89400d8ed5611d2b36a0aab4faf Mon Sep 17 00:00:00 2001 From: Ross Jekel <4460474+jekelsc@users.noreply.github.com> Date: Sun, 23 Jun 2019 17:54:52 -0700 Subject: [PATCH 5/6] Closes #26 - Build logic using Expr Updated to generate expressions directly rather than parsing a string. --- src/TableView.jl | 146 ++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/src/TableView.jl b/src/TableView.jl index 09207a8..ae2c430 100644 --- a/src/TableView.jl +++ b/src/TableView.jl @@ -159,105 +159,106 @@ function _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id, onimport(w, handler) end -const _mapnumberop = Dict{String, String}( - "equals" => "==", - "notEqual" => "!=", - "lessThan" => "<", - "lessThanOrEqual" => "<=", - "greaterThan" => ">", - "greaterThanOrEqual" => ">=", - ) - -const _mapdateop = Dict{String, String}( - "equals" => "==", - "greaterThan" => ">", - "lessThan" => "<", - "notEqual" => "!=", -) - -const _dateformat = DateFormat("y-m-d") - -function _regex_escape(s::AbstractString) - res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:\"])" => s"\\\1") - replace(res, "\0" => "\\0") -end +const OptionalExpr = Union{Missing, Expr} function _build_expressions(filtermodel) - # Return an array of column expression strings + # Returns an iterator of Expr + + mapop = Dict{String, Symbol}( + "equals" => :(==), + "notEqual" => :(!=), + "lessThan" => :(<), + "lessThanOrEqual" => :(<=), + "greaterThan" => :(>), + "greaterThanOrEqual" => :(>=), + ) - function build_number(column, filter) + function build_number(column::Expr, filter)::OptionalExpr + expression::OptionalExpr = missing optype = filter["type"] filtervalue = filter["filter"] - expression = "true" if filtervalue !== nothing if optype == "inRange" filterto = filter["filterTo"] # Only create a range expression if it is fully specified if filterto !== nothing - expression = "($filtervalue <= $column <= $filterto)" + expression = :( ($filtervalue <= $column <= $filterto) ) end else - expression = "($column $(_mapnumberop[optype]) $filtervalue)" + expression = Expr(:call, mapop[optype], + column, filtervalue) end end return expression end - function build_text(column, filter) + function build_text(column::Expr, filter)::OptionalExpr + expression::OptionalExpr = missing + + function regex_escape(s::AbstractString) + res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:])" => s"\\\1") + replace(res, "\0" => "\\0") + end + # Unfortunately ag-grid's default text filter converts the user's input # to lowercase. Using regex with ignore case option rather normalizing # case on the field value. Thus we need to escape the user's input - filtervalue = _regex_escape(filter["filter"]) - + filtervalue = regex_escape(filter["filter"]) optype = filter["type"] - expression = "true" - if optype == "equals" - expression = "occursin(r\"\"\"^$filtervalue\$\"\"\"i, $column)" + matcher = Regex("^" * filtervalue * "\$", "i") + expression = :(occursin($matcher, $column)) elseif optype == "notEqual" - expression = "!occursin(r\"\"\"^$filtervalue\$\"\"\"i, $column)" + matcher = Regex("^" * filtervalue * "\$", "i") + expression = :(!occursin($matcher, $column)) elseif optype == "startsWith" - expression = "occursin(r\"\"\"^$filtervalue\"\"\"i, $column)" + matcher = Regex("^" * filtervalue, "i") + expression = :(occursin($matcher, $column)) elseif optype == "endsWith" - expression = "occursin(r\"\"\"$filtervalue\$\"\"\"i, $column)" + matcher = Regex(filtervalue * "\$", "i") + expression = :(occursin($matcher, $column)) elseif optype == "contains" - expression = "occursin(r\"\"\"$filtervalue\"\"\"i, $column)" + matcher = Regex(filtervalue, "i") + expression = :(occursin($matcher, $column)) elseif optype == "notContains" - expression = "!occursin(r\"\"\"$filtervalue\"\"\"i, $column)" + matcher = Regex(filtervalue, "i") + expression = :(!occursin($matcher, $column)) end return expression end - function build_date(column, filter) - optype = filter["type"] - filtervalue = filter["dateFrom"] - expression = "true" + function build_date(column::Expr, filter)::OptionalExpr + expression::OptionalExpr = missing + filtervalue = filter["dateFrom"] if filtervalue !== nothing - filtervalue = "Date$(Dates.yearmonthday(Date(filtervalue, _dateformat)))" + format = dateformat"y-m-d" + filtervalue = Date(filtervalue, format) + optype = filter["type"] if optype == "inRange" filterto = filter["dateTo"] # Only create a range expression if it is fully specified if filterto !== nothing - filterto = "Date$(Dates.yearmonthday(Date(filterto, _dateformat)))" - expression = """($filtervalue <= $column <= $filterto)""" + filterto = Date(filterto, format) + expression = :( ($filtervalue <= $column <= $filterto) ) end else - expression = """($column $(_mapdateop[optype]) $filtervalue)""" + expression = Expr(:call, mapop[optype], + column, filtervalue) end end return expression end - function build_filter(column, filter) - filtertype = filter["filterType"] - expression = "true" + function build_filter(column::Expr, filter)::OptionalExpr + expression::OptionalExpr = missing + filtertype = filter["filterType"] if filtertype == "number" expression = build_number(column, filter) elseif filtertype == "text" @@ -269,40 +270,45 @@ function _build_expressions(filtermodel) return expression end - function build_boolean(column, conditions) - return "(" * - build_filter(column, conditions["condition1"]) * - (conditions["operator"] == "OR" ? " || " : " && ") * - build_filter(column, conditions["condition2"]) * - ")" + function build_boolean(column::Expr, conditions)::OptionalExpr + expression::OptionalExpr = missing + + expr1 = build_filter(column, conditions["condition1"]) + expr2 = build_filter(column, conditions["condition2"]) + + if expr1 !== missing && expr2 !== missing + expression = conditions["operator"] == "OR" ? + :( ($expr1 || $expr2) ) : + :( ($expr1 && $expr2) ) + elseif expr1 !== missing + expression = expr1 + elseif expr2 !== missing + expression = expr2 + end + + return expression end - function column_access(column) - # Sanitize the column access. Even though column names - # are unlikely to inject code, raw strings do not interpolate. - # When using raw string, quote backslashes preceeding quotes first, - # then escape the quotes. This then roundtrips through - # parse/eval. - quoted = replace(replace(column, "\\\"" => "\\\\\""), "\"" => "\\\"") - return "getproperty(row, Symbol(raw\"\"\"$quoted\"\"\"))" + function column_access(column):Expr + return :( getproperty(row, Symbol($column)) ) end - return [ + return skipmissing([ haskey(cond, "filterType") ? build_filter(column_access(col), cond) : build_boolean(column_access(col), cond) - for (col, cond) in filtermodel] + for (col, cond) in filtermodel]) end -const _filterfns = Dict{String, Any}() +const _filterfns = Dict{Expr, Any}() function _filterfn(filtermodel) - code = "(row) -> begin $(join(_build_expressions(filtermodel), " && ")) end" - if haskey(_filterfns, code) - return _filterfns[code] + expression = :((row) -> $(Expr(:(&&), _build_expressions(filtermodel)...))) + if haskey(_filterfns, expression) + return _filterfns[expression] end - fltr = _filterfns[code] = eval(Meta.parse(code)) + fltr = _filterfns[expression] = eval(expression) # On the first call, we need to wrap the function to invokelatest return (row) -> Base.invokelatest(fltr, row) end From 40d9403dfcbf3b2f13b0e4d691b96a9e7e6a2880 Mon Sep 17 00:00:00 2001 From: Ross Jekel <4460474+jekelsc@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:19:49 -0700 Subject: [PATCH 6/6] Lose the OptionalExpr types @code_typed shows functions like build_date returning type Union{Missing, Expr} when not using OptionaExpr, but Any after a type assertion when using OptionalExpr. Type inference is better than type assertion if the code is correct I guess. --- src/TableView.jl | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/TableView.jl b/src/TableView.jl index ae2c430..7bb5952 100644 --- a/src/TableView.jl +++ b/src/TableView.jl @@ -159,8 +159,6 @@ function _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id, onimport(w, handler) end -const OptionalExpr = Union{Missing, Expr} - function _build_expressions(filtermodel) # Returns an iterator of Expr @@ -173,8 +171,8 @@ function _build_expressions(filtermodel) "greaterThanOrEqual" => :(>=), ) - function build_number(column::Expr, filter)::OptionalExpr - expression::OptionalExpr = missing + function build_number(column, filter) + expression = missing optype = filter["type"] filtervalue = filter["filter"] @@ -194,8 +192,8 @@ function _build_expressions(filtermodel) return expression end - function build_text(column::Expr, filter)::OptionalExpr - expression::OptionalExpr = missing + function build_text(column, filter) + expression = missing function regex_escape(s::AbstractString) res = replace(s, r"([()[\]{}?*+\-|^\$\\.&~#\s=!<>|:])" => s"\\\1") @@ -230,8 +228,8 @@ function _build_expressions(filtermodel) return expression end - function build_date(column::Expr, filter)::OptionalExpr - expression::OptionalExpr = missing + function build_date(column, filter) + expression = missing filtervalue = filter["dateFrom"] if filtervalue !== nothing @@ -255,8 +253,8 @@ function _build_expressions(filtermodel) return expression end - function build_filter(column::Expr, filter)::OptionalExpr - expression::OptionalExpr = missing + function build_filter(column, filter) + expression = missing filtertype = filter["filterType"] if filtertype == "number" @@ -270,8 +268,8 @@ function _build_expressions(filtermodel) return expression end - function build_boolean(column::Expr, conditions)::OptionalExpr - expression::OptionalExpr = missing + function build_boolean(column, conditions) + expression = missing expr1 = build_filter(column, conditions["condition1"]) expr2 = build_filter(column, conditions["condition2"]) @@ -289,7 +287,7 @@ function _build_expressions(filtermodel) return expression end - function column_access(column):Expr + function column_access(column) return :( getproperty(row, Symbol($column)) ) end