wip: query=>filter + updated index.gltf (portals work)
This commit is contained in:
parent
0f60ebc1e8
commit
7249584dfa
File diff suppressed because one or more lines are too long
4
make
4
make
|
@ -27,8 +27,8 @@ install(){
|
|||
|
||||
tests(){
|
||||
{
|
||||
which python3 && python3 test/generated/test.py src/spec/*.json | awk '{ print "py: "$0 } END{ print "\n"}'
|
||||
which node && node test/generated/test.js src/spec/*.json | awk '{ print "js: "$0 } END{ print "\n"}'
|
||||
which python3 && python3 test/generated/test.py src/spec/*.json | awk '{ print "py: "$0 } END{ print "\n"}'
|
||||
} | awk '$2 ~ /src/ { $2=sprintf("%-30s",$2); print $0; next; } 1' | tee /tmp/log.txt
|
||||
grep error /tmp/log.txt && exit 1 || exit 0
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ build(){
|
|||
|
||||
parser(){
|
||||
try rm dist/*
|
||||
trace haxe build.hxml
|
||||
haxe build.hxml
|
||||
ok=$?
|
||||
sed -i 's|.*nonlocal .*||g' dist/xrfragment.py
|
||||
ls -lah dist/*
|
||||
|
|
|
@ -1,74 +1,71 @@
|
|||
xrf.portalNonEuclidian = function(v, opts){
|
||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||
xrf.portalNonEuclidian = function(opts){
|
||||
let { frag, mesh, model, camera, scene, renderer, stencilObjects} = opts
|
||||
|
||||
let toFrag = xrf.URI.parse( v.string )
|
||||
// turn plane into stencilplane
|
||||
mesh.material = new xrf.THREE.MeshPhongMaterial({ color: 'green' });
|
||||
mesh.material.depthWrite = false;
|
||||
mesh.material.stencilWrite = true;
|
||||
mesh.material.stencilRef = xrf.portalNonEuclidian.stencilRef;
|
||||
mesh.material.stencilFunc = THREE.AlwaysStencilFunc;
|
||||
mesh.material.stencilZPass = THREE.ReplaceStencilOp;
|
||||
//mesh.material.side = THREE.DoubleSide // *TODO* this requires flipping normals based on camera orientation
|
||||
mesh.portal = {
|
||||
stencilRef: xrf.portalNonEuclidian.stencilRef
|
||||
}
|
||||
|
||||
// turn plane into stencilplane
|
||||
mesh.material = new THREE.MeshPhongMaterial({ color: 'green' });
|
||||
mesh.material.depthWrite = false;
|
||||
mesh.material.stencilWrite = true;
|
||||
mesh.material.stencilRef = xrf.portalNonEuclidian.stencilRef;
|
||||
mesh.material.stencilFunc = THREE.AlwaysStencilFunc;
|
||||
mesh.material.stencilZPass = THREE.ReplaceStencilOp;
|
||||
//mesh.material.side = THREE.DoubleSide // *TODO* this requires flipping normals based on camera orientation
|
||||
mesh.portal = {
|
||||
stencilRef: xrf.portalNonEuclidian.stencilRef
|
||||
}
|
||||
let stencilPos = new xrf.THREE.Vector3()
|
||||
mesh.getWorldPosition(stencilPos)
|
||||
|
||||
let stencilPos = new xrf.THREE.Vector3()
|
||||
mesh.getWorldPosition(stencilPos)
|
||||
// allow objects to flip between original and stencil position (which puts them behind stencilplane)
|
||||
const addStencilFeature = (n) => {
|
||||
n.stencil = (
|
||||
(pos,stencilPos, stencilMat, mat ) => (enabled) => {
|
||||
let sRef = enabled ? mesh.portal.stencilRef : 0
|
||||
stencilMat.depthTest = false
|
||||
n.position.copy( enabled ? stencilPos : pos )
|
||||
n.material = enabled ? stencilMat : mat
|
||||
xrf.portalNonEuclidian.selectStencil(n, sRef ) // disable depthtest of world-container (sky e.g.)
|
||||
n.traverse( (c) => !c.portal && (xrf.portalNonEuclidian.selectStencil(c,sRef)) )
|
||||
}
|
||||
)( n.position.clone(), stencilPos, n.material.clone(), n.material )
|
||||
return n
|
||||
}
|
||||
|
||||
// allow objects to flip between original and stencil position (which puts them behind stencilplane)
|
||||
const addStencilFeature = (n) => {
|
||||
n.stencil = (
|
||||
(pos,stencilPos, stencilMat, mat ) => (enabled) => {
|
||||
let sRef = enabled ? mesh.portal.stencilRef : 0
|
||||
stencilMat.depthTest = false
|
||||
n.position.copy( enabled ? stencilPos : pos )
|
||||
n.material = enabled ? stencilMat : mat
|
||||
xrf.portalNonEuclidian.selectStencil(n, sRef ) // disable depthtest of world-container (sky e.g.)
|
||||
n.traverse( (c) => !c.portal && (xrf.portalNonEuclidian.selectStencil(c,sRef)) )
|
||||
}
|
||||
)( n.position.clone(), stencilPos, n.material.clone(), n.material )
|
||||
return n
|
||||
}
|
||||
// collect related objects from XRWG to render inside stencilplane
|
||||
if( stencilObjects.length == 0 ) return console.warn(`no objects are tagged with (portal)object name '${mesh.name}'`)
|
||||
stencilObjects = stencilObjects
|
||||
.filter( (n) => !n.portal ) // filter out (self)references to portals (prevent recursion)
|
||||
.map(addStencilFeature)
|
||||
|
||||
// add missing lights to make sure things get lit properly
|
||||
xrf.scene.traverse( (n) => n.isLight &&
|
||||
!stencilObjects.find( (o) => o.uuid == n.uuid ) &&
|
||||
(stencilObjects.push(n))
|
||||
)
|
||||
|
||||
// collect related objects from XRWG to render inside stencilplane
|
||||
let objs = XRWG.match(mesh.name,0)
|
||||
if( objs.length == 0 ) return console.warn(`no objects are tagged with (portal)object name '${mesh.name}'`)
|
||||
objs = objs[0].nodes
|
||||
.filter( (n) => !n.portal ) // filter out (self)references to portals (prevent recursion)
|
||||
.map(addStencilFeature)
|
||||
|
||||
// add missing lights to make sure things get lit properly
|
||||
xrf.scene.traverse( (n) => n.isLight &&
|
||||
!objs.find( (o) => o.uuid == n.uuid ) &&
|
||||
(objs.push(n))
|
||||
)
|
||||
// put it into a scene (without .add() because it reparents objects) so we can render it separately
|
||||
mesh.stencilObjects = new xrf.THREE.Scene()
|
||||
mesh.stencilObjects.children = stencilObjects
|
||||
|
||||
// put it into a scene (without .add() because it reparents objects) so we can render it separately
|
||||
mesh.stencilObjects = new xrf.THREE.Scene()
|
||||
mesh.stencilObjects.children = objs
|
||||
// *TODO* stencilize any tagged plane without material
|
||||
|
||||
// *TODO* stencilize any tagged plane without material
|
||||
// enable the stencil-material of the stencil objects
|
||||
const showPortal = (n,show) => {
|
||||
if( n.portal ) n.visible = show
|
||||
return true
|
||||
}
|
||||
mesh.onAfterRender = function(renderer, scene, camera, geometry, material, group ){
|
||||
mesh.stencilObjects.traverse( (n) => showPortal(n,false) && n.stencil && (n.stencil(true)) )
|
||||
renderer.autoClear = false
|
||||
renderer.autoClearStencil = false
|
||||
//renderer.sortObjects = false
|
||||
renderer.render( mesh.stencilObjects, camera )
|
||||
//renderer.sortObjects = true
|
||||
mesh.stencilObjects.traverse( (n) => showPortal(n,true) && n.stencil && (n.stencil(false)) )
|
||||
}
|
||||
|
||||
// enable the stencil-material of the stencil objects
|
||||
const showPortal = (n,show) => {
|
||||
if( n.portal ) n.visible = show
|
||||
return true
|
||||
}
|
||||
mesh.onAfterRender = function(renderer, scene, camera, geometry, material, group ){
|
||||
mesh.stencilObjects.traverse( (n) => showPortal(n,false) && n.stencil && (n.stencil(true)) )
|
||||
renderer.autoClear = false
|
||||
renderer.autoClearStencil = false
|
||||
//renderer.sortObjects = false
|
||||
renderer.render( mesh.stencilObjects, camera )
|
||||
//renderer.sortObjects = true
|
||||
mesh.stencilObjects.traverse( (n) => showPortal(n,true) && n.stencil && (n.stencil(false)) )
|
||||
}
|
||||
|
||||
xrf.portalNonEuclidian.stencilRef += 1 // each portal has unique stencil id
|
||||
console.log("enabling portal for object '${mesh.name}'`")
|
||||
xrf.portalNonEuclidian.stencilRef += 1 // each portal has unique stencil id
|
||||
console.log("enabling portal for object '${mesh.name}'`")
|
||||
}
|
||||
|
||||
xrf.portalNonEuclidian.selectStencil = (n, stencilRef, depthTest) => {
|
||||
|
@ -81,3 +78,22 @@ xrf.portalNonEuclidian.selectStencil = (n, stencilRef, depthTest) => {
|
|||
}
|
||||
|
||||
xrf.portalNonEuclidian.stencilRef = 1
|
||||
|
||||
// scan for non-euclidian portals (planes with nameless & textureless material which are tagged)
|
||||
xrf.addEventListener('parseModel', (opts) => {
|
||||
let {model} = opts
|
||||
model.scene.traverse( (n) => {
|
||||
const hasMaterialName = n.material && n.material.name.length > 0
|
||||
const hasTexture = n.material && n.material.map
|
||||
const isPlane = n.geometry && n.geometry.attributes.uv && n.geometry.attributes.uv.count == 4
|
||||
const isHref = n.userData.href != undefined
|
||||
const isSRC = n.userData.src != undefined || n.isSRC
|
||||
let stencilObjects = XRWG.match(n.name,0)
|
||||
const hasReferences = stencilObjects.length && stencilObjects[0].nodes.length > 1
|
||||
|
||||
if( n.geometry && hasReferences && !hasMaterialName && !hasTexture && !isSRC && !n.isSRC){
|
||||
xrf.portalNonEuclidian({...opts, mesh:n, stencilObjects: stencilObjects[0].nodes})
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
/*
|
||||
* TODO: refactor/fix this (queries are being refactored to filters)
|
||||
*/
|
||||
// spec: https://xrfragment.org/#queries
|
||||
|
||||
xrf.frag.q = function(v, opts){
|
||||
xrf.filter = function(v, opts){
|
||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||
console.log(" └ running query ")
|
||||
let qobjs = Object.keys(v.query)
|
||||
|
||||
// convience function for other fragments (which apply to the query)
|
||||
frag.q.getObjects = () => {
|
||||
frag.filter.getObjects = () => {
|
||||
let objs = []
|
||||
scene.traverse( (o) => {
|
||||
for ( let name in v.query ) {
|
||||
|
@ -21,20 +24,20 @@ xrf.frag.q = function(v, opts){
|
|||
return o
|
||||
})
|
||||
}
|
||||
xrf.frag.q.filter(scene,frag) // spec : https://xrfragment.org/#queries
|
||||
xrf.filter.scene(scene,frag) // spec : https://xrfragment.org/#queries
|
||||
}
|
||||
|
||||
xrf.frag.q.filter = function(scene,frag){
|
||||
xrf.filter.scene = function(scene,frag){
|
||||
// spec: https://xrfragment.org/#queries
|
||||
let q = frag.q.query
|
||||
scene.traverse( (mesh) => {
|
||||
for ( let i in q ) {
|
||||
let isMeshId = q[i].id != undefined
|
||||
let isMeshProperty = q[i].rules != undefined && q[i].rules.length && !isMeshId
|
||||
let isMeshId = q[i].id != undefined
|
||||
let isMeshProperty = q[i].filter != undefined && !isMeshId
|
||||
if( q[i].root && mesh.isSRC ) continue; // ignore nested object for root-items (queryseletor '/foo' e.g.)
|
||||
if( isMeshId &&
|
||||
(i == mesh.name || xrf.hasTag(i,mesh.userData.tag))) mesh.visible = q[i].id
|
||||
if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i])
|
||||
//if( isMeshProperty && mesh.userData[i] ) mesh.visible = (new xrf.Query(frag.q.string)).testProperty(i,mesh.userData[i])
|
||||
}
|
||||
})
|
||||
}
|
|
@ -33,11 +33,6 @@ xrf.frag.href = function(v, opts){
|
|||
|
||||
if( mesh.userData.XRF.href.exec ) return // mesh already initialized
|
||||
|
||||
// derived properties
|
||||
const isLocal = v.string[0] == '#'
|
||||
const isPlane = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4
|
||||
const hasSrc = mesh.userData.src != undefined
|
||||
|
||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||
|
||||
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
|
||||
|
@ -82,8 +77,6 @@ xrf.frag.href = function(v, opts){
|
|||
mesh.addEventListener('mouseenter', selected(true) )
|
||||
mesh.addEventListener('mouseleave', selected(false) )
|
||||
|
||||
if( isLocal && isPlane && !hasSrc && !mesh.material.map ) xrf.portalNonEuclidian(v,opts)
|
||||
|
||||
// lazy add mesh (because we're inside a recursive traverse)
|
||||
setTimeout( (mesh) => {
|
||||
xrf.interactive.add(mesh)
|
||||
|
|
|
@ -7,6 +7,7 @@ xrf.frag.src = function(v, opts){
|
|||
let src;
|
||||
let url = v.string
|
||||
let vfrag = xrfragment.URI.parse(url)
|
||||
console.dir({url,vfrag})
|
||||
opts.isPlane = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4
|
||||
|
||||
const addModel = (model,url,frag) => {
|
||||
|
@ -15,10 +16,11 @@ xrf.frag.src = function(v, opts){
|
|||
xrf.frag.src.scale( src, opts, url )
|
||||
xrf.frag.src.eval( src, opts, url )
|
||||
// allow 't'-fragment to setup separate animmixer
|
||||
xrf.emit('parseModel', {...opts, scene:src, model})
|
||||
enableSourcePortation(src)
|
||||
mesh.add(src)
|
||||
mesh.traverse( (n) => n.isSRC = n.isXRF = true )
|
||||
model.scene = src
|
||||
mesh.add(model.scene)
|
||||
mesh.traverse( (n) => n.isSRC = n.isXRF = true ) // mark everything SRC
|
||||
xrf.emit('parseModel', {...opts, scene:src, model})
|
||||
if( mesh.material ) mesh.material.visible = false
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,7 @@ xrf.frag.src.filterScene = (scene,opts) => {
|
|||
let { mesh, model, camera, renderer, THREE, hashbus, frag} = opts
|
||||
let obj, src
|
||||
// cherrypicking of object(s)
|
||||
if( !frag.q ){
|
||||
if( !frag.filter ){
|
||||
src = new THREE.Group()
|
||||
if( Object.keys(frag).length > 0 ){
|
||||
for( var i in frag ){
|
||||
|
@ -132,9 +134,10 @@ xrf.frag.src.filterScene = (scene,opts) => {
|
|||
}
|
||||
|
||||
// filtering of objects using query
|
||||
if( frag.q ){
|
||||
if( frag.filter ){
|
||||
console.warn("TODO: filter scene");
|
||||
src = scene
|
||||
xrf.frag.q.filter(src,frag)
|
||||
xrf.filter.scene(src,frag)
|
||||
}
|
||||
src.traverse( (m) => {
|
||||
if( m.userData && (m.userData.src || m.userData.href) ) return ; // prevent infinite recursion
|
||||
|
|
141
src/Test.hx
141
src/Test.hx
|
@ -1,4 +1,4 @@
|
|||
import xrfragment.Query;
|
||||
import xrfragment.Filter;
|
||||
import xrfragment.URI;
|
||||
import xrfragment.XRF;
|
||||
|
||||
|
@ -17,26 +17,25 @@ class Test {
|
|||
static public function main():Void {
|
||||
test( "url.json", Spec.load("src/spec/url.json") );
|
||||
test( "t.json", Spec.load("src/spec/t.json") );
|
||||
test( "q.selectors.json", Spec.load("src/spec/query.selectors.json") );
|
||||
test( "q.root.json", Spec.load("src/spec/query.root.json") );
|
||||
test( "q.rules.json", Spec.load("src/spec/query.rules.json") );
|
||||
test( "filter.selectors.json", Spec.load("src/spec/filter.selectors.json") );
|
||||
//test( Spec.load("src/spec/tmp.json") );
|
||||
if( errors > 1 ) trace("\n-----\n[ ❌] "+errors+" errors :/");
|
||||
}
|
||||
|
||||
static public function test( topic:String, spec:Array<Dynamic>):Void {
|
||||
trace("\n[.] running "+topic);
|
||||
var Query = xrfragment.Query;
|
||||
var Filter = xrfragment.Filter;
|
||||
for( i in 0...spec.length ){
|
||||
var q:Query = null;
|
||||
var f:Filter = null;
|
||||
var res:haxe.DynamicAccess<Dynamic> = null;
|
||||
var valid:Bool = false;
|
||||
var item:Dynamic = spec[i];
|
||||
if( item.fn == "query" ) q = new Query(item.data);
|
||||
if( item.fn == "url" ) res = URI.parse(item.data,0);
|
||||
if( item.expect.fn == "test" ) valid = item.expect.out == q.test( item.expect.input[0] );
|
||||
if( item.expect.fn == "testProperty" ) valid = item.expect.out == q.testProperty( item.expect.input[0], item.expect.input[1] );
|
||||
if( item.expect.fn == "testPropertyExclude" ) valid = item.expect.out == q.testProperty( item.expect.input[0], item.expect.input[1], true );
|
||||
f = new Filter(item.data);
|
||||
res = URI.parse(item.data,0);
|
||||
if( item.expect.fn == "test" ) valid = item.expect.out == f.test( item.expect.input[0] );
|
||||
if( item.expect.fn == "testProperty" ) valid = item.expect.out == f.testProperty( item.expect.input[0], item.expect.input[1] );
|
||||
if( item.expect.fn == "testPropertyInt" ) valid = item.expect.out == f.testProperty( item.expect.input[0], item.expect.input[1] );
|
||||
if( item.expect.fn == "testPropertyExclude" ) valid = item.expect.out == f.testProperty( item.expect.input[0], item.expect.input[1], true );
|
||||
if( item.expect.fn == "testParsed" ) valid = item.expect.out == res.exists(item.expect.input);
|
||||
if( item.expect.fn == "testPredefinedView" ) valid = res.exists(item.expect.input) && item.expect.out == res.get(item.expect.input).is( XRF.PV_EXECUTE) ;
|
||||
if( item.expect.fn == "testPropertyAssign" ) valid = res.exists(item.expect.input) && item.expect.out == res.get(item.expect.input).is( XRF.PROP_BIND) ;
|
||||
|
@ -46,7 +45,7 @@ class Test {
|
|||
if( item.expect.fn == "equal.x" ) valid = equalX(res,item);
|
||||
if( item.expect.fn == "equal.xy" ) valid = equalXY(res,item);
|
||||
if( item.expect.fn == "equal.xyz" ) valid = equalXYZ(res,item);
|
||||
if( item.expect.fn == "testQueryRoot" ) valid = item.expect.out == q.get()[ item.expect.input[0] ].root;
|
||||
if( item.expect.fn == "testFilterRoot" ) valid = item.expect.out == f.get()[ item.expect.input[0] ].root;
|
||||
var ok:String = valid ? "[ ✔ ] " : "[ ❌] ";
|
||||
trace( ok + item.fn + ": '" + item.data + "'" + (item.label ? " (" + (item.label?item.label:item.expect.fn) +")" : ""));
|
||||
if( !valid ) errors += 1;
|
||||
|
@ -75,114 +74,18 @@ class Test {
|
|||
trace( Uri.parse(url,0) );
|
||||
}
|
||||
|
||||
static public function testQuery():Void {
|
||||
var Query = xrfragment.Query;
|
||||
static public function testFilter():Void {
|
||||
var Filter = xrfragment.Filter;
|
||||
|
||||
trace( (new Query("foo or bar")).toObject() );
|
||||
trace( (new Query("class:fopoer or bar foo:bar")).toObject().or[0] );
|
||||
trace( (new Query("-skybox class:foo")).toObject().or[0] );
|
||||
trace( (new Query("foo/flop moo or bar")).toObject().or[0] );
|
||||
trace( (new Query("-foo/flop moo or bar")).toObject().or[0] );
|
||||
trace( (new Query("price:>4 moo or bar")).toObject().or[0] );
|
||||
trace( (new Query("price:>=4 moo or bar")).toObject().or[0] );
|
||||
trace( (new Query("price:<=4 moo or bar")).toObject().or[0] );
|
||||
trace( (new Query("price:!=4 moo or bar")).toObject().or[0] );
|
||||
|
||||
var q:Dynamic = new Query("price:!=4 moo or bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
q.test( "price", 4);
|
||||
var ok = !q.selected("slkklskdf");
|
||||
if( !ok ) throw 'node should not be allowed';
|
||||
|
||||
q = new Query("price:!=3 moo or bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
q.test( "price", 4);
|
||||
var ok = q.selected("slkklskdf");
|
||||
if( !ok ) throw 'non-mentioned node should be allowed';
|
||||
|
||||
q = new Query("moo or bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
var ok = !q.selected("slkklskdf");
|
||||
if( !ok ) throw 'node should not be allowed';
|
||||
obj = q.toObject();
|
||||
var ok = q.selected("moo");
|
||||
if( !ok ) throw 'moo should be allowed';
|
||||
var ok = q.selected("bar");
|
||||
if( !ok ) throw 'bar should be allowed';
|
||||
|
||||
q = new Query("price:>3 moo or bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
q.test( "price", 4);
|
||||
var ok = q.selected("foo");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
var ok = q.selected("bar");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
var ok = q.selected("moo");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
|
||||
q = new Query("price:>3 price:<10 -bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
q.test( "price", 4);
|
||||
var ok = q.selected("foo");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
var ok = !q.selected("bar");
|
||||
if( !ok ) throw 'bar should not be allowed';
|
||||
q.test("price", 20);
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'price 20 should not be allowed';
|
||||
|
||||
q = new Query("-bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
var ok = q.selected("foo");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
var ok = !q.selected("bar");
|
||||
if( !ok ) throw 'bar should not be allowed';
|
||||
|
||||
q = new Query("title:*");
|
||||
var obj:Dynamic = q.toObject();
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'node should not be allowed';
|
||||
q.test("foo","bar");
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'node should not be allowed';
|
||||
q.test("title","bar");
|
||||
var ok = q.selected("foo");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
|
||||
q = new Query("-bar +bar");
|
||||
var obj:Dynamic = q.toObject();
|
||||
var ok = q.selected("foo");
|
||||
if( !ok ) throw 'node should be allowed';
|
||||
var ok = q.selected("bar");
|
||||
if( !ok ) throw 'bar should be allowed';
|
||||
|
||||
q = new Query("?discount");
|
||||
var obj:Dynamic = q.toObject();
|
||||
q.test("?discount","-foo");
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'foo should not be allowed';
|
||||
|
||||
q = new Query("?");
|
||||
q.test("?","-foo");
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'foo should not be allowed';
|
||||
|
||||
q = new Query("?");
|
||||
var ok = q.selected("foo");
|
||||
if( !ok ) throw 'foo should not be allowed';
|
||||
|
||||
q = new Query("?discount");
|
||||
q.test("?discount","-foo");
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'foo should not be allowed';
|
||||
|
||||
q = new Query("?discount +foo");
|
||||
var obj:Dynamic = q.toObject();
|
||||
q.test("?discount","-foo");
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'foo should not be allowed';
|
||||
var ok = !q.selected("foo");
|
||||
if( !ok ) throw 'foo should not be allowed';
|
||||
trace( (new Filter("foo or bar")).toObject() );
|
||||
trace( (new Filter("class:fopoer or bar foo:bar")).toObject().or[0] );
|
||||
trace( (new Filter("-skybox class:foo")).toObject().or[0] );
|
||||
trace( (new Filter("foo/flop moo or bar")).toObject().or[0] );
|
||||
trace( (new Filter("-foo/flop moo or bar")).toObject().or[0] );
|
||||
trace( (new Filter("price:>4 moo or bar")).toObject().or[0] );
|
||||
trace( (new Filter("price:>=4 moo or bar")).toObject().or[0] );
|
||||
trace( (new Filter("price:<=4 moo or bar")).toObject().or[0] );
|
||||
trace( (new Filter("price:!=4 moo or bar")).toObject().or[0] );
|
||||
|
||||
trace("all tests passed");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[
|
||||
{"fn":"url","data":"http://foo.com?foo=1#foo*&-sometag&-someid&myid", "expect":{ "fn":"testParsed", "input":"myid","out":true},"label":"myid exists"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#tag=bar", "expect":{ "fn":"testParsed", "input":"tag", "out":true},"label":"tag exists"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#-tag=bar", "expect":{ "fn":"testParsed", "input":"tag", "out":true},"label":"tag exists"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#price=>2", "expect":{ "fn":"testParsed", "input":"price","out":true},"label":"query test"},
|
||||
{"fn":"query","data":"tag=bar", "expect":{ "fn":"testProperty","input":["tag","bar"],"out":true}},
|
||||
{"fn":"query","data":"-tag=foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":false}},
|
||||
{"fn":"query","data":"price=>2", "expect":{ "fn":"testProperty","input":["price","1"],"out":false}},
|
||||
{"fn":"query","data":"price=<2", "expect":{ "fn":"testProperty","input":["price","5"],"out":false}},
|
||||
{"fn":"query","data":"price=<2", "expect":{ "fn":"testProperty","input":["price","1"],"out":true}}
|
||||
]
|
|
@ -1,2 +0,0 @@
|
|||
[
|
||||
]
|
|
@ -1,15 +0,0 @@
|
|||
[
|
||||
{"fn":"query","data":"price:>=5", "expect":{ "fn":"testProperty","input":["price","10"],"out":true}},
|
||||
{"fn":"query","data":"price:>=15", "expect":{ "fn":"testProperty","input":["price","10"],"out":false}},
|
||||
{"fn":"query","data":"price:>=5", "expect":{ "fn":"testProperty","input":["price","4"],"out":false}},
|
||||
{"fn":"query","data":"price:>=5", "expect":{ "fn":"testProperty","input":["price","0"],"out":false}},
|
||||
{"fn":"query","data":"price:>=2", "expect":{ "fn":"testProperty","input":["price","2"],"out":true}},
|
||||
{"fn":"query","data":"price:>=5 price:0", "expect":{ "fn":"testProperty","input":["price","1"],"out":false},"label":"price=1"},
|
||||
{"fn":"query","data":"price:>=5 price:0", "expect":{ "fn":"testProperty","input":["price","0"],"out":true},"label":"price=0"},
|
||||
{"fn":"query","data":"price:>=5 price:0", "expect":{ "fn":"testProperty","input":["price","6"],"out":true},"label":"price=6"},
|
||||
{"fn":"query","data":"tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true}},
|
||||
{"fn":"query","data":"-tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":false}},
|
||||
{"fn":"query","data":"-tag:foo", "expect":{ "fn":"testPropertyExclude","input":["tag","foo"],"out":true},"label":"testExclude"},
|
||||
{"fn":"query","data":".foo price:5 -tag:foo", "expect":{ "fn":"test","input":[{"price":5}],"out":true}},
|
||||
{"fn":"query","data":".foo price:5 -tag:foo", "expect":{ "fn":"test","input":[{"tag":"foo","price":5}],"out":false}}
|
||||
]
|
|
@ -1,15 +0,0 @@
|
|||
[
|
||||
{"fn":"query","data":"tag:bar", "expect":{ "fn":"testProperty","input":["tag","bar"],"out":true}},
|
||||
{"fn":"query","data":"tag:bar -tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":false}},
|
||||
{"fn":"query","data":"tag:bar -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true}},
|
||||
{"fn":"query","data":"tag:bar -tag:bar tag:bar", "expect":{ "fn":"testProperty","input":["tag","bar"],"out":true}},
|
||||
{"fn":"query","data":"tag:foo -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||
{"fn":"query","data":"tag:foo -tag:foo bar:5 tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||
{"fn":"query","data":"tag:foo -tag:foo bar:>5 tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||
{"fn":"query","data":"tag:foo -tag:foo bar:>5 tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||
{"fn":"query","data":"tag:foo -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["tag","foo"],"out":true},"label":"tag:foo"},
|
||||
{"fn":"query","data":"tag:foo -tag:foo tag:foo", "expect":{ "fn":"testProperty","input":["id","foo"],"out":true},"label":"id:foo"},
|
||||
{"fn":"query","data":"tag:foo -foo foo", "expect":{ "fn":"testProperty","input":["id","foo"],"out":true},"label":"id:foo?"},
|
||||
{"fn":"query","data":"/foo", "expect":{ "fn":"testQueryRoot","input":["foo"],"out":true},"label":"foo should be root-only"},
|
||||
{"fn":"query","data":"/foo foo", "expect":{ "fn":"testQueryRoot","input":["foo"],"out":false},"label":"foo should recursively selected"}
|
||||
]
|
|
@ -1,7 +1,6 @@
|
|||
[
|
||||
{"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2", "expect":{ "fn":"equal.xyz", "input":"pos","out":false},"label":"equal.xyz: should trigger incompatible type)"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#pos=1.2,2.2,3", "expect":{ "fn":"equal.xyz", "input":"pos","out":"1.2,2.2,3"},"label":"equal.xyz"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#q=-bar", "expect":{ "fn":"testBrowserOverride", "input":"q","out":false},"label":"browser URI cannot override q (defined in asset)"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"another","out":true},"label":"test predefined view executed (multiple)"},
|
||||
{"fn":"url","data":"http://foo.com?foo=1#mypredefinedview&another", "expect":{ "fn":"testPredefinedView", "input":"mypredefinedview","out":true},"label":"test predefined view executed (multiple)"},
|
||||
|
|
|
@ -38,24 +38,26 @@ package xrfragment;
|
|||
|
||||
@:expose // <- makes the class reachable from plain JavaScript
|
||||
@:keep // <- avoids accidental removal by dead code elimination
|
||||
class Query {
|
||||
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 `Query.parse(str)` using the spec:
|
||||
* 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: query (string)
|
||||
// 1. requirement: receive arguments: filter (string)
|
||||
|
||||
private var str:String = "";
|
||||
private var q:haxe.DynamicAccess<Dynamic> = {}; // 1. create an associative array/object to store query-arguments as objects
|
||||
private var isProp:EReg = ~/^.*:[><=!]?/; // 1. detect object id's & properties `foo:1` and `foo` (reference regex: `/^.*:[><=!]?/` )
|
||||
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\.]+$/` )
|
||||
private var q:haxe.DynamicAccess<Dynamic> = {}; // 1. create an associative array/object to store filter-arguments as objects
|
||||
private var isProp:EReg = ~/^.*=[><=!]?/; // 1. detect object id's & properties `foo=1` and `foo` (reference regex= `/^.*=[><=!]?/` )
|
||||
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\.]+$/` )
|
||||
private var isDeepSelect:EReg = ~/\*$/; // 1. detect nested keys like 'foo*' (reference regex= `/\*$/` )
|
||||
private var isSelectorExclude:EReg = ~/^-/; // 1. detect exclude keys like `-foo` (reference regex= `/^-/` )
|
||||
|
||||
public function new(str:String){
|
||||
if( str != null ) this.parse(str);
|
||||
|
@ -76,34 +78,32 @@ class Query {
|
|||
|
||||
function process(str,prefix = ""){
|
||||
str = StringTools.trim(str);
|
||||
var k:String = str.split(":")[0]; // 1. for every query token split string on `:`
|
||||
var v:String = str.split(":")[1];
|
||||
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<Dynamic> = {};
|
||||
if( q.get(prefix+k) ) filter = q.get(prefix+k);
|
||||
filter['rules'] = filter['rules'] != null ? filter['rules'] : new Array<Dynamic>(); // 1. create an empty array `rules`
|
||||
|
||||
if( isProp.match(str) ){ // 1. <b>WHEN</b></b> when a `:` key/value is detected:
|
||||
var oper:String = "";
|
||||
if( str.indexOf("*") != -1 ) oper = "*"; // 1. then scan for `*` operator (means include all objects for [src](#src) embedded fragment)
|
||||
if( str.indexOf(">") != -1 ) oper = ">"; // 1. then scan for `>` operator
|
||||
if( str.indexOf("<") != -1 ) oper = "<"; // 1. then scan for `<` operator
|
||||
if( str.indexOf(">=") != -1 ) oper = ">="; // 1. then scan for `>=` operator
|
||||
if( str.indexOf("<=") != -1 ) oper = "<="; // 1. then scan for `<=` operator
|
||||
if( isExclude.match(k) ){
|
||||
oper = "!=";
|
||||
oper = "=!";
|
||||
k = k.substr(1); // 1. then strip key-operator: convert "-foo" into "foo"
|
||||
}else v = v.substr(oper.length); // 1. then strip value operator: change value ">=foo" into "foo"
|
||||
if( oper.length == 0 ) oper = "=";
|
||||
var rule:haxe.DynamicAccess<Dynamic> = {};
|
||||
if( isNumber.match(v) ) rule[ oper ] = Std.parseFloat(v);
|
||||
else rule[oper] = v;
|
||||
filter['rules'].push( rule ); // 1. add operator and value to rule-array
|
||||
filter['filter'] = rule; // 1. add filter rule
|
||||
q.set( k, filter );
|
||||
return;
|
||||
}else{ // 1. <b>ELSE </b> we are dealing with an object
|
||||
filter[ "id" ] = isExclude.match(str) ? false: true; // 1. therefore we we set `id` to `true` or `false` (false=excluder `-`)
|
||||
filter[ "root" ] = isRoot.match(str) ? true: false; // 1. and we set `root` to `true` or `false` (true=`/` root selector is present)
|
||||
filter[ "id" ] = isExclude.match(str) ? false: true; // 1. therefore we we set `id` to `true` or `false` (false=excluder `-`)
|
||||
filter[ "root" ] = isRoot.match(str) ? true: false; // 1. and we set `root` to `true` or `false` (true=`/` root selector is present)
|
||||
filter[ "deep" ] = isDeepSelect.match(str) ? true: false; // 1. and we set `deep` to `true` or `false` (for objectnames with * suffix)
|
||||
if( isExclude.match(str) ) str = str.substr(1); // convert '-foo' into 'foo'
|
||||
if( isRoot.match(str) ) str = str.substr(1); // 1. we convert key '/foo' into 'foo'
|
||||
q.set( str ,filter ); // 1. finally we add the key/value to the store (`store.foo = {id:false,root:true}` e.g.)
|
||||
|
@ -128,6 +128,10 @@ class Query {
|
|||
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;
|
||||
|
@ -148,32 +152,29 @@ class Query {
|
|||
|
||||
// conditional rules
|
||||
for ( k in Reflect.fields(q) ){
|
||||
var filter:Dynamic = Reflect.field(q,k);
|
||||
if( filter.rules == null ) continue;
|
||||
var rules:Array<Dynamic> = filter.rules;
|
||||
var f:Dynamic = Reflect.field(q,k);
|
||||
if( f.filter == null ) continue;
|
||||
|
||||
for( rule in rules ){
|
||||
//if( Std.isOfType(value, String) ) contiggnue;
|
||||
if( exclude ){
|
||||
if( Reflect.field(rule,'!=') != null && testprop( Std.string(value) == Std.string(Reflect.field(rule,'!='))) && exclude ) qualify += 1;
|
||||
}else{
|
||||
if( Reflect.field(rule,'*') != null && testprop( Std.parseFloat(value) != null ) ) qualify += 1;
|
||||
if( Reflect.field(rule,'>') != null && testprop( Std.parseFloat(value) > Std.parseFloat(Reflect.field(rule,'>' )) ) ) qualify += 1;
|
||||
if( Reflect.field(rule,'<') != null && testprop( Std.parseFloat(value) < Std.parseFloat(Reflect.field(rule,'<' )) ) ) qualify += 1;
|
||||
if( Reflect.field(rule,'>=') != null && testprop( Std.parseFloat(value) >= Std.parseFloat(Reflect.field(rule,'>=')) ) ) qualify += 1;
|
||||
if( Reflect.field(rule,'<=') != null && testprop( Std.parseFloat(value) <= Std.parseFloat(Reflect.field(rule,'<=')) ) ) qualify += 1;
|
||||
if( Reflect.field(rule,'=') != null && (
|
||||
testprop( value == Reflect.field(rule,'=')) ||
|
||||
testprop( Std.parseFloat(value) == Std.parseFloat(Reflect.field(rule,'=')))
|
||||
)) qualify += 1;
|
||||
}
|
||||
//if( Std.isOfType(value, String) ) contiggnue;
|
||||
if( exclude ){
|
||||
if( Reflect.field(f.filter,'!=') != null && testprop( Std.string(value) == Std.string(Reflect.field(f.filter,'!='))) && exclude ) qualify += 1;
|
||||
}else{
|
||||
if( Reflect.field(f.filter,'*') != null && testprop( Std.parseFloat(value) != null ) ) qualify += 1;
|
||||
if( Reflect.field(f.filter,'>') != null && testprop( Std.parseFloat(value) > Std.parseFloat(Reflect.field(f.filter,'>' )) ) ) qualify += 1;
|
||||
if( Reflect.field(f.filter,'<') != null && testprop( Std.parseFloat(value) < Std.parseFloat(Reflect.field(f.filter,'<' )) ) ) qualify += 1;
|
||||
if( Reflect.field(f.filter,'>=') != null && testprop( Std.parseFloat(value) >= Std.parseFloat(Reflect.field(f.filter,'>=')) ) ) qualify += 1;
|
||||
if( Reflect.field(f.filter,'<=') != null && testprop( Std.parseFloat(value) <= Std.parseFloat(Reflect.field(f.filter,'<=')) ) ) qualify += 1;
|
||||
if( Reflect.field(f.filter,'=') != null && (
|
||||
testprop( value == Reflect.field(f.filter,'=')) ||
|
||||
testprop( Std.parseFloat(value) == Std.parseFloat(Reflect.field(f.filter,'=')))
|
||||
)) qualify += 1;
|
||||
}
|
||||
}
|
||||
return qualify > 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* > icanhazcode? yes, see [Parser.hx](https://github.com/coderofsalvation/xrfragment/blob/main/src/xrfragment/Query.hx)
|
||||
* > icanhazcode? yes, see [Parser.hx](https://github.com/coderofsalvation/xrfragment/blob/main/src/xrfragment/Filter.hx)
|
||||
*
|
||||
* # Tests
|
||||
*
|
|
@ -7,8 +7,9 @@ import xrfragment.XRF;
|
|||
@:expose // <- makes the class reachable from plain JavaScript
|
||||
@:keep // <- avoids accidental removal by dead code elimination
|
||||
class Parser {
|
||||
public static var error:String = "";
|
||||
public static var debug:Bool = false;
|
||||
public static var error:String = "";
|
||||
public static var debug:Bool = false;
|
||||
public static var keyClean:EReg = ~/(\*$|^-)/g;
|
||||
|
||||
@:keep
|
||||
public static function parse(key:String,value:String,store:haxe.DynamicAccess<Dynamic>):Bool {
|
||||
|
@ -16,33 +17,17 @@ class Parser {
|
|||
var Frag:Map<String, Int> = new Map<String, Int>();
|
||||
|
||||
Frag.set("#", XRF.ASSET | XRF.T_PREDEFINED_VIEW | XRF.PV_EXECUTE );
|
||||
Frag.set("prio", XRF.ASSET | XRF.T_INT );
|
||||
Frag.set("src", XRF.ASSET | XRF.T_URL );
|
||||
|
||||
// category: href navigation / portals / teleporting
|
||||
Frag.set("href", XRF.ASSET | XRF.T_URL | XRF.T_PREDEFINED_VIEW );
|
||||
Frag.set("tag", XRF.ASSET | XRF.T_STRING );
|
||||
|
||||
// category: query selector / object manipulation
|
||||
// spatial category: query selector / object manipulation
|
||||
Frag.set("pos", XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.T_STRING_OBJ | XRF.METADATA | XRF.NAVIGATOR );
|
||||
Frag.set("q", XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA );
|
||||
Frag.set("scale", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
|
||||
Frag.set("rot", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA | XRF.NAVIGATOR );
|
||||
Frag.set("mov", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
|
||||
Frag.set("show", XRF.QUERY_OPERATOR | XRF.PV_OVERRIDE | XRF.T_INT | XRF.METADATA );
|
||||
Frag.set("env", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_STRING | XRF.METADATA );
|
||||
|
||||
// category: animation
|
||||
Frag.set("t", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_STRING | XRF.NAVIGATOR | XRF.METADATA);
|
||||
Frag.set("tv", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_FLOAT | XRF.T_VECTOR2 | XRF.T_VECTOR3 | XRF.NAVIGATOR | XRF.METADATA);
|
||||
Frag.set("gravity", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
|
||||
Frag.set("physics", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.METADATA );
|
||||
|
||||
// category: device / viewport settings
|
||||
Frag.set("fov", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_INT | XRF.NAVIGATOR | XRF.METADATA );
|
||||
Frag.set("clip", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR2 | XRF.NAVIGATOR | XRF.METADATA );
|
||||
Frag.set("fog", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR2 | XRF.NAVIGATOR | XRF.METADATA );
|
||||
Frag.set("bg", XRF.ASSET | XRF.PV_OVERRIDE | XRF.T_VECTOR3 | XRF.NAVIGATOR | XRF.METADATA );
|
||||
|
||||
// category: author / metadata
|
||||
Frag.set("namespace", XRF.ASSET | XRF.T_STRING );
|
||||
|
@ -64,12 +49,12 @@ class Parser {
|
|||
// 1. requirement: receive arguments: key (string), value (string), store (writable associative array/object)
|
||||
|
||||
// dynamic fragments cases: predefined views & assign/binds
|
||||
var isPVDynamic:Bool = value.length == 0 && key.length > 0 && !Frag.exists(key);
|
||||
var isPVDynamic:Bool = key.length > 0 && !Frag.exists(key);
|
||||
var isPVDefault:Bool = value.length == 0 && key.length > 0 && key == "#";
|
||||
if( isPVDynamic ){ //|| isPVDefault ){ // 1. add keys without values to store as [predefined view](predefined_view)
|
||||
var v:XRF = new XRF(key, XRF.PV_EXECUTE | XRF.NAVIGATOR );
|
||||
v.validate(key); // will fail but will parse multiple args for us (separated by |)
|
||||
store.set(key, v );
|
||||
v.validate(value); // will fail but will parse multiple args for us (separated by |)
|
||||
store.set( keyClean.replace(key,''), v );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -85,9 +70,8 @@ class Parser {
|
|||
}else{ // 1. expose (but mark) non-offical fragments too
|
||||
if( Std.isOfType(value, String) ) v.guessType(v,value);
|
||||
v.noXRF = true;
|
||||
store.set(key,v);
|
||||
store.set( keyClean.replace(key,'') ,v);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,6 @@ import xrfragment.XRF;
|
|||
* sub-delims = "," / "="
|
||||
* ```
|
||||
*
|
||||
* > Example: `://foo.com/my3d.asset#pos=1,0,0&prio=-5&t=0,100|100,200`
|
||||
*
|
||||
* | Explanation | |
|
||||
* |-|-|
|
||||
* | `pos=1,2,3` | vector/coordinate argument e.g. |
|
||||
* | `pos=1,2,3&rot=0,90,0&q=.foo` | combinators |
|
||||
*
|
||||
* 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 `Parser.parse(k,v,store)` using the spec:
|
||||
*
|
||||
*/
|
||||
|
@ -38,7 +31,7 @@ class URI {
|
|||
if( url == null || url.indexOf("#") == -1 ) return store;
|
||||
var fragment:Array<String> = url.split("#"); // 1. fragment URI starts with `#`
|
||||
var splitArray:Array<String> = fragment[1].split('&'); // 1. fragments are split by `&`
|
||||
for (i in 0...splitArray.length) { // 1. loop thru each fragment
|
||||
for (i in 0...splitArray.length) { // 1. loop thru each fragment
|
||||
|
||||
var splitByEqual = splitArray[i].split('='); // 1. for each fragment split on `=` to separate key/values
|
||||
var regexPlus = ~/\+/g; // 1. fragment-values are urlencoded (space becomes `+` using `encodeUriComponent` e.g.)
|
||||
|
|
|
@ -15,7 +15,7 @@ class XRF {
|
|||
// scope types (powers of 2)
|
||||
public static var ASSET:Int = 1; // fragment is immutable
|
||||
public static var PROP_BIND:Int = 2; // fragment binds/controls one property with another
|
||||
public static var QUERY_OPERATOR:Int = 4; // fragment will be applied to result of queryselecto
|
||||
public static var QUERY_OPERATOR:Int = 4; // fragment will be applied to result of filterselecto
|
||||
public static var PROMPT:Int = 8; // ask user whether this fragment value can be changed
|
||||
public static var ROUNDROBIN:Int = 16; // evaluation of this (multi) value can be roundrobined
|
||||
public static var NAVIGATOR:Int = 32; // fragment can be overridden by (manual) browser URI change
|
||||
|
@ -55,7 +55,7 @@ class XRF {
|
|||
public var string:String; // |string| | | #q=-sun |
|
||||
public var int:Int; // |int | | [-]x[xxxxx] | #price:>=100 |
|
||||
public var float:Float; // |float | | [-]x[.xxxx] (ieee)| #prio=-20 |
|
||||
public var query:Query;
|
||||
public var filter:Filter;
|
||||
public var noXRF:Bool;
|
||||
//
|
||||
public function new(_fragment:String,_flags:Int){
|
||||
|
@ -78,8 +78,6 @@ class XRF {
|
|||
|
||||
public function validate(value:String) : Bool{
|
||||
guessType(this, value); // 1. extract the type
|
||||
// special case: query has its own DSL (*TODO* allow fragments to have custom validators)
|
||||
if( fragment == "q" ) query = (new Query(value)).get();
|
||||
// validate
|
||||
var ok:Bool = true;
|
||||
if( !is(T_FLOAT) && is(T_VECTOR2) && !(Std.isOfType(x,Float) && Std.isOfType(y,Float)) ) ok = false;
|
||||
|
@ -90,23 +88,27 @@ class XRF {
|
|||
@:keep
|
||||
public function guessType(v:XRF, str:String):Void {
|
||||
v.string = str;
|
||||
if( str.split(",").length > 1){ // 1. `,` assumes 1D/2D/3D vector-values like x[,y[,z]]
|
||||
var xyzw:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values
|
||||
if( xyzw.length > 0 ) v.x = Std.parseFloat(xyzw[0]); // 1. anything else will be treated as string-value
|
||||
if( xyzw.length > 1 ) v.y = Std.parseFloat(xyzw[1]); // 1. incompatible value-types will be dropped / not used
|
||||
if( xyzw.length > 2 ) v.z = Std.parseFloat(xyzw[2]); //
|
||||
if( xyzw.length > 3 ) v.w = Std.parseFloat(xyzw[3]); //
|
||||
} // > the xrfragment specification should stay simple enough
|
||||
// > for anyone to write a parser using either regexes or grammar/lexers
|
||||
if( isColor.match(str) ) v.color = str; // > therefore expressions/comprehensions are not supported (max wildcard/comparison operators for queries e.g.)
|
||||
if( isFloat.match(str) ){
|
||||
v.x = Std.parseFloat(str);
|
||||
v.float = v.x;
|
||||
}
|
||||
if( isInt.match(str) ){
|
||||
v.int = Std.parseInt(str);
|
||||
v.x = cast(v.int);
|
||||
}
|
||||
if( !Std.isOfType(str,String) ) return;
|
||||
if( str.length > 0 ){
|
||||
if( str.split(",").length > 1){ // 1. `,` assumes 1D/2D/3D vector-values like x[,y[,z]]
|
||||
var xyzw:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values
|
||||
if( xyzw.length > 0 ) v.x = Std.parseFloat(xyzw[0]); // 1. anything else will be treated as string-value
|
||||
if( xyzw.length > 1 ) v.y = Std.parseFloat(xyzw[1]); // 1. incompatible value-types will be dropped / not used
|
||||
if( xyzw.length > 2 ) v.z = Std.parseFloat(xyzw[2]); //
|
||||
if( xyzw.length > 3 ) v.w = Std.parseFloat(xyzw[3]); //
|
||||
} // > the xrfragment specification should stay simple enough
|
||||
// > for anyone to write a parser using either regexes or grammar/lexers
|
||||
if( isColor.match(str) ) v.color = str; // > therefore expressions/comprehensions are not supported (max wildcard/comparison operators for queries e.g.)
|
||||
if( isFloat.match(str) ){
|
||||
v.x = Std.parseFloat(str);
|
||||
v.float = v.x;
|
||||
}
|
||||
if( isInt.match(str) ){
|
||||
v.int = Std.parseInt(str);
|
||||
v.x = cast(v.int);
|
||||
}
|
||||
filter = (new Filter(v.fragment+"="+v.string)).get();
|
||||
}else filter = (new Filter(v.fragment)).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue