// SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 Leon van Kammen/NLNET package xrfragment; import xrfragment.XRF; //return untyped __js__("window.location.search"); #if js var ok:Bool = js.Syntax.code(' // haxe workarounds Array.prototype.contains = Array.prototype.includes if (typeof Array.prototype.remove !== "function") { Array.prototype.remove = function (item) { const oldLength = this.length let newLength = 0 for (let i = 0; i < oldLength; 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 = newLength; i < oldLength; i++) delete this[i] return true } } return false } } '); #end @:expose // <- makes the class reachable from plain JavaScript @:keep // <- avoids accidental removal by dead code elimination class Filter { /** * # Spec * * > version 1.0.0 [![Actions Status](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") * * In case your programming language has no parser ([check here](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: */ // 1. requirement: receive arguments: filter (string) private var str:String = ""; private var q:haxe.DynamicAccess = {}; // 1. create an associative array/object to store filter-arguments as objects public function new(str:String){ if( str != null ) this.parse(str); } public function toObject() : Dynamic { return Reflect.copy(this.q); } public function get() : Dynamic { return Reflect.copy(this.q); } public function parse(str:String) : Dynamic { var token = str.split(" "); var q:haxe.DynamicAccess = {}; function process(str,prefix = ""){ str = StringTools.trim(str); var k:String = str.split("=")[0]; // 1. for every filter token split string on `=` var v:String = str.split("=")[1]; // retrieve existing filter if any var filter:haxe.DynamicAccess = {}; if( q.get(prefix+k) ) filter = q.get(prefix+k); if( XRF.isProp.match(str) ){ // 1. WHEN when a `=` key/value is detected: var oper:String = ""; if( str.indexOf(">") != -1 ) oper = ">"; // 1. then scan for `>` operator if( str.indexOf("<") != -1 ) oper = "<"; // 1. then scan for `<` operator if( XRF.isExclude.match(k) ){ k = k.substr(1); // 1. then strip operators from key: convert "-foo" into "foo" } 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 '=' var rule:haxe.DynamicAccess = {}; if( XRF.isNumber.match(v) ) rule[ oper ] = Std.parseFloat(v); else rule[oper] = v; q.set('expr',rule); } q.set("deep", XRF.isDeep.match(str) ? k.split("*").length-1 : 0 ); // 1. and we set `deep` to >0 or based on * occurences (true=`*` deep selector is present) q.set("show", XRF.isExclude.match(str) ? false : true ); // 1. therefore we we set `show` to `true` or `false` (false=excluder `-`) q.set("key", XRF.operators.replace(k,'') ); q.set("value",v); } for( i in 0...token.length ) process( token[i] ); return this.q = q; } @:keep 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; } /* * this is a utility function of a filter which helps * in telling if a property qualifies according to this filter object */ @:keep public function testProperty( property:String, value:String, ?exclude:Bool ):Bool{ 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; } // class or id if( q[value] != null ){ var v:haxe.DynamicAccess = q[value]; if( v.get(property) != null ) return v.get(property); } // conditional rules if( Reflect.field(q,'expr') ){ var f:Dynamic = Reflect.field(q,'expr'); if( !Reflect.field(q,'show') ){ if( Reflect.field(f,'!=') != null && testprop( Std.string(value) == Std.string(Reflect.field(f,'!='))) && exclude ) qualify += 1; }else{ 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,'='))) )) qualify += 1; } } return qualify > 0; } } /** * > icanhazcode? yes, see [Parser.hx](https://github.com/coderofsalvation/xrfragment/blob/main/src/xrfragment/Filter.hx) * * # Tests * * the spec is tested with [JSON unittests](./../src/spec) consumed by [Test.hx](./../src/Test.hx) to cross-test all languages. */