From 36e48a521d84b1dc5e6acb92d0f6d4cc712a2216 Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Fri, 29 Mar 2024 17:36:48 +0000 Subject: [PATCH] work in progress [might break] --- src/Test.hx | 14 +- src/spec/url.json | 3 +- src/xrfragment/URI.hx | 3 + src/xrfragment/URL.hx | 328 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+), 6 deletions(-) create mode 100644 src/xrfragment/URL.hx diff --git a/src/Test.hx b/src/Test.hx index 6f733e3..974c956 100644 --- a/src/Test.hx +++ b/src/Test.hx @@ -1,5 +1,6 @@ import xrfragment.Filter; import xrfragment.URI; +import xrfragment.URL; import xrfragment.XRF; class Spec { @@ -44,6 +45,8 @@ class Test { 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 == "testURL" ) testURL( item.data, item.expect.input, item.expect.out ); + if( item.expect.fn == "equal.string" ) valid = res.get(item.expect.input) && item.expect.out == res.get(item.expect.input).string; if( item.expect.fn == "equal.x" ) valid = equalX(res,item); if( item.expect.fn == "equal.xy" ) valid = equalXY(res,item); @@ -53,6 +56,7 @@ class Test { if( item.expect.fn == "equal.mediafragmentS") valid = equalMediaFragment(res,item,"s"); if( item.expect.fn == "testFilterRoot" ) valid = res.exists(item.expect.input[0]) && res.get(item.expect.input[0]).filter.get().root == item.expect.out; if( item.expect.fn == "testFilterDeep" ) valid = res.exists(item.expect.input[0]) && res.get(item.expect.input[0]).filter.get().deep == item.expect.out; + var ok:String = valid ? "[ ✔ ] " : "[ ❌] "; trace( ok + item.fn + ": '" + item.data + "'" + (item.label ? " (" + (item.label?item.label:item.expect.fn) +")" : "")); if( !valid ) errors += 1; @@ -79,11 +83,11 @@ class Test { else return res.get( key ).floats[ Std.parseInt(item.expect.input) ] == Std.parseFloat(item.expect.out); } - static public function testUrl():Void { - var Uri = xrfragment.URI; - var url:String = "http://foo.com?foo=1#bar=flop&a=1,2&b=c|d|1,2,3"; - trace(url); - trace( Uri.parse(url,0) ); + static public function testURL( url:String, attr:String, output:String): Bool { + var URL = xrfragment.URL; + var url = URL.parse(url,false); + if( attr == 'scheme' && url.scheme == output ) return true; + return false; } static public function testFilter():Void { diff --git a/src/spec/url.json b/src/spec/url.json index 95672b3..12fa3f0 100644 --- a/src/spec/url.json +++ b/src/spec/url.json @@ -2,5 +2,6 @@ {"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)"}, - {"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testParsed", "input":"mycustom","out":true},"label":"test custom property"} + {"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testParsed", "input":"mycustom","out":true},"label":"test custom property"}, + {"fn":"url","data":"http://foo.com?foo=1#mycustom=foo", "expect":{ "fn":"testURL", "input":"scheme","out":"https://"},"label":"test URL scheme"} ] diff --git a/src/xrfragment/URI.hx b/src/xrfragment/URI.hx index f14d3e7..d251ea2 100644 --- a/src/xrfragment/URI.hx +++ b/src/xrfragment/URI.hx @@ -26,6 +26,9 @@ import xrfragment.XRF; @:expose // <- makes the class reachable from plain JavaScript @:keep // <- avoids accidental removal by dead code elimination class URI { + + public static var fragment:haxe.DynamicAccess; + @:keep public static function parse(url:String,filter:Int):haxe.DynamicAccess { var store:haxe.DynamicAccess = {}; // 1. store key/values into a associative array or dynamic object diff --git a/src/xrfragment/URL.hx b/src/xrfragment/URL.hx new file mode 100644 index 0000000..288a900 --- /dev/null +++ b/src/xrfragment/URL.hx @@ -0,0 +1,328 @@ +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 + */ +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 query : String; + public var browserMode: Bool; + public var fragment : String; + public var hash : haxe.DynamicAccess; + + /** + * 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, browserMode:Bool ):URL + { + + // The almighty regexp (courtesy of http://blog.stevenlevithan.com/archives/parseuri) + var r : EReg = ~/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + + // Match the regexp to the url + r.match(stringUrl); + + var url:URL = new URL(); + url.browserMode = browserMode; + + // 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; + if( !url.browserMode ) url.host = null; + } + } + + if( url.fragment.length > 0 ){ + url.hash = xrfragment.URI.parse( url.fragment, 0 ); + }else url.hash = {}; + + return url; + } + + /** + * 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 + */ + private 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, url.browserMode); + } + + 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 + */ + private 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; + } + + /** + * clone the provided url + */ + private static function cloneURL(url:URL, browserMode:Bool):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.browserMode = browserMode; + clonedURL.fragment = url.fragment; + + return clonedURL; + } +}