2023-05-23 11:30:39 +02:00
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 Leon van Kammen/NLNET
2023-03-09 19:58:08 +01:00
package xrfragment ;
2023-03-31 19:13:42 +02:00
//return untyped __js__("window.location.search");
2023-03-09 19:58:08 +01:00
#if js
var ok: Bool = js . Syntax . code ( '
// haxe workarounds
Array . prototype . contains = Array . prototype . includes
if ( typeof A r r a y . p r o t o t y p e . r e m o v e ! = = " f u n c t i o n " ) {
Array . prototype . remove = function ( item ) {
const oldLength = this . length
let newLength = 0
for ( let i = 0 ; i < o l d L e n g t h ; i + + ) {
const entry = this [ i ]
if ( entry == = item ) {
let newLength = i ++
while ( i != = this . length ) {
const entry = this [ i ]
if ( entry != = item ) this [ newLength ++ ] = entry
i ++
}
this . length = newLength
for ( let i = n e w L e n g t h ; i < o l d L e n g t h ; i + + ) delete this [ i ]
return true
}
}
return false
}
}
' ) ;
#end
2023-03-31 19:13:42 +02:00
@ : expose // <- makes the class reachable from plain JavaScript
@ : keep // <- avoids accidental removal by dead code elimination
2023-11-10 18:22:47 +01:00
class Filter {
2023-03-09 19:58:08 +01:00
2023-06-27 12:15:04 +02:00
/ * *
* # Spec
*
* > version 1.0 .0 [ ! [ Actions S t a t u s ] ( https : //github.com/coderofsalvation/xrfragment/workflows/test/badge.svg)](https://github.com/coderofsalvation/xrfragment/actions) generated by `make doc` @ $(date +"%Y-%m-%dT%H:%M:%S%z")
*
2023-11-10 18:22:47 +01:00
* In c ase your programming language has no parser ( [ check h e r e ] ( https : //github.com/coderofsalvation/xrfragment/tree/main/dist)) you can [crosscompile it](https://github.com/coderofsalvation/xrfragment/blob/main/build.hxml), or roll your own `Filter.parse(str)` using the spec:
2023-06-27 12:15:04 +02:00
* /
2023-11-10 18:22:47 +01:00
// 1. requirement: receive arguments: filter (string)
2023-06-27 12:15:04 +02:00
2023-03-09 19:58:08 +01:00
private var str: String = " " ;
2023-11-10 18:22:47 +01:00
private var q: haxe . DynamicAccess < Dynamic > = { } ; // 1. create an associative array/object to store filter-arguments as objects
2023-11-22 21:03:41 +01:00
private var isProp: EReg = ~/^.*=[><=]?/ ; // 1. detect object id's & properties `foo=1` and `foo` (reference regex= `~/^.*=[><=]?/` )
2023-11-10 18:22:47 +01:00
private var isExclude: EReg = ~/^-/ ; // 1. detect excluders like `-foo`,`-foo=1`,`-.foo`,`-/foo` (reference regex= `/^-/` )
private var isRoot: EReg = ~/^[-]?\// ; // 1. detect root selectors like `/foo` (reference regex= `/^[-]?\//` )
private var isNumber: EReg = ~/^[0-9\.]+$/ ; // 1. detect number values like `foo=1` (reference regex= `/^[0-9\.]+$/` )
2023-11-22 21:03:41 +01:00
private var operators: EReg = ~/(^-|\*$|\/)/ ; // 1. detect operators so you can easily strip keys (reference regex= `/(^-|\*$)/` )
2023-11-10 18:22:47 +01:00
private var isSelectorExclude: EReg = ~/^-/ ; // 1. detect exclude keys like `-foo` (reference regex= `/^-/` )
2023-03-09 19:58:08 +01:00
public function n e w ( str : String ) {
if ( str != null ) this . parse ( str ) ;
}
public function toObject ( ) : Dynamic {
2023-11-16 14:50:57 +01:00
return Reflect . copy ( this . q ) ;
2023-03-09 19:58:08 +01:00
}
2023-04-14 14:45:50 +02:00
public function get ( ) : Dynamic {
2023-11-16 14:50:57 +01:00
return Reflect . copy ( this . q ) ;
2023-04-14 14:45:50 +02:00
}
2023-06-22 13:27:08 +02:00
public function parse ( str : String ) : Dynamic {
2023-03-09 19:58:08 +01:00
var token = str . split ( " " ) ;
var q: haxe . DynamicAccess < Dynamic > = { } ;
2023-03-21 17:57:54 +01:00
function process ( str , prefix = " " ) {
str = StringTools . trim ( str ) ;
2023-11-17 15:30:18 +01:00
var k: String = str . split ( " = " ) [ 0 ] ; // 1. for every filter token split string on `=`
2023-11-10 18:22:47 +01:00
var v: String = str . split ( " = " ) [ 1 ] ;
2023-03-24 17:10:30 +01:00
// retrieve existing filter if any
var filter: haxe . DynamicAccess < Dynamic > = { } ;
if ( q . get ( prefix + k ) ) filter = q . get ( prefix + k ) ;
2023-11-17 15:30:18 +01:00
if ( isProp . match ( str ) ) { // 1. <b>WHEN</b></b> when a `=` key/value is detected:
2023-03-21 17:57:54 +01:00
var oper: String = " " ;
2023-06-27 12:15:04 +02:00
if ( str . indexOf ( " > " ) != - 1 ) oper = " > " ; // 1. then scan for `>` operator
if ( str . indexOf ( " < " ) != - 1 ) oper = " < " ; // 1. then scan for `<` operator
2023-07-03 17:06:16 +02:00
if ( isExclude . match ( k ) ) {
2023-11-17 15:30:18 +01:00
k = k . substr ( 1 ) ; // 1. then strip operators from key: convert "-foo" into "foo"
2023-11-16 14:50:57 +01:00
}
2023-11-17 15:30:18 +01:00
v = v . substr ( oper . length ) ; // 1. then strip operators from value: change value ">=foo" into "foo"
if ( oper . length == 0 ) oper = " = " ; // 1. when no operators detected, assume operator '='
2023-09-15 19:42:37 +02:00
var rule: haxe . DynamicAccess < Dynamic > = { } ;
if ( isNumber . match ( v ) ) rule [ oper ] = Std . parseFloat ( v ) ;
e lse rule [ oper ] = v ;
2023-11-16 14:50:57 +01:00
q . set ( ' e x p r ' , rule ) ;
2023-06-27 12:15:04 +02:00
} e lse { // 1. <b>ELSE </b> we are dealing with an object
2023-11-16 14:50:57 +01:00
q . set ( " r o o t " , isRoot . match ( str ) ? true : false ) ; // 1. and we set `root` to `true` or `false` (true=`/` root selector is present)
2023-03-09 19:58:08 +01:00
}
2023-11-16 14:50:57 +01:00
q . set ( " s h o w " , isExclude . match ( str ) ? false : true ) ; // 1. therefore we we set `show` to `true` or `false` (false=excluder `-`)
2023-11-17 15:30:18 +01:00
q . set ( " k e y " , operators . replace ( k , ' ' ) ) ;
2023-11-16 14:50:57 +01:00
q . set ( " v a l u e " , v ) ;
2023-03-09 19:58:08 +01:00
}
2023-09-15 19:42:37 +02:00
for ( i in 0 ... token . length ) process ( token [ i ] ) ;
2023-04-14 14:45:50 +02:00
return this . q = q ;
2023-03-09 19:58:08 +01:00
}
2023-03-31 19:13:42 +02:00
@ : keep
2023-03-24 17:10:30 +01:00
public function test ( ? obj : Dynamic ) : Bool {
var qualify: Bool = false ;
// first apply includes, then excludes
for ( k in Reflect . fields ( obj ) ) {
var v: String = Std . string ( Reflect . field ( obj , k ) ) ;
if ( testProperty ( k , v ) ) qualify = true ;
}
for ( k in Reflect . fields ( obj ) ) {
var v: String = Std . string ( Reflect . field ( obj , k ) ) ;
if ( testProperty ( k , v , true ) ) qualify = false ;
}
return qualify ;
}
2023-11-10 18:22:47 +01:00
/ *
* this is a utility function of a f i l t e r w h i c h h e l p s
* i n telling if a p r o p e r t y q u a l i f i e s a c c o r d i n g t o t h i s f i l t e r o b j e c t
* /
2023-03-31 19:13:42 +02:00
@ : keep
2023-03-24 17:10:30 +01:00
public function testProperty ( property : String , value : String , ? exclude : Bool ) : Bool {
2023-03-21 17:57:54 +01:00
var conds: Int = 0 ;
var fails: Int = 0 ;
var qualify: Int = 0 ;
var testprop = function ( expr : Bool ) : Bool {
conds += 1 ;
fails += expr ? 0 : 1 ;
return expr ;
2023-03-09 19:58:08 +01:00
}
2023-03-21 17:57:54 +01:00
// class or id
if ( q [ value ] != null ) {
var v: haxe . DynamicAccess < Dynamic > = q [ value ] ;
if ( v . get ( property ) != null ) return v . get ( property ) ;
}
2023-03-24 17:10:30 +01:00
// conditional rules
2023-11-16 14:50:57 +01:00
if ( Reflect . field ( q , ' e x p r ' ) ) {
var f: Dynamic = Reflect . field ( q , ' e x p r ' ) ;
2023-11-10 18:22:47 +01:00
2023-11-16 14:50:57 +01:00
if ( ! Reflect . field ( q , ' s h o w ' ) ) {
if ( Reflect . field ( f , ' ! = ' ) != null && testprop ( Std . string ( value ) == Std . string ( Reflect . field ( f , ' ! = ' ) ) ) && exclude ) qualify += 1 ;
2023-11-10 18:22:47 +01:00
} e lse {
2023-11-16 14:50:57 +01:00
if ( Reflect . field ( f , ' * ' ) != null && testprop ( Std . parseFloat ( value ) != null ) ) qualify += 1 ;
if ( Reflect . field ( f , ' > ' ) != null && testprop ( Std . parseFloat ( value ) > Std . parseFloat ( Reflect . field ( f , ' > ' ) ) ) ) qualify += 1 ;
if ( Reflect . field ( f , ' < ' ) != null && testprop ( Std . parseFloat ( value ) < Std . parseFloat ( Reflect . field ( f , ' < ' ) ) ) ) qualify += 1 ;
if ( Reflect . field ( f , ' = ' ) != null && (
testprop ( value == Reflect . field ( f , ' = ' ) ) ||
testprop ( Std . parseFloat ( value ) == Std . parseFloat ( Reflect . field ( f , ' = ' ) ) )
2023-11-10 18:22:47 +01:00
) ) qualify += 1 ;
2023-03-24 17:10:30 +01:00
}
2023-03-09 19:58:08 +01:00
}
2023-03-21 17:57:54 +01:00
return qualify > 0 ;
2023-03-09 19:58:08 +01:00
}
}
2023-06-27 12:15:04 +02:00
/ * *
2023-11-10 18:22:47 +01:00
* > icanhazcode ? yes , s e e [ P a r s e r . h x ] ( h t t p s : //github.com/coderofsalvation/xrfragment/blob/main/src/xrfragment/Filter.hx)
2023-06-27 12:15:04 +02:00
*
* # Tests
*
* the spec is tested with [ JSON u n i t t e s t s ] ( . / . . / src / spec ) consumed by [ Test . hx ] ( . / . . / src / Test . hx ) to cross - test all languages .
* /