From 8de198830222aff130b0acfd5994ef05ea4340e9 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Wed, 10 Apr 2024 16:38:50 +0000 Subject: [PATCH] work in progress [might break] --- src/3rd/js/plugin/frontend/$chat.js | 2 +- src/3rd/js/plugin/frontend/frontend.js | 2 +- src/3rd/js/plugin/matrix/matrix.js | 2 +- src/3rd/js/plugin/p2p/trystero.js | 2 +- src/3rd/js/three/hashbus.js | 2 +- src/3rd/js/three/index.js | 18 - src/3rd/js/three/navigator.js | 74 +++-- src/3rd/js/three/xrf/href.js | 1 - src/3rd/js/three/xrf/src.js | 2 +- src/3rd/js/three/xrf/src/audio.js | 6 +- src/3rd/js/three/xrf/src/fbx.js | 3 +- src/3rd/js/three/xrf/src/glsl.js | 7 +- src/3rd/js/three/xrf/src/gltf.js | 14 +- src/3rd/js/three/xrf/src/html.js | 2 +- src/3rd/js/three/xrf/src/image.js | 4 +- src/3rd/js/three/xrf/src/video.js | 6 +- src/Test.hx | 15 +- src/xrfragment/URI.hx | 421 +++++++++++++++++++++++- src/xrfragment/URL.hx | 436 ------------------------- 19 files changed, 499 insertions(+), 520 deletions(-) delete mode 100644 src/xrfragment/URL.hx diff --git a/src/3rd/js/plugin/frontend/$chat.js b/src/3rd/js/plugin/frontend/$chat.js index 2bd87ad..296eac8 100644 --- a/src/3rd/js/plugin/frontend/$chat.js +++ b/src/3rd/js/plugin/frontend/$chat.js @@ -106,7 +106,7 @@ chatComponent = { div.classList.add.apply(div.classList, opts.class.concat(["envelope"])) } if( !msg.className.match(/(info|guide|ui)/) ){ - let frag = xrf.URI.parse(document.location.hash) + let frag = xrf.URI.parse(document.location.hash).XRF opts.from = 'you' if( frag.pos ) opts.pos = frag.pos.string msg.classList.add('self') diff --git a/src/3rd/js/plugin/frontend/frontend.js b/src/3rd/js/plugin/frontend/frontend.js index 0c158d6..9dcd75b 100644 --- a/src/3rd/js/plugin/frontend/frontend.js +++ b/src/3rd/js/plugin/frontend/frontend.js @@ -274,7 +274,7 @@ window.frontend = (opts) => new Proxy({ // load original scene and overwrite with updates let url = document.location.search.replace(/\?/,'') - let {urlObj,dir,file,hash,ext} = xrf.navigator.origin = xrf.parseUrl(url) + let {urlObj,dir,file,hash,ext} = xrf.navigator.origin = xrf.URI.parse(url) const Loader = xrf.loaders[ext] loader = new Loader().setPath( dir ) notify('exporting scene

please wait..') diff --git a/src/3rd/js/plugin/matrix/matrix.js b/src/3rd/js/plugin/matrix/matrix.js index 4022afa..487f492 100644 --- a/src/3rd/js/plugin/matrix/matrix.js +++ b/src/3rd/js/plugin/matrix/matrix.js @@ -314,7 +314,7 @@ window.matrix = (opts) => new Proxy({ this.ydoc.scene.set('href',document.location.hash ) } }) - let hashvars = xrf.URI.parse( document.location.hash ) + let hashvars = xrf.URI.parse( document.location.hash ).XRF if( hashvars.meet ) this.parseLink(hashvars.meet.string) } }, diff --git a/src/3rd/js/plugin/p2p/trystero.js b/src/3rd/js/plugin/p2p/trystero.js index 21dd921..36c3baf 100644 --- a/src/3rd/js/plugin/p2p/trystero.js +++ b/src/3rd/js/plugin/p2p/trystero.js @@ -258,7 +258,7 @@ window.trystero = (opts) => new Proxy({ let isTeleport = href.match(/(pos=|http:)/) if( isLocal && !isTeleport && this.href.send ) this.href.send({href}) }) - let hashvars = xrf.URI.parse( document.location.hash ) + let hashvars = xrf.URI.parse( document.location.hash ).XRF if( hashvars.meet ) this.parseLink(hashvars.meet.string) } diff --git a/src/3rd/js/three/hashbus.js b/src/3rd/js/three/hashbus.js index 12f376e..e83b1ae 100644 --- a/src/3rd/js/three/hashbus.js +++ b/src/3rd/js/three/hashbus.js @@ -7,7 +7,7 @@ let pub = function( url, node_or_model, flags ){ // evaluate fragments in url if( !url ) return if( !url.match(/#/) ) url = `#${url}` let { THREE, camera } = xrf - let frag = xrf.URI.parse( url, flags ) + let frag = xrf.URI.parse( url, flags ).XRF let fromNode = node_or_model != xrf.model let isNode = node_or_model && node_or_model.children diff --git a/src/3rd/js/three/index.js b/src/3rd/js/three/index.js index 8899511..344a920 100644 --- a/src/3rd/js/three/index.js +++ b/src/3rd/js/three/index.js @@ -88,24 +88,6 @@ xrf.reset = () => { xrf.layers = 0 } -xrf.parseUrl = (url) => { - let urlExHash = url.replace(/#.*/,'') - let urlObj,file - let store = {} - try{ - urlObj = new URL( urlExHash.match(/:\/\//) ? urlExHash : String(`https://fake.com/${url}`).replace(/\/\//,'/') ) - file = urlObj.pathname.substring(urlObj.pathname.lastIndexOf('/') + 1); - let search = urlObj.search.substr(1).split("&") - for( let i in search ) store[ (search[i].split("=")[0]) ] = search[i].split("=")[1] || '' - }catch(e){ } - let hashmap = url.match("#") ? url.replace(/.*#/,'').split("&") : [] - for( let i in hashmap ) store[ (hashmap[i].split("=")[0]) ] = hashmap[i].split("=")[1] || '' - let dir = url.substring(0, url.lastIndexOf('/') + 1) - const hash = url.match(/#/) ? url.replace(/.*#/,'') : '' - const ext = file.split('.').pop() - return {urlObj,dir,file,hash,ext,store} -} - xrf.add = (object) => { object.isXRF = true // mark for easy deletion when replacing scene xrf.scene.add(object) diff --git a/src/3rd/js/three/navigator.js b/src/3rd/js/three/navigator.js index c9339b1..8fd3e93 100644 --- a/src/3rd/js/three/navigator.js +++ b/src/3rd/js/three/navigator.js @@ -1,21 +1,22 @@ -xrf.navigator = {URL:{}} +xrf.navigator = {URI:{}} xrf.navigator.to = (url,flags,loader,data) => { if( !url ) throw 'xrf.navigator.to(..) no url given' - let URL = xrfragment.URL.toAbsolute( xrf.navigator.URL, url ) - console.dir({URL, nav: xrf.navigator.URL}) - let fileChange = URL.file && URL.file != xrf.navigator.URL.file - let hasPos = URL.hash.pos - let hashChange = String(xrf.navigator.URL.fragment||"") != String(URL.fragment||"") + let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + URI.hash = xrf.navigator.reactifyHash(URI.hash) + let fileChange = URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file + let external = URI.URN != document.location.origin + document.location.pathname + let hasPos = URI.hash.pos + let hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"") let hashbus = xrf.hashbus - xrf.navigator.URL = URL - let {directory,file,fragment,fileExt} = URL; + xrf.navigator.URI = URI + let {directory,file,fragment,fileExt} = URI; + console.dir({URI, nav: xrf.navigator.URI}) - debugger const evalFragment = () => { - if( URL.fragment ){ - hashbus.pub( URL.fragment, xrf.model, flags ) // eval local URI XR fragments + if( URI.fragment ){ + hashbus.pub( URI.fragment, xrf.model, flags ) // eval local URI XR fragments xrf.navigator.updateHash(fragment) // which don't require } } @@ -25,16 +26,16 @@ xrf.navigator.to = (url,flags,loader,data) => { .emit('navigate', {url,loader,data}) .then( () => { - console.log("URN: "+URL.URN) + const Loader = xrf.loaders[fileExt] + if( fileExt && !loader ){ - const Loader = xrf.loaders[fileExt] if( !Loader ) return resolve() - loader = loader || new Loader().setPath( URL.URN ) + loader = loader || new Loader().setPath( URI.URN ) } - if( !URL.fragment && !URL.file && !URL.fileExt ) return resolve(xrf.model) // nothing we can do here + if( !URI.fragment && !URI.file && !URI.fileExt ) return resolve(xrf.model) // nothing we can do here - if( xrf.model && hashChange && !hasPos ){ + if( xrf.model && !fileChange && hashChange && !hasPos ){ evalFragment() return resolve(xrf.model) // positional navigation } @@ -42,7 +43,7 @@ xrf.navigator.to = (url,flags,loader,data) => { xrf .emit('navigateLoading', {url,loader,data}) .then( () => { - if( !fileChange && hashChange && hasPos ){ // we're already loaded + if( (!fileChange || !file) && hashChange && hasPos ){ // we're already loaded evalFragment() xrf.emit('navigateLoaded',{url}) return resolve(xrf.model) @@ -55,16 +56,15 @@ xrf.navigator.to = (url,flags,loader,data) => { // force relative path for files which dont include protocol or relative path if( directory ) directory = directory[0] == '.' || directory.match("://") ? directory : `.${directory}` - loader = loader || new Loader().setPath( URL.URN ) + loader = loader || new Loader().setPath( URI.URN ) const onLoad = (model) => { - model.file = URL.file + model.file = URI.file // only change url when loading *another* file if( xrf.model ){ - let path = URL.directory != document.location.pathname ? URL.directory : ''; - xrf.navigator.pushState( `${path}${URL.file}`, fragment ) + xrf.navigator.pushState( external ? URI.URN + URI.file : URI.file, fragment ) } - //if( xrf.model ) xrf.navigator.pushState( `${ document.location.pathname != URL.directory ? URL.directory: ''}${URL.file}`, fragment ) + //if( xrf.model ) xrf.navigator.pushState( `${ document.location.pathname != URI.directory ? URI.directory: ''}${URI.file}`, fragment ) xrf.model = model if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model @@ -75,13 +75,12 @@ xrf.navigator.to = (url,flags,loader,data) => { xrf.XRWG.generate({model,scene:model.scene}) // spec: 2. init metadata inside model for non-SRC data - if( !model.isSRC ){ + if( !model.isSRC ){ model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) ) } - // spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view) xrf.frag.defaultPredefinedViews({model,scene:model.scene}) - // spec: predefined view(s) & objects-of-interest-in-XRWG from URL (https://xrfragment.org/#predefined_view) + // spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view) let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments xrf.add( model.scene ) @@ -108,7 +107,7 @@ xrf.navigator.to = (url,flags,loader,data) => { xrf.navigator.init = () => { if( xrf.navigator.init.inited ) return - xrf.navigator.URL = xrfragment.URL.parse(document.location.href) + xrf.navigator.URI = xrfragment.URI.parse(document.location.href) window.addEventListener('popstate', function (event){ if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion) @@ -138,10 +137,10 @@ xrf.navigator.setupNavigateFallbacks = () => { xrf.addEventListener('navigate', (opts) => { let {url} = opts - let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) + let {fileExt} = xrfragment.URI.parse(url) // handle http links - if( url.match(/^http/) && !xrf.loaders[ext] ){ + if( url.match(/^http/) && !xrf.loaders[fileExt] ){ let inIframe try { inIframe = window.self !== window.top; } catch (e) { inIframe = true; } return inIframe ? window.parent.postMessage({ url }, '*') : window.open( url, '_blank') @@ -161,7 +160,7 @@ xrf.navigator.setupNavigateFallbacks = () => { xrf.navigator.updateHash = (hash,opts) => { if( hash.replace(/^#/,'') == document.location.hash.substr(1) || hash.match(/\|/) ) return // skip unnecesary pushState triggers - console.log(`URL: ${document.location.search.substr(1)}#${hash}`) + console.log(`URI: ${document.location.search.substr(1)}#${hash}`) xrf.navigator.updateHash.active = true // important to prevent recursion document.location.hash = hash xrf.navigator.updateHash.active = false @@ -172,3 +171,20 @@ xrf.navigator.pushState = (file,hash) => { window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` ) xrf.emit('pushState', {file, hash} ) } + +xrf.navigator.reactifyHash = ( obj ) => { + return new Proxy(obj,{ + get(me,k) { return me[k] }, + set(me,k,v){ + me[k] = v + xrf.navigator.to( "#" + this.toString(me) ) + }, + toString(me){ + let parts = [] + Object.keys(me).map( (k) => { + parts.push( me[k] ? `${k}=${encodeURIComponent(me[k])}` : k ) + }) + return parts.join('&') + } + }) +} diff --git a/src/3rd/js/three/xrf/href.js b/src/3rd/js/three/xrf/href.js index 3f83494..dd1c618 100644 --- a/src/3rd/js/three/xrf/href.js +++ b/src/3rd/js/three/xrf/href.js @@ -49,7 +49,6 @@ xrf.frag.href = function(v, opts){ .emit('href',{click:true,mesh,xrf:v}) // let all listeners agree .then( () => { - let {urlObj,dir,file,hash,ext} = xrf.parseUrl(v.string) const isLocal = v.string[0] == '#' const hasPos = isLocal && v.string.match(/pos=/) const flags = isLocal ? xrf.XRF.PV_OVERRIDE : undefined diff --git a/src/3rd/js/three/xrf/src.js b/src/3rd/js/three/xrf/src.js index ab8aa5d..f26780e 100644 --- a/src/3rd/js/three/xrf/src.js +++ b/src/3rd/js/three/xrf/src.js @@ -7,7 +7,7 @@ xrf.frag.src = function(v, opts){ if( mesh.isSRC ) return // only embed src once let url = xrf.frag.src.expandURI( mesh, v.string ) - let srcFrag = opts.srcFrag = xrfragment.URI.parse(url) + let srcFrag = opts.srcFrag = xrfragment.URI.parse(url).XRF opts.isLocal = v.string[0] == '#' opts.isPortal = xrf.frag.src.renderAsPortal(mesh) opts.isSRC = mesh.isSRC = true diff --git a/src/3rd/js/three/xrf/src/audio.js b/src/3rd/js/three/xrf/src/audio.js index 563b6be..645e581 100644 --- a/src/3rd/js/three/xrf/src/audio.js +++ b/src/3rd/js/three/xrf/src/audio.js @@ -8,8 +8,8 @@ let loadAudio = (mimetype) => function(url,opts){ let {mesh,src,camera,THREE} = opts - let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) - let frag = xrf.URI.parse( url ) + let URL = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + let frag = URL.XRF xrf.init.audio() let isPositionalAudio = !(mesh.position.x == 0 && mesh.position.y == 0 && mesh.position.z == 0) @@ -20,7 +20,7 @@ let loadAudio = (mimetype) => function(url,opts){ mesh.media = mesh.media || {} mesh.media.audio = { set: (mediafragment,v) => mesh.media.audio[mediafragment] = v } - let finalUrl = url.replace(/#.*/,'') + let finalUrl = URL.URN + URL.file if( xrf.debug != undefined ) console.log("GET "+finalUrl) audioLoader.load( finalUrl, function( buffer ) { diff --git a/src/3rd/js/three/xrf/src/fbx.js b/src/3rd/js/three/xrf/src/fbx.js index 42516f5..e636d51 100644 --- a/src/3rd/js/three/xrf/src/fbx.js +++ b/src/3rd/js/three/xrf/src/fbx.js @@ -5,7 +5,8 @@ xrf.frag.src.type['fbx'] = function( url, opts ){ return new Promise( async (resolve,reject) => { let {mesh,src} = opts - let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) + let URL = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + let frag = URL.XRF let loader let {THREE} = await import('https://unpkg.com/three@0.161.0/build/three.module.js') diff --git a/src/3rd/js/three/xrf/src/glsl.js b/src/3rd/js/three/xrf/src/glsl.js index 885d755..52c6940 100644 --- a/src/3rd/js/three/xrf/src/glsl.js +++ b/src/3rd/js/three/xrf/src/glsl.js @@ -5,14 +5,17 @@ xrf.frag.src.type['x-shader/x-fragment'] = function(url,opts){ let {mesh,THREE} = opts + let URL = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + let frag = URL.XRF + let isFragmentShader = /\.(fs|frag|glsl)$/ let isVertexShader = /\.(vs|vert)$/ let shaderReqs = [] let shaderCode = {} let shader = { - fragment: { code: '', url: url.match( isFragmentShader ) ? url : '' }, - vertex: { code: '', url: url.match( isVertexShader ) ? url : '' } + fragment: { code: '', url: url.match( isFragmentShader ) ? URL.URN + URL.file : '' }, + vertex: { code: '', url: url.match( isVertexShader ) ? URL.URN + URL.file : '' } } var onShaderLoaded = ((args) => (type, status, code) => { diff --git a/src/3rd/js/three/xrf/src/gltf.js b/src/3rd/js/three/xrf/src/gltf.js index 83ba3d5..9a6e728 100644 --- a/src/3rd/js/three/xrf/src/gltf.js +++ b/src/3rd/js/three/xrf/src/gltf.js @@ -5,19 +5,15 @@ xrf.frag.src.type['gltf'] = function( url, opts ){ return new Promise( (resolve,reject) => { let {mesh,src} = opts - let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) + let URL = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + let {directory,file,fileExt,URN} = URL; let loader - const Loader = xrf.loaders[ext] + const Loader = xrf.loaders[fileExt] if( !Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .'+ext - if( !dir.match("://") ){ // force relative path - dir = dir.substr(0,2) == './' ? dir : `./${dir}` - loader = new Loader().setPath( dir ) - }else{ - loader = new Loader() - } + loader = new Loader().setPath( URN ) - loader.load(url, (model) => { + loader.load(file, (model) => { model.isSRC = true resolve(model) }) diff --git a/src/3rd/js/three/xrf/src/html.js b/src/3rd/js/three/xrf/src/html.js index 08d3800..c37d066 100644 --- a/src/3rd/js/three/xrf/src/html.js +++ b/src/3rd/js/three/xrf/src/html.js @@ -2,7 +2,7 @@ let loadHTML = (mimetype) => function(url,opts){ let {mesh,src,camera} = opts let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) - let frag = xrf.URI.parse( url ) + let frag = xrf.URI.parse( url ).XRF console.warn("todo: html viewer for src not implemented") } diff --git a/src/3rd/js/three/xrf/src/image.js b/src/3rd/js/three/xrf/src/image.js index a87e135..92e3931 100644 --- a/src/3rd/js/three/xrf/src/image.js +++ b/src/3rd/js/three/xrf/src/image.js @@ -7,6 +7,8 @@ xrf.frag.src.type['image/png'] = function(url,opts){ let {mesh,THREE} = opts let restrictTo3DBoundingBox = mesh.geometry + let URL = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + let frag = URL.XRF mesh.material = new xrf.THREE.MeshBasicMaterial({ map: null, @@ -50,7 +52,7 @@ xrf.frag.src.type['image/png'] = function(url,opts){ renderImage(texture) } - new THREE.TextureLoader().load( url, onLoad, null, console.error ); + new THREE.TextureLoader().load( URL.URN + URL.file, onLoad, null, console.error ); } diff --git a/src/3rd/js/three/xrf/src/video.js b/src/3rd/js/three/xrf/src/video.js index 14cfdb1..6a99ed1 100644 --- a/src/3rd/js/three/xrf/src/video.js +++ b/src/3rd/js/three/xrf/src/video.js @@ -1,9 +1,9 @@ let loadVideo = (mimetype) => function(url,opts){ let {mesh,src,camera} = opts - let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) const THREE = xrf.THREE - let frag = xrf.URI.parse( url ) + let URL = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) + let frag = URL.XRF mesh.media = mesh.media || {} @@ -25,7 +25,7 @@ let loadVideo = (mimetype) => function(url,opts){ },false) }) - video.src = url + video.src = URL.URN + URL.file video.speed = 1.0 video.looping = false video.set = (mediafragment,v) => { diff --git a/src/Test.hx b/src/Test.hx index 59990fd..996f0b2 100644 --- a/src/Test.hx +++ b/src/Test.hx @@ -1,6 +1,5 @@ import xrfragment.Filter; import xrfragment.URI; -import xrfragment.URL; import xrfragment.XRF; class Spec { @@ -14,7 +13,7 @@ class Spec { class Test { static var errors:Int = 0; - static var browser : xrfragment.URL = null; + static var browser : xrfragment.URI = null; static public function main():Void { test( "url.json", Spec.load("src/spec/url.json") ); @@ -36,7 +35,7 @@ class Test { var valid:Bool = false; var item:Dynamic = spec[i]; f = new Filter(item.data); - res = URI.parse(item.data,null); + res = URI.parseFragment(item.data,null); 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] ); @@ -44,8 +43,8 @@ class Test { 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) ; - if( item.expect.fn == "testBrowserOverride" ) valid = item.expect.out == (URI.parse(item.data,XRF.NAVIGATOR)).exists(item.expect.input); - if( item.expect.fn == "testEmbedOverride" ) valid = item.expect.out == (URI.parse(item.data,XRF.METADATA)).exists(item.expect.input); + if( item.expect.fn == "testBrowserOverride" ) valid = item.expect.out == (URI.parseFragment(item.data,XRF.NAVIGATOR)).exists(item.expect.input); + if( item.expect.fn == "testEmbedOverride" ) valid = item.expect.out == (URI.parseFragment(item.data,XRF.METADATA)).exists(item.expect.input); if( item.expect.fn == "testURL" ) valid = testURL( item.data, item.expect.input, item.expect.out, false ); if( item.expect.fn == "testURLHash" ) valid = testURL( item.data, item.expect.input, item.expect.out, false ); if( item.expect.fn == "testURLBrowse" ) valid = testURL( item.data, item.expect.input, item.expect.out, true ); @@ -87,11 +86,11 @@ class Test { } static public function testURL( _url:String, attr:String, output:String, browserMode: Bool = false): Bool { - var URL = xrfragment.URL; - var url:URL = URL.parse(_url); + var URI = xrfragment.URI; + var url:URI = URI.parse(_url,0); if( browserMode ){ if( browser == null ) browser = url; - url = browser = URL.toAbsolute( browser, _url ); + url = browser = URI.toAbsolute( browser, _url ); } var parts:Array = attr.split("."); if( parts.length > 1 && parts[0] == "hash" && url.hash.exists( parts[1]) ){ diff --git a/src/xrfragment/URI.hx b/src/xrfragment/URI.hx index d251ea2..cfbc706 100644 --- a/src/xrfragment/URI.hx +++ b/src/xrfragment/URI.hx @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 Leon van Kammen/NLNET + +/* + * various snippets originate from: + * + * http://haxe.org/doc/snip/uri_parser, + * https://github.com/haxecocktail/cocktail-url/blob/master/cocktail/url/URI.hx + */ + package xrfragment; import xrfragment.Parser; @@ -27,10 +35,44 @@ import xrfragment.XRF; @:keep // <- avoids accidental removal by dead code elimination class URI { - public static var fragment:haxe.DynamicAccess; + /** + * URI parts names + */ + private static var _parts : Array = ["source", "scheme", "authority", "userInfo", "user", + "password","host","port","relative","path","directory","file","query","fragment"]; + + /** + * URI parts + */ + public var url : String; + public var source : String; + public var scheme : String; + public var authority : String; + public var userInfo : String; + public var user : String; + public var password : String; + public var host : String; + public var port : String; + public var relative : String; + public var path : String; + public var directory : String; + public var file : String; + public var fileExt : String; + public var query : String; + public var fragment : String = ""; + public var hash : haxe.DynamicAccess = {}; + public var XRF : haxe.DynamicAccess = {}; + public var URN : String; + + /** + * class constructor + */ + public function new( ) + { + } @:keep - public static function parse(url:String,filter:Int):haxe.DynamicAccess { + public static function parseFragment(url:String,filter:Int):haxe.DynamicAccess { var store:haxe.DynamicAccess = {}; // 1. store key/values into a associative array or dynamic object if( url == null || url.indexOf("#") == -1 ) return store; var fragment:Array = url.split("#"); // 1. fragment URI starts with `#` @@ -71,6 +113,381 @@ class URI { return parts.join("#"); } + /** + * Parse a string url and return a typed + * object from it. + * + * note : implementation originate from here : + * http://haxe.org/doc/snip/uri_parser + */ + public static function parse(stringUrl:String, flags:Int ):URI + { + // The almighty regexp (courtesy of http://blog.stevenlevithan.com/archives/parseuri) + var r : EReg = ~/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + + if( stringUrl.indexOf("://") == -1 && stringUrl.charAt(0) != '/' ){ + stringUrl = "/" + stringUrl; // workaround for relative urls + } + + // Match the regexp to the url + r.match(stringUrl); + + var url:URI = new URI(); + + // Use reflection to set each part + for (i in 0..._parts.length) + { + Reflect.setField(url, _parts[i], r.matched(i)); + } + + //hack for relative url with only a file + if (isRelative(url) == true) + { + if (url.directory == null && url.host != null) + { + url.file = url.host; + } + } + + url.hash = {}; + if( url.fragment != null && url.fragment.length > 0 ){ + url.XRF = xrfragment.URI.parseFragment( "#"+url.fragment, flags ); + var key:String; + for( key in url.XRF.keys() ){ + var v:haxe.DynamicAccess = url.XRF.get(key); + url.hash[key] = v.get("string"); + } + } + + computeVars(url); + + return url; + } + + private static function computeVars( url:URI ) { + // clean up url + var r = ~/\/\//g; + if( url.directory != null && url.directory.indexOf("//") != -1 ){ + url.directory = r.replace(url.directory,"/"); + } + if( url.path != null && url.path.indexOf("//") != -1 ){ + url.path = r.replace(url.path,"/"); + } + if( url.file != null && url.file.indexOf("//") != -1 ){ + url.file = r.replace(url.file,"/"); + } + // generate URN + url.URN = url.scheme + "://" + url.host; + if( url.port != null ) url.URN += ":"+url.port; + url.URN += url.directory; + + // extract file extension if any + if( url.file != null){ + var parts:Array = url.file.split("."); + if( parts.length > 1 ){ + url.fileExt = parts.pop(); + } + } + + } + + /** + * Serialize an URl OBJect into an + * URI string + */ + public static function toString(url:URI):String + { + var result:String = ""; + + if (url.scheme != null) + { + result += url.scheme + "://"; + } + + if (url.user != null) + { + result += url.user + ":"; + } + + if (url.password != null) + { + result += url.password + "@"; + } + + if (url.host != null) + { + result += url.host; + } + + if (url.port != null) + { + result += ":" + url.port; + } + + if (url.directory != null) + { + result += url.directory; + } + + if (url.file != null) + { + result += url.file; + } + + if (url.query != null) + { + result += "?" + url.query; + } + + if (url.fragment != null) + { + result += "#" + url.fragment; + } + + return result; + } + + /** + * takes 2 urls and return a new url which is the result + * of appending the second url to the first. + * + * if the first url points to a file, the file is removed + * and the appended url is added after the last directory + * + * only the query string and fragment of the appended url are used + */ + public static function appendURI(url:URI, appendedURI:URI):URI + { + if (isRelative(url) == true) + { + return appendToRelativeURI(url, appendedURI); + } + else + { + return appendToAbsoluteURI(url, appendedURI); + } + } + + /** + * return wether the url is relative (true) + * or absolute (false) + */ + public static function isRelative(url:URI):Bool + { + return url.scheme == null; + } + + /** + * append the appended url to a relative url + */ + public static function appendToRelativeURI(url:URI, appendedURI:URI):URI + { + //when relative url parsed, if it contains only a file (ex : "style.css") + //then it will store it in the host attribute. So if the url has no directory + //then only the appended url content is returned, as this method replace the file + //part of the base url anyway + if (url.directory == null || url.host == null) + { + return cloneURI(appendedURI); + } + + var resultURI:URI = new URI(); + resultURI.host = url.host; + resultURI.directory = url.directory; + + if (appendedURI.host != null) + { + resultURI.directory += appendedURI.host; + } + + if (appendedURI.directory != null) + { + var directory = appendedURI.directory; + if (appendedURI.host == null) + { + //remove the initial '/' char if no host, as already present + //in base url + resultURI.directory += directory.substr(1); + } + else + { + resultURI.directory += directory; + } + + } + + if (appendedURI.file != null) + { + resultURI.file = appendedURI.file; + } + + resultURI.path = resultURI.directory + resultURI.file; + + if (appendedURI.query != null) + { + resultURI.query = appendedURI.query; + } + + if (appendedURI.fragment != null) + { + resultURI.fragment = appendedURI.fragment; + } + + return resultURI; + } + + /** + * append the appended url to an absolute url + */ + public static function appendToAbsoluteURI(url:URI, appendedURI:URI):URI + { + var resultURI:URI = new URI(); + + if (url.scheme != null) + { + resultURI.scheme = url.scheme; + } + + if (url.host != null) + { + resultURI.host = url.host; + } + + var directory:String = ""; + if (url.directory != null) + { + directory = url.directory; + } + + if (appendedURI.host != null) + { + appendedURI.directory += appendedURI.host; + } + + if (appendedURI.directory != null) + { + directory += appendedURI.directory; + } + + resultURI.directory = directory; + + if (appendedURI.file != null) + { + resultURI.file = appendedURI.file; + } + + resultURI.path = resultURI.directory + resultURI.file; + + if (appendedURI.query != null) + { + resultURI.query = appendedURI.query; + } + + if (appendedURI.fragment != null) + { + resultURI.fragment = appendedURI.fragment; + } + + return resultURI; + } + + /** + * append the appended url to an absolute url + */ + public static function toAbsolute(url:URI, newUrl:String ):URI + { + var newURI:URI = parse(newUrl,0); + var resultURI:URI = new URI(); + + resultURI.port = url.port; + + if (newURI.scheme != null) + { + resultURI.scheme = newURI.scheme; + }else{ + resultURI.scheme = url.scheme; + } + + if (newURI.host != null && newURI.host.length > 0 ) + { + trace("host: "+newURI.host); + resultURI.host = newURI.host; + resultURI.port = null; + resultURI.fragment = null; + resultURI.hash = {}; + resultURI.XRF = {}; + if( newURI.port != null ){ + resultURI.port = newURI.port; + } + }else{ + resultURI.host = url.host; + } + + var directory:String = ""; + if (url.directory != null) + { + directory = url.directory; + } + + if (newURI.directory != null) + { + if( newUrl.charAt(0) != '/' && newUrl.indexOf("://") == -1 ){ + directory += newURI.directory; + }else{ + directory = newURI.directory; + } + } + + resultURI.directory = directory; + + if (newURI.file != null) + { + resultURI.file = newURI.file; + } + + resultURI.path = resultURI.directory + resultURI.file; + + if (newURI.query != null) + { + resultURI.query = newURI.query; + } + + if (newURI.fragment != null) + { + resultURI.fragment = newURI.fragment; + } + resultURI.hash = newURI.hash; + resultURI.XRF = newURI.XRF; + computeVars(resultURI); + + return resultURI; + } + + /** + * clone the provided url + */ + private static function cloneURI(url:URI):URI + { + var clonedURI:URI = new URI(); + + clonedURI.url = url.url; + clonedURI.source = url.source; + clonedURI.scheme = url.scheme; + clonedURI.authority = url.authority; + clonedURI.userInfo = url.userInfo; + clonedURI.password = url.password; + clonedURI.host = url.host; + clonedURI.port = url.port; + clonedURI.relative = url.relative; + clonedURI.path = url.path; + clonedURI.directory = url.directory; + clonedURI.file = url.file; + clonedURI.query = url.query; + clonedURI.fragment = url.fragment; + + return clonedURI; + } + } /** diff --git a/src/xrfragment/URL.hx b/src/xrfragment/URL.hx deleted file mode 100644 index 5772f0e..0000000 --- a/src/xrfragment/URL.hx +++ /dev/null @@ -1,436 +0,0 @@ -package xrfragment; - -import xrfragment.URI; - -/* - * Cocktail, HTML rendering engine - * http://haxe.org/com/libs/cocktail - * - * Copyright (c) Silex Labs - * Cocktail is available under the MIT license - * http://www.silexlabs.org/labs/cocktail-licensing/ - * https://github.com/haxecocktail/cocktail-url/blob/master/cocktail/url/URL.hx - */ - -/** - * Parse and serialize an URL. - * - * note : parts of the implementation originate from - * here : http://haxe.org/doc/snip/uri_parser, - * some part of the URL have been renamed to match - * w3c spec - * - * @author Yannick Dominguez - */ -@:expose // <- makes the class reachable from plain JavaScript -@:keep // <- avoids accidental removal by dead code elimination -class URL -{ - /** - * URL parts names - */ - private static var _parts : Array = ["source", "scheme", "authority", "userInfo", "user", - "password","host","port","relative","path","directory","file","query","fragment"]; - - /** - * URL parts - */ - public var url : String; - public var source : String; - public var scheme : String; - public var authority : String; - public var userInfo : String; - public var user : String; - public var password : String; - public var host : String; - public var port : String; - public var relative : String; - public var path : String; - public var directory : String; - public var file : String; - public var fileExt : String; - public var query : String; - public var fragment : String; - public var hash : haxe.DynamicAccess = {}; - public var XRF : haxe.DynamicAccess = {}; - public var URN : String; - - /** - * class constructor - */ - public function new( ) - { - } - - /** - * Parse a string url and return a typed - * object from it. - * - * note : implementation originate from here : - * http://haxe.org/doc/snip/uri_parser - */ - public static function parse(stringUrl:String ):URL - { - // The almighty regexp (courtesy of http://blog.stevenlevithan.com/archives/parseuri) - var r : EReg = ~/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - - if( stringUrl.indexOf("://") == -1 && stringUrl.charAt(0) != '/' ){ - stringUrl = "/" + stringUrl; // workaround for relative urls - } - - // Match the regexp to the url - r.match(stringUrl); - - var url:URL = new URL(); - - // Use reflection to set each part - for (i in 0..._parts.length) - { - Reflect.setField(url, _parts[i], r.matched(i)); - } - - //hack for relative url with only a file - if (isRelative(url) == true) - { - if (url.directory == null && url.host != null) - { - url.file = url.host; - } - } - - url.hash = {}; - if( url.fragment != null && url.fragment.length > 0 ){ - url.XRF = xrfragment.URI.parse( "#"+url.fragment, 0 ); - var key:String; - for( key in url.XRF.keys() ){ - var v:haxe.DynamicAccess = url.XRF.get(key); - url.hash[key] = v.get("string"); - } - } - - computeVars(url); - - return url; - } - - private static function computeVars( url:URL ) { - // clean up url - var r = ~/\/\//g; - if( url.directory != null && url.directory.indexOf("//") != -1 ){ - url.directory = r.replace(url.directory,"/"); - } - if( url.path != null && url.path.indexOf("//") != -1 ){ - url.path = r.replace(url.path,"/"); - } - if( url.file != null && url.file.indexOf("//") != -1 ){ - url.file = r.replace(url.file,"/"); - } - // generate URN - url.URN = url.scheme + "://" + url.host; - if( url.port != null ) url.URN += ":"+url.port; - url.URN += url.directory; - - // extract file extension if any - if( url.file != null){ - var parts:Array = url.file.split("."); - if( parts.length > 1 ){ - url.fileExt = parts.pop(); - } - } - - } - - /** - * Serialize an URl OBJect into an - * URL string - */ - public static function toString(url:URL):String - { - var result:String = ""; - - if (url.scheme != null) - { - result += url.scheme + "://"; - } - - if (url.user != null) - { - result += url.user + ":"; - } - - if (url.password != null) - { - result += url.password + "@"; - } - - if (url.host != null) - { - result += url.host; - } - - if (url.port != null) - { - result += ":" + url.port; - } - - if (url.directory != null) - { - result += url.directory; - } - - if (url.file != null) - { - result += url.file; - } - - if (url.query != null) - { - result += "?" + url.query; - } - - if (url.fragment != null) - { - result += "#" + url.fragment; - } - - return result; - } - - /** - * takes 2 urls and return a new url which is the result - * of appending the second url to the first. - * - * if the first url points to a file, the file is removed - * and the appended url is added after the last directory - * - * only the query string and fragment of the appended url are used - */ - public static function appendURL(url:URL, appendedURL:URL):URL - { - if (isRelative(url) == true) - { - return appendToRelativeURL(url, appendedURL); - } - else - { - return appendToAbsoluteURL(url, appendedURL); - } - } - - /** - * return wether the url is relative (true) - * or absolute (false) - */ - public static function isRelative(url:URL):Bool - { - return url.scheme == null; - } - - /** - * append the appended url to a relative url - */ - public static function appendToRelativeURL(url:URL, appendedURL:URL):URL - { - //when relative url parsed, if it contains only a file (ex : "style.css") - //then it will store it in the host attribute. So if the url has no directory - //then only the appended url content is returned, as this method replace the file - //part of the base url anyway - if (url.directory == null || url.host == null) - { - return cloneURL(appendedURL); - } - - var resultURL:URL = new URL(); - resultURL.host = url.host; - resultURL.directory = url.directory; - - if (appendedURL.host != null) - { - resultURL.directory += appendedURL.host; - } - - if (appendedURL.directory != null) - { - var directory = appendedURL.directory; - if (appendedURL.host == null) - { - //remove the initial '/' char if no host, as already present - //in base url - resultURL.directory += directory.substr(1); - } - else - { - resultURL.directory += directory; - } - - } - - if (appendedURL.file != null) - { - resultURL.file = appendedURL.file; - } - - resultURL.path = resultURL.directory + resultURL.file; - - if (appendedURL.query != null) - { - resultURL.query = appendedURL.query; - } - - if (appendedURL.fragment != null) - { - resultURL.fragment = appendedURL.fragment; - } - - return resultURL; - } - - /** - * append the appended url to an absolute url - */ - public static function appendToAbsoluteURL(url:URL, appendedURL:URL):URL - { - var resultURL:URL = new URL(); - - if (url.scheme != null) - { - resultURL.scheme = url.scheme; - } - - if (url.host != null) - { - resultURL.host = url.host; - } - - var directory:String = ""; - if (url.directory != null) - { - directory = url.directory; - } - - if (appendedURL.host != null) - { - appendedURL.directory += appendedURL.host; - } - - if (appendedURL.directory != null) - { - directory += appendedURL.directory; - } - - resultURL.directory = directory; - - if (appendedURL.file != null) - { - resultURL.file = appendedURL.file; - } - - resultURL.path = resultURL.directory + resultURL.file; - - if (appendedURL.query != null) - { - resultURL.query = appendedURL.query; - } - - if (appendedURL.fragment != null) - { - resultURL.fragment = appendedURL.fragment; - } - - return resultURL; - } - - /** - * append the appended url to an absolute url - */ - public static function toAbsolute(url:URL, newUrl:String ):URL - { - var newURL:URL = parse(newUrl); - var resultURL:URL = new URL(); - - resultURL.port = url.port; - - if (newURL.scheme != null) - { - resultURL.scheme = newURL.scheme; - }else{ - resultURL.scheme = url.scheme; - } - - if (newURL.host != null && newURL.host.length > 0 ) - { - trace("host: "+newURL.host); - resultURL.host = newURL.host; - resultURL.port = null; - if( newURL.port != null ){ - resultURL.port = newURL.port; - } - }else{ - resultURL.host = url.host; - } - - var directory:String = ""; - if (url.directory != null) - { - directory = url.directory; - } - - if (newURL.directory != null) - { - if( newUrl.charAt(0) != '/' && newUrl.indexOf("://") == -1 ){ - directory += newURL.directory; - }else{ - directory = newURL.directory; - } - } - - resultURL.directory = directory; - - if (newURL.file != null) - { - resultURL.file = newURL.file; - } - - resultURL.path = resultURL.directory + resultURL.file; - - if (newURL.query != null) - { - resultURL.query = newURL.query; - } - - if (newURL.fragment != null) - { - resultURL.fragment = newURL.fragment; - } - resultURL.hash = newURL.hash; - resultURL.XRF = newURL.XRF; - computeVars(resultURL); - - return resultURL; - } - - /** - * clone the provided url - */ - private static function cloneURL(url:URL):URL - { - var clonedURL:URL = new URL(); - - clonedURL.url = url.url; - clonedURL.source = url.source; - clonedURL.scheme = url.scheme; - clonedURL.authority = url.authority; - clonedURL.userInfo = url.userInfo; - clonedURL.password = url.password; - clonedURL.host = url.host; - clonedURL.port = url.port; - clonedURL.relative = url.relative; - clonedURL.path = url.path; - clonedURL.directory = url.directory; - clonedURL.file = url.file; - clonedURL.query = url.query; - clonedURL.fragment = url.fragment; - - return clonedURL; - } -}