2023-05-23 11:30:39 +02:00
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 Leon van Kammen/NLNET
2024-04-10 16:38:50 +00:00
/ *
* various snippets originate from :
*
* http : //haxe.org/doc/snip/uri_parser,
* https : //github.com/haxecocktail/cocktail-url/blob/master/cocktail/url/URI.hx
* /
2023-03-09 22:32:28 +01:00
package xrfragment ;
2023-03-31 12:58:53 +02:00
import xrfragment . Parser ;
2023-04-14 14:45:50 +02:00
import xrfragment . XRF ;
2023-03-31 12:58:53 +02:00
2023-04-02 21:19:03 +02:00
/ * *
2023-06-27 12:15:04 +02:00
* # Spec
2023-04-02 21:22:22 +02:00
*
2023-06-27 12:15:04 +02:00
* > version 1.0 .0 [ ! [ Actions S t a t u s ] ( https : //github.com/coderofsalvation/xrfragment/workflows/test/badge.svg)](https://github.com/coderofsalvation/xrfragment/actions) generated by `make doc` @ $(date +"%Y-%m-%dT%H:%M:%S%z")
*
* # # # XR Fragment URI Grammar
2023-04-06 18:27:49 +02:00
*
* ` ` `
2024-02-14 15:47:34 +00:00
* reserved = gen - delims / sub - delims / xrf - scheme
2023-04-06 18:27:49 +02:00
* gen - delims = " # " / " & "
2023-06-27 12:15:04 +02:00
* sub - delims = " , " / " = "
2024-02-14 15:47:34 +00:00
* xrf - scheme = " x r f : / / "
2023-04-06 18:27:49 +02:00
* ` ` `
2023-04-06 18:29:53 +02:00
*
2023-06-27 12:15:04 +02:00
* In c ase your programming language has no parser ( [ check h e r e ] ( https : //github.com/coderofsalvation/xrfragment/tree/main/dist)) you can [crosscompile it](https://github.com/coderofsalvation/xrfragment/blob/main/build.hxml), or roll your own `Parser.parse(k,v,store)` using the spec:
2023-04-06 18:30:36 +02:00
*
2023-04-02 21:19:03 +02:00
* /
2023-03-30 19:51:32 +02:00
@ : expose // <- makes the class reachable from plain JavaScript
@ : keep // <- avoids accidental removal by dead code elimination
2023-03-31 14:47:54 +02:00
class URI {
2024-03-29 17:36:48 +00:00
2024-04-10 16:38:50 +00:00
/ * *
* URI parts names
* /
private static var _parts : Array < String > = [ " s o u r c e " , " s c h e m e " , " a u t h o r i t y " , " u s e r I n f o " , " u s e r " ,
" p a s s w o r d " , " h o s t " , " p o r t " , " r e l a t i v e " , " p a t h " , " d i r e c t o r y " , " f i l e " , " q u e r y " , " f r a g m e n t " ] ;
/ * *
* 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 < Dynamic > = { } ;
public var XRF : haxe . DynamicAccess < Dynamic > = { } ;
public var URN : String ;
/ * *
* c lass constructor
* /
public function n e w ( )
{
}
2024-03-29 17:36:48 +00:00
2023-04-02 21:19:03 +02:00
@ : keep
2024-04-10 16:38:50 +00:00
public static function parseFragment ( url : String , filter : Int ) : haxe . DynamicAccess < Dynamic > {
2023-06-27 12:15:04 +02:00
var store: haxe . DynamicAccess < Dynamic > = { } ; // 1. store key/values into a associative array or dynamic object
2023-06-10 14:43:07 +02:00
if ( url == null || url . indexOf ( " # " ) == - 1 ) return store ;
2023-05-04 13:28:12 +02:00
var fragment: Array < String > = url . split ( " # " ) ; // 1. fragment URI starts with `#`
2023-04-02 21:19:03 +02:00
var splitArray: Array < String > = fragment [ 1 ] . split ( ' & ' ) ; // 1. fragments are split by `&`
2023-11-10 18:22:47 +01:00
for ( i in 0 ... splitArray . length ) { // 1. loop thru each fragment
2023-03-31 14:40:24 +02:00
2023-04-02 21:19:03 +02:00
var splitByEqual = splitArray [ i ] . split ( ' = ' ) ; // 1. for each fragment split on `=` to separate key/values
2023-03-31 12:58:53 +02:00
var regexPlus = ~/\+/g ; // 1. fragment-values are urlencoded (space becomes `+` using `encodeUriComponent` e.g.)
2023-03-30 19:51:32 +02:00
var key: String = splitByEqual [ 0 ] ;
2023-04-14 17:37:33 +02:00
var value: String = " " ;
2023-03-30 19:51:32 +02:00
if ( splitByEqual . length > 1 ) {
2024-02-09 18:00:22 +01:00
if ( XRF . isVector . match ( splitByEqual [ 1 ] ) ) value = splitByEqual [ 1 ] ;
e lse value = StringTools . urlDecode ( regexPlus . split ( splitByEqual [ 1 ] ) . join ( " " ) ) ;
2023-03-09 22:32:28 +01:00
}
2023-11-16 14:50:57 +01:00
var ok: Bool = Parser . parse ( key , value , store , i ) ; // 1. for every recognized fragment key/value-pair call [Parser.parse](#%E2%86%AA%20Parser.parse%28k%2Cv%2Cstore%29)
2023-03-30 19:51:32 +02:00
}
2023-05-04 13:28:12 +02:00
if ( filter != null && filter != 0 ) {
for ( key in store . keys ( ) ) {
var xrf: XRF = store . get ( key ) ;
if ( ! xrf . is ( filter ) ) {
store . remove ( key ) ;
2023-04-14 17:37:33 +02:00
}
2023-04-14 14:45:50 +02:00
}
}
2023-05-04 13:28:12 +02:00
return store ;
2023-03-09 22:32:28 +01:00
}
2024-02-08 19:40:43 +01:00
@ keep
public static function template ( uri : String , vars : Dynamic ) : String {
var parts = uri . split ( " # " ) ;
if ( parts . length == 1 ) return uri ; // we only do level1 fragment expansion
var frag = parts [ 1 ] ;
frag = StringTools . replace ( frag , " { " , " : : " ) ;
frag = StringTools . replace ( frag , " } " , " : : " ) ;
frag = new haxe . Template ( frag ) . execute ( vars ) ;
frag = StringTools . replace ( frag , " n u l l " , " " ) ; // *TODO* needs regex to check for [#&]null[&]
parts [ 1 ] = frag ;
return parts . join ( " # " ) ;
}
2024-02-14 15:47:34 +00:00
2024-04-10 16:38:50 +00:00
/ * *
* 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*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ ;
2024-07-12 17:52:26 +00:00
if ( stringUrl . indexOf ( " : / / " ) == - 1 && stringUrl . charAt ( 0 ) != ' / ' && stringUrl . charAt ( 0 ) != ' # ' ) {
2024-04-10 16:38:50 +00:00
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 ;
}
2024-04-16 18:40:49 +02:00
url . host = " " ;
2024-04-10 16:38:50 +00:00
}
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 < Dynamic > = url . XRF . get ( key ) ;
url . hash [ key ] = v . get ( " s t r i n g " ) ;
}
}
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 < String > = 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 w h i c h i s t h e r e s u l t
* of appending the second url to the first .
*
* if t h e f i r s t u r l p o i n t s t o a f i l e , t h e f i l e i s r e m o v e d
* 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 ) ;
}
e lse
{
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 ) ;
}
e lse
{
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 ;
2024-04-16 13:19:08 +00:00
resultURI . source = newUrl ;
2024-04-10 16:38:50 +00:00
if ( newURI . scheme != null )
{
resultURI . scheme = newURI . scheme ;
} e lse {
resultURI . scheme = url . scheme ;
}
if ( newURI . host != null && newURI . host . length > 0 )
{
resultURI . host = newURI . host ;
resultURI . port = null ;
resultURI . fragment = null ;
resultURI . hash = { } ;
resultURI . XRF = { } ;
if ( newURI . port != null ) {
resultURI . port = newURI . port ;
}
} e lse {
resultURI . host = url . host ;
}
var directory: String = " " ;
if ( url . directory != null )
{
directory = url . directory ;
}
2024-07-12 18:08:18 +00:00
if ( newURI . directory != null && newURI . source . charAt ( 0 ) != " # " && newURI . directory . length > 0 )
2024-04-10 16:38:50 +00:00
{
if ( newUrl . charAt ( 0 ) != ' / ' && newUrl . indexOf ( " : / / " ) == - 1 ) {
2024-07-09 17:27:21 +00:00
var stripRelative : EReg = ~/\.\/.*/ ;
directory = stripRelative . replace ( directory , ' ' ) ;
2024-04-10 16:38:50 +00:00
directory += newURI . directory ;
} e lse {
directory = newURI . directory ;
}
}
resultURI . directory = directory ;
2024-07-12 17:52:26 +00:00
2024-07-12 18:08:18 +00:00
if ( newURI . file != null && newURI . file . length > 0 )
2024-04-10 16:38:50 +00:00
{
resultURI . file = newURI . file ;
2024-04-16 18:40:49 +02:00
} e lse {
resultURI . file = url . file ;
2024-04-10 16:38:50 +00:00
}
2024-07-09 17:27:21 +00:00
2024-04-10 16:38:50 +00:00
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 ;
}
2023-03-09 22:32:28 +01:00
}
2023-06-27 12:15:04 +02:00
/ * *
* > icanhazcode ? yes , s e e [ U R I . h x ] ( h t t p s : //github.com/coderofsalvation/xrfragment/blob/main/src/xrfragment/URI.hx)
*
* # Tests
*
* the spec is tested with [ JSON u n i t t e s t s ] ( . / . . / src / spec ) consumed by [ Test . hx ] ( . / . . / src / Test . hx ) to cross - test all languages .
* /