by Duncan Guthrie
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-275@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI proposes a programming interface for working with RFC 3986 universal resource identifiers (URIs), as well as RFC 3987’s generalization to internationalized resource identifiers (IRIs). This document defines record types, normalization procedures, and conversion between URIs and IRIs. The proposal also specifies a basic programming interface for working with paths in isolation, as this is a pervasive usage of URIs and IRIs. Finally, we contribute a test suite to specify the behaviour of normalization with respect to relative references, which has in the past been a source of divergence between implementations.
none so far
RFC 3986 [1] describes an abstract syntax for uniform resource identifiers (URIs), as well as their relative references, which allow documents to be authored without knowing the final publishing location. RFC 3987 [2] defines IRIs, which generalize URIs to Unicode by defining an interpretation of percent-encoded bytes as sequences of UTF-8 octets.
URIs and IRIs are widely used to denote resources across the world wide web, and are the basis of a number of web standards. It is critical to present a programming interface for manipulation of different components in isolation (e.g. paths, hostnames), as working with an URI’s coarse string representation directly is error-prone. RFC 3986 defines an abstract syntax and hence conveniently forms the basis of such a programming interface. Further, RFC 3987’s generalization to internationalized identifiers is a natural extension, allowing us to faithfully denote resources in a number of languages, using the universal character set. Indeed, IRIs form the basis of modern standards like Resource Description Framework (RDF) [4], a widespread formal model for metadata interchange and knowledge representation. We hence require support for both URIs and IRIs.
Finally, paths are one of the most prevalent applications of URIs and IRIs. We develop paths as an object disjoint from other Scheme types with distinct segment structure, rather than adopting a string representation, because normalization of paths is a key source of divergence in URI and IRI implementations to date. This object is fleshed out in a basic path library. This library structure also allows us to directly expose procedures such as normalization and merging of base and relative paths, independently of URIs and IRIs.
RFC 3986 and 3987 (2005) are the most recent standards proposed by the IETF, but the WHATWG also publish the URL Living Standard [3]. The most dramatic change is to eschew the formal grammar defined in RFC 3986 in favour of a long-form text description of a parsing strategy. This arguably makes it difficult for implementors to verify correctness, or to select a processing strategy most appropriate to their implementation (data structures, parsing techniques). It is difficult to understand how RFC 3986 and the WHATWG specification diverge, and the WHATWG approach represents much more of a moving target than the IETF processes of deprecation. Finally, there is no successor proposal for IRIs, which remain current in recent standards such as JSON-LD (2020) [5]. These issues are discussed here [20], and the proliferation of different, competing standards are discussed here [21].
We do however incorporate one part of the WHATWG standard: escapes (percent-encodings) are referred to as percent-encoded bytes, as this terminology more clearly distinguishes the escape from the interpretation (e.g. as a UTF-8 octet).
RFC 3986 and 3987 distinguish URIs and IRIs, respectively, from their relative references, which must be resolved against a base URI or IRI in order to be used. The main application of relative references is to allow one to refer to resources and author documents without knowing the final publishing location. For example, a graph database may produce RDF documents, where resources are denoted with IRI relative references, assuming that these documents would be interchanged with another system which mints full IRIs with respect to its hosting location.
Somewhat confusingly, RFC 3986 defines “URI-reference” as the most common usage of URI: either an URI or a relative reference. We follow this common usage by developing polymorphic getters and setters which work on URIs and relative references, with the predicate uri? holding true for both URIs and relative references. This procedure would hence correspond to testing for an RFC 3986 “URI-reference”. (RFC 3987 follows an identical convention for IRIs and their relative references, so the same approach is followed for IRIs.)
The most divergent behaviour between widespread implementations of URIs and IRIs has been with respect to normalization of relative references. We explicitly avoid defining path segment normalization for relative references (regardless of whether the path is absolute or relative) because the behaviour is largely undefined by RFC 3986 and 3987 or any other current specification. See the section on normalization for more details, and the test suite.
Finally, while every URI is a valid IRI, our uri? predicate does not hold against IRIs. While wholly disjoint from each other, URIs can be converted to IRIs, and vice versa, using the uri->iri and iri->uri procedures respectively.
URIs and IRIs are structured data. Of course, record types are convenient as they generate dedicated getters and setters. More importantly, however, we think that record structures are necessary in this case for normalization procedures to be structure-preserving. Specifically, normalization procedures may alter the path, such that its segments may be mistaken for other parts of the URI or IRI, such as the <://> portion separating scheme from authority, or for the hostname. Updates to authority components or to the path need to similarly ensure that they produce valid URIs and IRIs when serialized (see below).
We argue that a string representation makes it too easy to inadvertently modify the structure, because normalization of individual components may yield a string which, when parsed again, is interpreted differently with respect to the structure. A good example of this is the restrictions on paths given an authority, because a non-empty authority is denoted in an URI using two slashes, which are characters which may also appear in paths.
A string representation of an URI or IRI (procedures uri->string and iri->string) is produced by concatenating string representations of the individual components (scheme, hostname &c.), with the expected separators between components. For efficiency, no assumption should be made that the contents of individual components can be checked at this point, which typically would involve additional, redundant parsing.
We require implementations to provide persistent (a.k.a. functional) updaters for URIs and IRIs, but not an in-place interface. We reason that if implementors choose persistent data structures, it may be cumbersome to create an impure interface, whereas it is not as cumbersome for implementors to create a (potentially inefficient) functional interface by copying the URI or IRI before updating in-place.
If an implementation provides disjoint mutable and immutable URI and IRI variants, then it is an error to call the in-place setters on an immutable variant, but the in-place variants should otherwise have equivalent error handling to the persistent variants.
Our design is to provide getters and setters which abstract away the internal representation, working on string representations of URI components. More generally, we suspect that existing implementations largely omit setters because preserving internal consistency is fairly cumbersome on the implementor and programmer, with validity of authority components being defined with respect to the path, and vice versa. The specific challenge is to ensure that setting a given component would not violate the URI grammar, as this may elicit a flat string representation which would have a different structure when parsed again.
In order to set one of the three authority components safely on an URI or IRI, then the following conditions must hold with respect to the URI or IRI’s existing path shape:
#f), rfc-path-abempty? must hold against the path.#f), and the URI or IRI is non-relative, then either rfc-path-absolute?, rfc-path-rootless? or empty-path? must hold against the path.#f), and the URI or IRI is a relative reference, then either rfc-path-absolute?, rfc-path-noscheme? or empty-path? must hold against the existing path component.Similarly, in order to set the path component safely, then the following conditions must hold with respect to the three authority components:
rfc-path-abempty? must hold against the path to be set.rfc-path-absolute?, rfc-path-rootless? or empty-path? must hold against the path to be set.rfc-path-absolute?, rfc-path-noscheme? or empty-path? must hold against the path to be set.Helper predicates for checking whether setting these components are located in the utility library: user-settable?, host-settable?, port-settable? and path-settable?.
R6RS’s utf8->string procedure silently substitutes the unicode replacement character (U+FFFD) given an invalid UTF-8 sequence. If the information is available, then invalid sequences of UTF-8 octets should raise an assertion violation with the offending octet and its position within the bytevector as irritants. This information may not be available, in which case the irritants consist of the irritants are the offending octet alone. SRFI 281 [18] provides unicode conversion procedures which can detect these sequences in external code.
We support three, scheme-independent normalization procedures:
.’ and ‘..’) are eliminated.The scheme and host components of both URIs and IRIs are considered case-insensitive, with other components considered case-sensitive. For URIs, the repertoire of characters is within U.S. ASCII., whereas for IRIs, Unicode characters may appear. Nonetheless, for both URIs and IRIs, only U.S. ASCII is case-insensitive, with characters like “É” never being normalized to a lower-case form like “é”.
Escapes take the form of percent-encoded bytes, appearing as % 0-F 0-F (hexadecimal digits) in URIs and IRIs. These hexadecimal digits have a canonical upper-case form. For example, %cf should be normalized to %CF. In practice, the sample implementation always parses these into a canonical form as it represents these internally as octets, not in the original string form. If implementations do preserve the original form, they must always support normalization into the canonical upper-case form.
The interpretation of escapes (percent-encoded bytes) differs for URIs and IRIs.
For URIs, these octets may individually be decoded to ASCII characters. Essentially, this occurs if a character is not in the URI reserved range, and if it is permissible within a given URI component (e.g. path). Characters in the reserved range, if encountered in the clear, must never be escaped. Conversely, characters not in the reserved range, and which are not permissible within a given URI component, are encoded as a series of percent-encoded bytes corresponding to the character’s UTF-8 octets. This bears particular mention because, while other character encodings are valid URIs, the RFC 3986 specification specifically requires this encoding, which enables compatibility with the closely related RFC 3987 specification for IRIs.
IRIs not only generalize the range of characters permissible within the IRI to certain Unicode ranges, but also interpret sequences of percent-encoded bytes as octets in UTF-8, which may be decoded into legal characters in the universal character set. Additionally, because all URIs are valid IRIs, the normalization of IRIs with respect to percent-encoded bytes is essentially the same as conversion from an URI to an IRI.
Path segment normalization is structurally identical for both URIs and IRIs. It interprets an entire path with respect to two control sequences: ‘.’ (current working directory) and ‘..’ (parent working directory), similar to UNIX paths. Unlike the other two normalization procedures, path segment normalization is undefined for relative references, because the path portion is not meaningful except during relative reference resolution.
It should be noted that path segment normalization is defined for non-relative IRIs with relative paths, such as <foo:a/b/../.././../../e>. This is a major source of diversion between RFC 3986 implementations. This appears to arise from reusing the Remove Dot Segments procedure as defined in RFC 3986 verbatim. In relative reference resolution, this procedure is never called on relative paths, only on absolute paths.
For example, for the (non-relative) URI <foo:a/b/../.././../../e>, implementations using the RFC 3986 procedure verbatim get <foo:/e>, whereas other implementations get <foo:e>. In the former camp are implementations like the Erlang/OTP system’s built-in uri_string [9], and Guile-RDF [10], whereas in the latter camp are implementations like Haskell’s network-uri [6] and Chicken’s uri-generic / uri-common libraries [7][8]. We are in the latter camp, and a fixed Remove Dot Segments procedure might be implemented as follows. The sample implementation internally represents raw segments as a vector of SRFI 160 [16] u16vector segments (percent-encoded bytes shifted by 256, unescaped UTF-8 octets represented verbatim), and uses the SRFI 262 pattern matcher [17].
(define (remove-dot-segments path)
(let* ([undotted
(remove-dot-segments/list (path-raw-segments path))]
[segments
(list->vector undotted)])
(cond [(absolute-path? path)
(make-raw-absolute-path segments)] ;; implementation-specific
[(and (relative-path? path)
(fx=? 1 (vector-length segments))
(u16vector-empty? (vector-ref segments 0)))
;; relative path with single empty segment is not meaningful:
(make-raw-relative-path (vector))] ;; implementation-specific
[else
(make-raw-relative-path segments)]))) ;; ''
(define (current-wd? seq) (u16vector= seq (u16vector #x2E)))
(define (parent-wd? seq) (u16vector= seq (u16vector #x2E #x2E)))
(define (remove-dot-segments/list segments)
(let loop ([ps (vector->list segments)]
[trailing-slash? #f]
[lst '()])
(match ps
['()
(if trailing-slash?
(reverse (cons (u16vector) lst))
(reverse lst))]
[(cons (? current-wd?) rst)
(loop rst #t lst)]
[(cons (? parent-wd?) rst)
(loop rst #t (if (pair? lst) (cdr lst) lst))]
[(cons x rst)
(loop rst #f (cons x lst))])))
Additionally, we think that this view of Remove Dot Segments is clearer than the stack-based description in RFC 3986, with fewer pattern-matching clauses required.
Relative reference resolution resolves an URI or IRI against a (non-relative) base URI or IRI. This SRFI proposal simply names these procedures resolve-uri and resolve-iri as the “relative reference resolution” language in RFC 3986 and 3987 specifically refers to “URI-reference” and “IRI-reference” (see above). Additionally, this naming is consistent with existing libraries such as Erlang’s uri_string module [9] and Java’s URI module [11].
The inverse of these procedures take a non-relative URI or IRI and a base URI or IRI, and materialize a relative reference which, given the base, would produce that non-relative URI or IRI. This relativization is not specified in RFC 3986 or 3987, but is fairly widespread, such as relative-from found in Haskell’s network-uri [6] and Chicken’s uri-generic [7], and relativize found in Java’s URI module [11]. Java’s class methods for both resolution and relativization belong to a base URI, whereas the Haskell and Chicken libraries flip relativization's arguments for consistency with the ‘from’ direction. This SRFI proposal names these procedures relativize-uri and relativize-iri, following Java’s naming and argument convention, although the behaviour and test cases are based on the Chicken library.
Relativization proceeds as follows, given base and non-base URI (or IRI) arguments:
#f), and with query and fragment of the non-base argument.rfc-path-abempty? holds against the path. A relative reference is returned in which the authority components are unset, query is unset, and with the following path:
.’).Calculating that relative path, on the level of segments behaves as follows, or an equivalent procedure:
..’ segments. This sequence is our candidate relativized path segments..’).’ or ‘..’), and if the final segment of the original non-base path was empty, use the candidate sequence as-is.Finally, these segments are tagged as a path object:
Indeed, treatment of (relative) path segments with an empty initial segment as an absolute path is also required of normalization procedures which elicit path objects, e.g. remove-dot-segments). Additionally, the relativization procedures check that the path was settable given the authority component, and will prepend ./ to paths with a leading double slash given no authority component being set (see here).
In RFC 3986 and 3987, IP literals (namely IPv6 addresses) appear in the host portion of an URI or IRI enclosed by square brackets. This SRFI proposal does not normalize these internally to an IP literal object or similar, with the IP literal returned by getters for the host portion enclosed by square brackets. This has the advantage that getters and setters exchange the same (bracketed) IP literal representation, but receiving applications likely need to remove the brackets before usage of IP literals returned by this library’s setters.
We additionally specify a basic path sub-library. Paths can be considered vectors of segments which are tagged with whether they have a leading slash (an absolute path). The character set allowed within these paths is equivalent to the characters permissible within an URI or within the range of the universal character set (UCS) permitted within an IRI path segment. This restriction is important because it excludes a number of control characters and characters which can never be typed.
Basic operations specified include path creation from sets of segments, subscripting, persistent updates, and count of segments. We go a little further and provide utility procedures for path shape (the variants in the RFC 3986 and 3987 ABNF), and for copying paths. Finally, this design allows us to explicitly expose in the programming interface generalist procedures inherited from RFC 3986 (e.g. remove-dot-segments).
For each procedure, error-handling behaviour is described in terms of exceptions to be raised and their irritants, either as an assertion violation, or as an error. R6RS conditions may not be available, in which implementations may simply signal an error. However, if these are available, then implementations should raise a compound condition as follows:
&who condition with the procedure name defined in this document&irritants condition with the irritants in the order described&assertion-violation or &error condition respectively, depending on the language in the procedure descriptionWith access to R6RS conditions, one might implement the encode-string procedure as follows. The first call to the R6RS assertion-violation procedure raises a compound condition of &who, &irritants, &assertion-violation and &error (as well as &message), where the who portion is the symbol encode-string, and the single irritant is the string argument.
(define encode-string
(case-lambda
[(str)
(encode-string str (char-set-complement char-set:iri-unreserved))]
[(str cset)
(cond [(not (string? str))
(assertion-violation 'encode-string "not a string" nonstr)]
[(not (char-set? escape-these))
(assertion-violation 'encode-string "not a character set" noncset)]
[else
...])]))
An R7RS-small implementation might implement it instead as follows:
(define encode-string
(case-lambda
[(str)
(encode-string str (char-set-complement char-set:iri-unreserved))]
[(str cset)
(cond [(not (string? str))
(error "not a string" nonstr)]
[(not (char-set? escape-these))
(error "not a character set" noncset)]
[else
...])]))
This specification follows the R6RS procedure entries convention. In addition to the naming conventions specifying type restriction for arguments where they are used, we add the following:
| iri | non-relative IRI or IRI relative reference |
| non-relative-iri | non-relative IRI |
| relative-iri | IRI relative reference |
| uri | non-relative URI or URI relative reference |
| non-relative-uri | non-relative URI |
| relative-uri | URI relative reference |
| path | relative or absolute path object |
| relative-path | relative path object |
| absolute-path | absolute path object |
Although we support both IRIs and URIs, for brevity we primarily describe behaviour for IRIs, and omit descriptions of the equivalent procedures for URIs where they behave the same. This works because URIs and IRIs are structurally identical, with the divergence between RFC 3986 and 3987 arising from the generalization of the character set, and the treatment of normalization.
Library references are in the form (srfi :275 ⟨sub-library⟩) consistent with SRFI 97 [15]. Five libraries are required to be exported by implementations.
(srfi :275 iri): RFC 3987 IRIs, setters and getters, character sets(srfi :275 uri): RFC 3986 URIs, setters and getters, character sets(srfi :275 normalize): Equivalence, conversion and normalization of IRIs and URIs(srfi :275 path): Basic path interface(srfi :275 utils): Miscellaneous utilitiesSee the appendix for exported procedures grouped by library.
Finally, throughout this document, in examples we enclose IRIs or URIs in angle brackets, e.g. <http://example.org>. An object is assumed to be either an IRI or URI depending on the producing procedures, e.g. string->iri.
string->iri
string->non-relative-iristring->rfc-absolute-iriget-iri
get-non-relative-iri
get-rfc-absolute-iriempty-iristring->uri
string->non-relative-uristring->rfc-absolute-uriget-uri
get-non-relative-uri
get-rfc-absolute-uriempty-uriiri?
relative-iri?
non-relative-iri?rfc-absolute-iri?uri?
relative-uri?
non-relative-uri?rfc-absolute-uri?iri-equal?
string->iri
iri->stringuri-equal?
string->uri
uri->stringiri->uri
uri->iriencode-string
decode-stringiri-schemeiri-user
iri-host
iri-port
iri-authority
iri-username+passwordiri-path
iri-path-stringiri-query
iri-fragmenturi-schemeuri-user
uri-host
uri-port
uri-authority
uri-username+passworduri-path
uri-path-stringuri-query
uri-fragmentupdate-iri-schemeupdate-iri-user
update-iri-host
update-iri-port
update-iri-authorityupdate-iri-path
update-iri-query
update-iri-fragmentupdate-uri-schemeupdate-uri-user
update-uri-host
update-uri-port
update-uri-authorityupdate-uri-path
update-uri-query
update-uri-fragmentresolve-iri
relativize-iriresolve-uri
relativize-urinormalize-iri-case
normalize-iri-escape
normalize-iri-pathnormalize-uri-case
normalize-uri-escape
normalize-uri-pathbuild-path
vector->relative-path
vector->absolute-pathstring->path
string->relative-path
string->absolute-pathempty-relative-path
empty-absolute-pathvector->rfc-path-rootless
vector->rfc-path-noschemevector->rfc-path-abempty
vector->rfc-path-absolutestring->rfc-path-rootless
string->rfc-path-noschemestring->rfc-path-abempty
string->rfc-path-absolutepath-equal?
string->path
path->string
path-segmentspath?
path-string?
relative-path?
absolute-path?
empty-path?rfc-path-rootless?
rfc-path-noscheme?rfc-path-abempty?
rfc-path-absolute?iri-path-relative?
iri-path-absolute?
iri-path-empty?iri-path-rfc-rootless?
iri-path-rfc-noscheme?iri-path-rfc-abempty?
iri-path-rfc-absolute?
uri-path-relative?
uri-path-absolute?
uri-path-empty?uri-path-rfc-rootless?
uri-path-rfc-noscheme?uri-path-rfc-abempty?
uri-path-rfc-absolute?path-length
path-ref
path-update
remove-dot-segments
merge-pathsiri-path-segment-ref
iri-path-segment-update
iri-path-segmentsuri-path-segment-ref
uri-path-segment-update
uri-path-segmentsusername+passworduser-settable?
host-settable?
port-settable?
path-settable?char-set:iri
char-set:iri-unreserved
char-set:ucschar
char-set:iri-privatechar-set:gen-delims
char-set:sub-delims
char-set:reservedchar-set:scheme
char-set:iri-userinfo
char-set:iri-reg-namechar-set:iri-segment
char-set:iri-query
char-set:iri-fragment
char-set:uri
char-set:uri-unreservedchar-set:gen-delims
char-set:sub-delims
char-set:reservedchar-set:scheme
char-set:uri-userinfo
char-set:uri-reg-namechar-set:uri-segment
char-set:uri-query
char-set:uri-fragment
char-set:pathReturns #t if obj is an IRI. Returns #f otherwise.
Returns #t providing that obj is a non-relative IRI (has a scheme component). Returns #f otherwise.
Returns #t providing that obj is an IRI relative reference (no scheme component). Returns #f otherwise.
Examples:
(define example-IRI (string->iri "/ex#IRI")) example-IRI → </ex#IRI> (iri? example-IRI) → #t (non-relative-iri? example-IRI) → #f (relative-iri? example-IRI) → #t
Returns #t providing that obj is a non-relative IRI and that its fragment component is #f. This corresponds exactly to RFC 3987’s “absolute-IRI” production. Returns #f otherwise.
Examples:
(define example0 (string->iri "http://example.org/ex?cond")) (define example1 (string->iri "http://example.org/ex#title")) (define example2 (string->iri "//example.org/ex?cond")) (define example3 (string->iri "//example.org/ex#title")) (map rfc-absolute-iri? (list example0 example1 example2 example3)) → (#t #f #f #f)
Helper procedure which takes no arguments and constructs a relative reference with all components set to #f, i.e. <> or the result of parsing "". Example:
(empty-iri) → <> (iri-equal? (string->iri "") (empty-iri)) → #t
Retrieve the scheme component of non-relative-iri as a string. Unlike the procedures to follow, this procedure never returns #f as this would imply that the scheme is unset, i.e. a relative reference.
Example:
(iri-scheme (string->iri "a/b/c")) → ERROR (iri-scheme (string->iri "urn:a/b/c")) → "urn"
Retrieve the respective RFC 3987 components of iri as either a string, or #f. An empty component is distinct from an unset one, for example given an IRI with bare ?, iri-query would return "", whereas if ? had been omitted, iri-query would return #f.
Retrieve the RFC 3987 port component of iri as either a non-negative integer, or #f.
Retrieve the RFC 3987 path component of iri as a path object as described in the path sub library. Unlike the other component, #f is never returned.
Examples for the above seven component-specific procedures:
(define example-IRI (string->iri "http://example.org:80/ex#IRI"))
example-IRI → <http://example.org:80/ex#IRI>
(iri? example-IRI) → #t
(non-relative-iri? example-IRI) → #t
(iri-scheme example-IRI) → "http"
(iri-user example-IRI) → #f
(iri-host example-IRI) → "example.org"
(iri-port example-IRI) → 80
(path-segments (iri-path example-IRI)) → #("ex")
(iri-query example-IRI) → #f
(iri-fragment example-IRI) → "IRI"
Get the authority components of iri (user, host and port). This procedure returns #f if none of the three authority components are set, else all three as multiple values. Examples:
(define example0 (iri->string "http://example.org:8080/some/where/place")) (define example1 (iri->string "http://user@example.org/some/where/place")) (define example2 (iri->string "urn:/some/where/place")) (iri-authority example0) → #f "example.org" 8080 (iri-authority example1) → "user" "example.org" #f (iri-authority example2) → #f
Helper procedure which splits the user component of iri at the first colon, producing strings corresponding to username and password as two values. If there is no colon then the whole user component is returned as first value, and #f as second. If the user component is not set, then #f and #f are returned. A colon appearing as a percent-encoded byte (%3A) is not considered a delimiter between username and password.
Examples:
(iri-username+password (string->iri"//foo:bar:qux@host")) → "foo" "bar:qux" (iri-username+password (string->iri"//foo%3Abar:qux@host")) → "foo%3Abar" "qux" (iri-username+password (string->iri"//@host")) → "" #f (iri-username+password (string->iri "//")) → #f #f (iri-username+password (string->iri "//foo@host")) → "foo" #f
We also provide a generic helper procedure for processing strings retrieved from the user component of an IRI or URI.
In contrast to iri-path, return a string representation of the path of iri, which may be the empty string.
Examples:
(define example0 (string->iri "http://example.org:80/ex#IRI"))
(define example1 (string->iri "http://example.org:80#IRI"))
(define example2 (string->iri "//a"))
(path-segments (iri-path example0)) → #("ex")
(iri-path-string example0) → "/ex"
(path-segments (iri-path example1)) → #()
(iri-path-string example1) → ""
(path-segments (iri-path example2)) → #()
(iri-path-string example2) → ""
Persistent setter for the scheme component of non-relative-iri, encoding the string string. During parsing, an error is raised if a character cannot be contained within a scheme at that position, with the character, its position in the IRI, and the IRI as irritants. Unlike the other setters for IRI components to follow, setting the scheme to #f or the empty string is not possible becasue it would imply that the IRI is a relative reference.
Examples:
(define example0 (string->iri "http://example.org:80/ex#IRI")) (update-iri-scheme example0 "file") → <file://example.org:80/ex#IRI> (update-iri-scheme example0 #f) → ERROR (update-iri-scheme example0 "http%40a") → ERROR (define example1 (string->iri "/some/where/place")) (update-iri-scheme example1 "file") → ERROR
(srfi :275 iri)(update-iri-user iri obj)(update-iri-host iri obj)(update-iri-port iri obj)Persistent setters to set a respective authority components of iri to obj. These procedures check that the authority component after being set to obj is not in conflict with the shape of the existing path. For instance, relative paths cannot usually be set when either of the authority components are set. The conditions associated with setting any authority component with the two input arguments are tested, and if they do not hold, then an error is raised with the input arguments as irritants.
In update-iri-user and update-iri-host, if obj is not #f and is a string, then it will be parsed for the respective component. Any character which cannot be contained within the component will be encoded as a sequence of percent-encoded bytes. When a percent-encoded byte is part of an invalid sequence of UTF-8 octets, an error is raised. In update-iri-port, if obj is not #f and is a non-negative integer, then it will be set-as is, otherwise #f. An assertion violation is raised with obj as irritant if it is not of the expected type just discussed or #f.
Examples:
(define example0 (string->iri "urn:/some/where/place")) (define example1 (string->iri "urn:/some/where/place")) (define example2 (string->iri "urn://")) (define example3 (string->iri "urn://user@example.org")) (define example4 (string->iri "urn://example.org:80")) (define example5 (string->iri "urn://example.org//some/where/place")) (define example6 (string->iri "urn:some/where/place")) (update-iri-host example0 "example.org") → <urn://example.org/some/where/place> (update-iri-host example1 #f) → <urn:/some/where/place> (update-iri-host example2 #f) → <urn:> (update-iri-host example3 #f) → <urn://user@> (update-iri-host example4 #f) → <urn://:80> (update-iri-host example5 #f) → ERROR (update-iri-host example6 "example.org") → ERROR
Combination procedure which subsumes the above procedures. If a single argument #f is given, then all three components will be set to #f providing this is not in conflict with path. If three arguments are given, then error behaviour is identical to the component-specific setters except that the check for conflict with the existing path is that setting all three to would not be in conflict with the path shape.
Persistent setters for the query and fragment components of an IRI. Error behaviour is identical to update-uri-user, except that there is no check that these are in conflict with the path shape. Examples:
(define example0 (string->iri "urn:/some/where/place")) (define example1 (string->iri "http://example.org?query")) (update-iri-query example0 "query") → <urn:/some/where/place?query> (update-iri-query example0 "") → <urn:/some/where/place?> (update-iri-query example0 #f) → <urn:/some/where/place> (update-iri-query example1 "") → <http://example.org?> (update-iri-fragment example0 "nonquery") → <urn:/some/where/place#nonquery> (update-iri-fragment example0 "") → <urn:/some/where/place#> (update-iri-fragment example0 #f) → <urn:/some/where/place> (update-iri-fragment example1 "") → <http://example.org#>
Update the path component of iri with obj. If obj is #f, then the path to be set is the empty path. If obj is a path object, then any character not permissible within a valid IRI path is escaped as percent-encoded bytes corresponding to the character’s UTF-8 octets. If obj is a string, then it is converted to a path object using a procedure equivalent to string->path, with any illegal characters again escaped as percent-encoded bytes. Next, the conditions associated with setting path are tested for iri and the path object to be set, and if they do not hold, then an error is raised with both input arguments as irritants. An assertion violation is raised with obj as irritant if it is not a path object, a string, or #f.
Examples:
(define example0 "urn:/some/where/place") (define example1 "http://example.org/some/where/place") (update-iri-path example0 "//some/where/place") → ERROR (update-iri-path example0 "") → <urn:> (update-iri-path example1 "//some/where/place") → <http://example.org//some/where/place> (update-iri-path example1 "") → <http://example.org> (update-iri-path example0 (string->path "/some/where/else")) → <urn:/some/where/else> (update-iri-path example0 (empty-relative-path)) → <urn:>
Returns #t if iri1 and iri2 have components which are all equal. A non-relative IRI is not equal to a relative reference and vice versa as relative references have no scheme component. For components other than path and port, two components are only equal if they encode the same sequence of characters and percent-encoded bytes exactly. Two port components are equal if eq? holds between them, and two path componets are equal if path-equal? holds. Returns #f otherwise.
Examples:
(define example0 (string->iri "/some/where/place") (define example1 (string->iri "urn:/some/where/place")) (define example2 (string->iri "http://example.org")) (define example3 (string->iri "http://example.org:")) ;; no such thing as empty port (define example4 (string->iri "http://example.org?a")) (define example5 (string->iri "http://example.org?")) ;; empty query vs #f (iri-equal? example0 example1) → #f (iri-equal? example2 example3) → #t ;; no such thing as empty port (iri-equal? example4 example5) → #f ;; empty query vs #f
(srfi :275 uri)(uri? uri)(non-relative-uri? uri)(relative-uri? uri)(rfc-absolute-uri? uri)(empty-uri)(uri-scheme uri)(uri-user uri)(uri-host uri)(uri-query uri)(uri-fragment uri)(uri-port uri)(uri-path uri)(uri-authority uri)(uri-username+password uri)(uri-path-string uri)(update-uri-scheme uri obj)(update-uri-user uri obj)(update-uri-host uri obj)(update-uri-port uri obj)(update-uri-path uri obj)(update-uri-query uri obj)(update-uri-fragment uri obj)(update-uri-authority uri #f)(uri-equal? uri1 uri2)Identical programming interfaces to that of IRIs are given for URIs, with argument restriction to the respective URI types, and with the repertoire of characters escaped by setters extended to characters permissible in a given IRI component, but not the equivalent URI component.
Relative reference resolution of iri against base. An assertion violation is raised with irritant base if it is not a non-relative IRI. Simple examples derived from the RDF Turtle test cases (see test suite section):
(define string-cases (list "g:h" "g" "./g" "g/" "/g" "//g"))
(define base01 (string->iri "http://a/bb/ccc/d;p?q"))
(define base02 (string->iri "http://a/bb/ccc/d/"))
(define base07 (string->iri "file:///a/bb/ccc/d;p?q"))
(define (resolve-with base-iri) ;; Higher-order. Returns a function.
(lambda (ref)
(resolve-iri-reference base-iri (string->iri ref))))
(map (resolve-with base01) string-cases)
→ (list <g:h>
<http://a/bb/ccc/g> <http://a/bb/ccc/g> <http://a/bb/ccc/g/>
<http://a/g> <http://g>)
(map (resolve-with base02) string-cases)
→ (list <g:h>
<http://a/bb/ccc/d/g> <http://a/bb/ccc/d/g> <http://a/bb/ccc/d/g/>
<http://a/g> <http://g>)
(map (resolve-with base07) string-cases)
→ (list <g:h>
<file:///a/bb/ccc/g> <file:///a/bb/ccc/g> <file:///a/bb/ccc/g/>
<file:///g> <file://g>)
The inverse of resolve-iri, which elicits a relative reference which is resolveable against base in order to produce non-relative-iri. It is not possible to elicit a path for which rfc-path-abempty? does not hold after calling remove-dot-segments for either argument, and an error is raised with the normalized path and respective argument as irritants. An assertion violation is raised if base is not a non-relative IRI. The argument order is consistent with resolve-iri, compared to Chicken’s relative-from procedure [7] where it is flipped.
Examples:
(define base-iri (string->iri "http://a/bb/ccc/d;p?q"))
(define reference0 (string->iri "g"))
(define resolved0 (string->iri "http://a/bb/ccc/g"))
;; (iri-equal? (relativize base (resolve base v)) v)
(iri-equal? (relativize-iri base-iri (resolve-iri base-iri reference0))
reference0) → #t
;; (iri-equal? (resolve base (relativize base v)) v)
(iri-equal? (resolve-iri base-iri (relativize-iri base-iri resolved0))
resolved0) → #t
Relative reference resolution of an IRI against a base URI, and the inverse, which produces a relative reference given a base IRI and a resolved IRI. Behaviour is structurally identical to that of relative reference resolution for IRIs, and the arguments are instead restricted to non-relative URIs and relative references respectively.
The following procedures are functional and structure preserving on the level of components, to avoid some of the problems with string representations being parsed differently post-normalization.
Normalize case-insensitive components (scheme and host), and convert percent-encoded bytes to canonical upper-case. Examples (see case normalization test cases):
(normalize-iri-case (string->iri "HttP://example.org/ex#test")) → <http://example.org/ex#test> (normalize-iri-case (string->iri "http://Example.ORG/ex#test")) → <http://example.org/ex#test> (normalize-iri-case (string->iri "http://CRÊPES.example.org")) → <http://crÊpes.example.org> (normalize-iri-case (string->iri "http://USER@example.org/Some/Where/Place?Query#test")) → <http://USER@example.org/Some/Where/Place?Query#test>
Normalize an IRI’s escapes, potentially interpreting percent-encoded bytes if part of valid UTF-8 octet sequences as characters. Conversely, characters which are not permissible within an URI component will appear as series of percent-encoded bytes corresponding to UTF-8 octets. This procedure is idempotent: a fully normalized IRI will be normalized to itself, and iri-equal? will hold true between the two.
Examples (see escape normalization test cases):
(normalize-iri-escape (string->iri "http://crepes.example.org/in/Rennes?Dim.%E2%80%A5Sam.")) → <http://crepes.example.org/in/Rennes?Dim.‥Sam.> (normalize-iri-escape (string->iri "https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82")) → <https://en.wiktionary.org/wiki/Ῥόδος> (normalize-iri-escape (string->iri "http://my%40name@example.org/ex#test")) → <http://my%40name@example.org/ex#test>
Normalize an URI’s escapes, potentially interpreting percent-encoded bytes as octets in U.S. ASCII. Conversely, characters which are not permissible within an URI component will appear as series of percent-encoded bytes corresponding to UTF-8 octets.
Examples (see escape normalization test cases):
(normalize-uri-escape (string->uri "http://dosh£@crepes.example.org")) → <http://dosh%C2%A3@crepes.example.org> (normalize-uri-escape (string->uri ))
These procedures normalize path segments given the control segments ‘.’ and ‘..’. While path segments of IRI or URI relative references are not normalized, these procedures simply have no effect and no error is signalled, consistent with the other normalization procedures. Although it is relatively uncommon for relative paths to appear in non-relative IRIs or URIs, they are permissible e.g. within URN components.
These procedures are essentially a sequence of the three normalization procedures described previously, but they may be more efficient, as they can perform a single check that setting any authority component is legal, and could avoid excessive copying.
Return a new string in which any characters in string contained in char-set are escaped as percent-encoded bytes. char-set is optional and defaults to characters outside of the IRI unreserved range (complement of char-set:iri-unreserved).
The choice of default range includes the reserved range shared by both IRIs and URIs. Although setters automatically escape characters which are not permissible in a given IRI or URI component, characters in the reserved range must never be percent-encoded during most URI operations, so encode-string allows this to be explicitly invoked. Notably, unlike other procedures, the percent-sign is treated as a normal character, rather than a control character, and is always percent-encoded in turn regardless of the optional character set.
In the following examples, recall that both ‘@’ and ‘!’ are in the reserved range:
(encode-string "user%40host") → "user%2540host" (encode-string "user@host") → "user%40host" (encode-string "user@host" char-set:full) → "%75%73%65%72%40%68%6F%73%74" (encode-string "a béc♂d😎e" (char-set-complement char-set:uri)) → "a%20b%C3%A9c%E2%99%82d%F0%9F%98%8Ee" ;; % is always escaped (encode-string "%a") → "%25%61" (encode-string "%a" (char-set-complement (char-set #\%))) → "%25%61" (define example0 (string->iri "http://example.org/some/where/place")) (update-iri-user example0 "person!pass") → <http://person!pass@example.org/some/where/place> (update-iri-user example0 (encode-string "person!pass") → <http://person%21pass@example.org/some/where/place>
Return a new string in which any sequences of percent-encoded bytes in string are decoded as sequences of UTF-8 octets, providing that the sequence corresponds to a character in char-set. char-set defaults to every character (SRFI 14 char-set:full [14]). When a percent-encoded byte is part of an invalid sequence of UTF-8 octets, an error is raised.
Examples:
(decode-string (encode-string "user%40host")) → "user%40host" (decode-string "a%20b%C3%A9c%E2%99%82d%F0%9F%98%8Ee" (char-set-complement char-set:uri)) → "a béc%E2%99%82d😎e" ;; overlong 4-byte encoding of U+0020 (decode-string "a%E0%80%80%A0b") → ERROR ;; incomplete 4-byte sequence (missing fourth byte for U+1F60E / ‘😎’) (decode-string "a%F0%9F%98") → ERROR
(srfi :275 iri)(string->iri string)(string->non-relative-iri string)(string->rfc-absolute-iri string)string->iri parses string into an IRI. An error is raised with the failed character and its position in the string, in the following cases. First, this may occur when a character is not permitted within a given IRI component (unlike the string setters, there is no automatic escaping). Second, when a percent-encoded byte is part of an invalid sequence of UTF-8 octets, an error is raised. Finally, if the end of the string was prematurely encountered, then the irritants are the EOF object and the string length.
string->non-relative-iri is a restriction on string->iri in that the scheme component is required. An error is raised with the EOF object and the string length as irritants if the scheme does not end with a colon, and with an illegal character and its position as irritants if this is contained within the scheme. Once a valid scheme has been parsed, the parse continues with the same behaviour as string->iri. This definition of a non-relative IRI (requirement of a scheme component) differs from the RFC 3987 “absolute-IRI” definition in that a fragment may appear, but we offer this procedure for convenience as many applications (like the RDF N-Triples concrete syntax) forbid relative references but permit non-relative IRIs to have fragment components.
string->rfc-absolute-iri has the same error behaviour as string->non-relative-iri except that the fragment component is strictly forbidden. That is, if the hash character is parsed, then an error is raised with the hash character and its position in the string as irritants.
Examples:
;; absolute (string->iri "urn:/some/where/place") → <urn:some/where/place> (string->non-relative-iri "urn:/some/where/place") → <urn:some/where/place> (string->rfc-absolute-iri "urn:/some/where/place") → <urn:some/where/place> ;; non-relative but not rfc-absolute (string->iri "http://example.org#fragment") → <http://example.org#fragment> (string->non-relative-iri "http://example.org#fragment") → <http://example.org#fragment> (string->rfc-absolute-iri "http://example.org#fragment") → ERROR ;; relative (string->iri "/some/where/place") → </some/where/place> (string->non-relative-iri "/some/where/place") → ERROR (string->rfc-absolute-iri "/some/where/place") → ERROR
Serialization of an IRI as a string. Examples:
(define example-A (string->iri "http://example.org/some/where/place")) (define example-B (string->iri "urn:/some/where/place")) (iri->string example-A) → "http://example.org/some/where/place" (iri->string example-B) → "urn:/some/where/place"
(srfi :275 iri)(get-iri port)(get-iri port obj)(get-non-relative-iri port)(get-non-relative-iri port obj)(get-rfc-absolute-iri port)(get-rfc-absolute-iri port obj)These procedures are the equivalent of the string parsing procedures immediately above, but which parse a stream of text from a port as an IRI. These procedures stop if the port produces a character or the EOF object which is eq? with optional datum obj, returning the IRI and with the current port position just before that of obj. These procedures raise an assertion violation if port is not a textual input port, and the parse behaviour is identical to the equivalent string procedures except that the position irritant is the equivalent port position.
These low-level procedures are offered because they can be used directly in streaming parsers for data which contains IRIs, such as JSON-LD [5]. An example of this in practice is as follows:
(define port0 (open-string-input-port "urn:/some/where/place")) (get-iri port0) → <urn:/some/where/place> (eof-object? (get-char port0)) → #t (define line "<http://example.org/some/where/place> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/01/rdf-schema#Resource> .") (define port1 (open-string-input-port line)) (get-char port1) → #\< (get-iri port1 #\>) → <http://example.org/some/where/place> (get-char port1) → #\>
(srfi :275 uri)(string->uri string)(string->non-relative-uri string)(string->rfc-absolute-uri string)(uri->string uri)(get-uri port)(get-uri port obj)(get-non-relative-uri port)(get-non-relative-uri port obj)(get-rfc-absolute-uri port)(get-rfc-absolute-uri port obj)Identical programming interface to that of IRIs are given for URIs, with argument restriction to the respective URI types, and with the repertoire of characters permissible unescaped restricted to those of URIs.
Convert an IRI to an URI. This proceeds by encoding any character within certain ranges (see RFC 3987 ucschar and iprivate) to a series of percent-encoded bytes corresponding to the character’s octets in UTF-8. This procedure is structure-preserving: (non-relative) IRIs are never transformed into relative references, or vice-versa. Example:
(define iri0 (string->iri "https://en.wiktionary.org/wiki/Ῥόδος")) (uri? (iri->uri iri0)) → #t (iri->uri iri0) → <https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82> (define iri1 (string->iri "https://example.org/ceol/Éirigh'sCuirOrtDoChuidÉadaigh")) (uri? (iri->uri iri1)) → #t (iri->uri iri1) → <https://example.org/ceol/%C3%89irigh'sCuirOrtDoChuid%C3%89adaigh>
Convert an URI to an IRI. This procedure can be viewed as upgrading the URI structure to that of an IRI, then normalizing it as an IRI.
(define uri0 (string->uri "https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82")) (iri? (uri->iri uri0)) → #t (uri->iri uri0) → <https://en.wiktionary.org/wiki/Ῥόδος> (define uri1 (string->uri "https://example.org/ceol/%C3%89irigh'sCuirOrtDoChuid%C3%89adaigh")) (iri? (uri->iri uri1)) → #t (uri->iri uri1) → <https://example.org/ceol/Éirigh'sCuirOrtDoChuidÉadaigh>
This section descibes the basic path sub-library, consisting of two path variants with a common internal segment structure, tagged with whether there is a leading slash. In addition to predicates to distinguish these two path types, we describe predicates which exactly follow the RFC 3986 and 3987 definitions, which are prefixed with rfc-. These do not check that the character set of the path is consistent with RFC 3986 or 3987, at most checking whether the first segment (if any) is empty or contains a colon character. Additionally, the rfc- procedures do not raise an error on non-path objects, instead returning #f, consistent with the generic absolute-path? or relative-path? procedures.
Predicates for relative and absolute path objects, which share a common internal segment structure, returning #t for the respective path objects. path? returns #t if obj is a relative or absolute path. All three return #f otherwise.
Returns #t if obj is both relative and has no segments. An absolute path with no segments is not considered empty, as it starts with a slash and is represented by the string "/". Returns #f otherwise.
Returns #t if obj is a relative path with at least one, non-empty segment. Returns #f otherwise.
Returns #t if obj is a relative path with at least one, non-empty segment, with the additional condition that the initial segment does not contain a plain (unescaped) colon character. Returns #f otherwise.
Returns #t providing that either absolute-path? or empty-path? hold against obj. Returns #f otherwise.
Returns #t providing the first segment of obj is non-empty (i.e. the path does not start with //). Unlike rfc-path-abempty?, this procedure does not hold true for empty paths. Returns #f otherwise.
Examples for the above predicates:
(define rfc-abempty-example (string->path "//some/where/place"))
(define rfc-absolute-example (string->path "/some/where/place"))
(define rfc-rootless-example (string->path "some:thing/where/place"))
(define rfc-noscheme-example (string->path "some/where/place"))
(define relative-empty-example (string->path ""))
(define absolute-empty-example (string->path "/"))
(define all
(list rfc-abempty-example rfc-absolute-example
rfc-rootless-example rfc-noscheme-example
relative-empty-example absolute-empty-example))
(map absolute-path? all) → (#t #t #f #f #f #t)
(map relative-path? all) → (#f #f #t #t #t #f)
(map empty-path? all) → (#f #f #f #f #t #f)
(map rfc-path-abempty? all) → (#t #t #f #f #t #t) ; empty path is also rfc-path-abempty?
(map rfc-path-absolute? all) → (#f #t #f #f #f #f) ; zero segments is not rfc-path-absolute?
(map rfc-path-rootless? all) → (#f #f #t #t #f #f) ; empty path is not rfc-path-rootless?
(map rfc-path-noscheme? all) → (#f #f #f #t #f #f)
Helper procedures which take no arguments and construct a relative path or an absolute path with no segments, i.e. a relative path like "" or an absolute path like "/". However, note that empty-path? does not hold for an absolute path with no segments, as it has a leading slash:
(empty-path? (empty-relative-path)) → #t (empty-path? (empty-absolute-path)) → #f
Retrieve the number of segments in path, a non-negative integer. Absolute paths have the same number of segments as a relative path without the leading slash (leading slash is not represented by an empty initial segment). Examples:
(define example0 (empty-relative-path)) (define example1 (empty-absolute-path)) (define example2 (string->path "some/where/place")) (define example3 (string->path "/some/where/place")) (path-length example0) → 0 (path-length example1) → 0 (path-length example2) → 3 (path-length example3) → 3
Serialize the internal segment encoding of path as a vector of (string) segments. The returned vector cannot be guaranteed to correspond to the internal structure, as there may be percent-encoded bytes requiring a non-string encoding. Examples:
(define example0 (empty-relative-path))
(define example1 (empty-absolute-path))
(define example2 (string->path "some/where/place"))
(define example3 (string->path "/some/where/place"))
(path-segments example0) → #()
(path-segments example1) → #()
(path-segments example2) → #("some" "where" "place")
(path-segments example3) → #("some" "where" "place")
Retrieve segment of path at index k as a string.
(define example0 (empty-relative-path)) (define example1 (empty-absolute-path)) (define example2 (string->path "some/where/place")) (define example3 (string->path "/some/where/place")) (path-ref example2 0) → "some" (path-ref example3 2) → "place" (path-ref example0 0) → ERROR (path-ref example1 9999) → ERROR (path-ref example2 12) → ERROR (path-ref example2 -9999) → ERROR
Update segment of path at index k with string. Like the conversion from vector procedures to follow, when a slash is encountered, then it will be escaped as a percent-encoded byte. When a percent-encoded byte is part of an invalid sequence of UTF-8 octets, an error is raised.
(define example0 (string->path "some/where/place")) (path->string (path-update example0 2 "else")) → "some/where/else" (define example1 (string->path "/some/where/place")) (path->string (path-update example1 0 "in/any")) → "/in%2Fany/where/place"
Returns #t providing that both path1 and path2 are absolute, or both relative, and that compared pairwise, segments encode the exact same sequence of characters and percent-encoded bytes. Returns #f otherwise. Examples:
(define example0 (string->path "some/where/place") (define example1 (string->path "/some/where/place")) (define example2 (string->path "some/where/place")) (path-equal? example0 example1) → #f (path-equal? example0 example2) → #t
Serialize path as a string, with no decoding of percent-encoded bytes. Examples:
(path->string (empty-relative-path)) → "" (path->string (empty-absolute-path)) → "/" (path->string (string->path "some/where/place")) → "some/where/place" (path->string (string->path "/some/where/place")) → "/some/where/place"
Returns #t if obj is either a path or string, and if a string, that it could represent a path without additional escaping. The specific check is that a character is permissible within an URI or is within the universal character set ranges permissible within an IRI (see char-set:path). No error is raised if obj is not a string, instead #f is returned. This procedure corresponds to the similarly-named Racket procedure.
Examples:
(path-string? "some/where/place") → #t (path-string? (string->path "some/where/place")) → #t (path-string? (utf8->string '#vu8(#xC0 #xA0))) → #f
Parse string as a path. Both relative paths with a colon in the initial segment, and absolute paths with an empty initial segment, are permitted. When a percent-encoded byte is part of an invalid sequence of UTF-8 octets, an error is raised.
Convert string to a relative path. If the optional noscheme? argument is not #f, and the first segment contains a colon character, then the path output will be prefixed with ./. An error is raised with the string as irritant if it has a leading slash. Error behaviour is otherwise identical to string->path. Examples:
(path->string (string->relative-path "some/where/place")) → "some/where/place" (path->string (string->relative-path "some:where/place/else")) → "some:where/place/else" (path->string (string->relative-path "some:where/place/else" #t)) → "./some:where/place/else" (string->relative-path "/some/where/place") → ERROR
(srfi :275 path)(string->absolute-path string)(string->absolute-path string nonempty-ini?)Convert string to an absolute path. If the optional nonempty-ini? argument is not #f, and the initial segment is empty (leading double slash), then an error is raised with irritant string. Error behaviour is otherwise identical to string->path. Examples:
(path->string (string->absolute-path "/some/where/place")) → "/some/where/place" (path->string (string->absolute-path "//some/where/place")) → "//some/where/place" (path->string (string->absolute-path "/some/where/place" #t)) → "/some/where/place" (path->string (string->absolute-path "//some/where/place" #t)) → ERROR
Helper procedures in which the final optional argument to string->relative-path is set to #f and #t respectively.
Helper procedures in which the final optional argument to string->absolute-path is set to #f and #t respectively.
Convert a vector of string segments to a relative path. This procedure behaves similarly to string->relative-path with the exception that that slashes are always escaped, so a slash at the start of the first segment does not raise an error. An error is raised if a segment is not a string, with the segment, its position, and vector as irritants, and if the initial segment is empty (serialized as leading slash), then an error is raised with the (string) segment, its position, and vector as irritants.
Examples:
(path-segments (vector->relative-path #("some" "where" "place"))) → #("some" "where" "place")
(path-segments (vector->relative-path #("some:where" "place" "else"))) → #("some:where" "place" "else")
(path-segments (vector->relative-path #("some:where" "place" "else") #t)) → #("." "some:where" "place" "else")
(srfi :275 path)(vector->absolute-path vector)(vector->absolute-path vector nonempty-ini?)Convert a vector of string segments to an absolute path. This procedure behaves similarly to string->absolute-path with the exception that that slashes are always escaped, so a slash at the start of the first segment does not raise an error. An error is raised if a segment is not a string, with the segment, its position, and vector as irritants.
Examples:
(path-segments (vector->absolute-path #("some" "where" "place"))) → #("some" "where" "place")
(path-segments (vector->absolute-path #("" "some" "where" "place"))) → #("" "some" "where" "place")
(path-segments (vector->absolute-path #("some" "where" "place") #t)) → #("some" "where" "place")
(vector->absolute-path #("" "some" "where" "place") #t) → ERROR
Helper procedures in which the final optional argument to vector->relative-path is set to #f and #t respectively.
Helper procedures in which the final optional argument to vector->absolute-path is set to #f and #t respectively.
Variadic procedure to build a path from a base path and any number of additional string segments. If no arguments are given, then the empty path is returned. This procedure corresponds to the similarly-named Racket procedure.
If base is a string, then the procedure behaves like vector->relative-path. If base is a path, then the procedure concatenates the base path with a relative path corresponding to the remaining segments. This is accomplished using the merge-paths procedure described in RFC 3986 section 5.3.2. The slash character is always escaped within segments.
By admitting the base as potentially another path, this allows one to build an absolute path from a list of segments, by choosing (empty-absolute-path) as base.
An error is raised if a segment is not a string, with the segment, and its position, as irritants. Additionally, if the initial segment (after expanding the base path) is empty, then an error is raised with the (string) segment and its position as irritants.
Examples:
(path->string (build-path (empty-relative-path) "some" "where" "place")) → "some/where/place" (path->string (build-path (empty-absolute-path) "some" "where" "place")) → "/some/where/place" (path->string (build-path "some" "where" "place")) "some/where/place" (path->string (build-path (empty-absolute-path) "" "res")) → "//res" (build-path (empty-relative-path) "" "res")) → ERROR ;; empty initial segment (build-path "" "res")) → ERROR ;; '' (path->string (build-path (string->path "/a/b") "x")) → "/a/x" ;; merge-paths
Remove dotted segments (‘.’ and ‘..’) in path by interpreting them alongside the path segment structure. This procedure corresponds to RFC 3986 section 5.2.4. The path is clamped at root (for absolute paths) and will preserve a relative path, for which see also path normalization test cases. Examples:
(path->string (remove-dot-segments (string->path "/a/b/../x"))) → "/a/x" (path->string (remove-dot-segments (string->path "/a/b/../../../../../../../.."))) → (path->string (remove-dot-segments (string->path "/a/b/../.././../../e"))) → "/e" (path->string (remove-dot-segments (string->path "/a/b/../.././../../"))) → "/" (path->string (remove-dot-segments (string->path "a/b/../.././../../e"))) → "e"
Merge base path base with path. In RFC 3986 section 5.2.3, if a base URI has an authority component and an empty path, then a leading slash concatenated with the reference's path is used. Otherwise, return a string consisting of the base path up to (and excluding) the last segment, concatenated with the reference path. Presence of an authority component is controlled by whether the optional base-has-authority? argument is non-#f, and is mostly important during relative reference resolution. This defaults to #f as this procedure in isolation assumes no authority component.
Examples (see also relative reference resolution test cases):
(path->string (merge-paths (string->path "/a/b") (string->path "x"))) → "/a/x" (path->string (merge-paths (string->path "/b/c/d;p") (string->path "../../../g"))) → "/b/c/../../../g" (path->string (merge-paths (string->path "/swap/test/animal.rdf") (string->path "animal.rdf"))) → "/swap/test/animal.rdf" ;; compare base-has-authority? (path->string (merge-paths (empty-relative-path) (path->string "a/b/c"))) → "a/b/c" (path->string (merge-paths (empty-relative-path) (path->string "a/b/c") #t)) → "/a/b/c"
(srfi :275 iri)(iri-path-absolute? iri)(iri-path-relative? iri)(iri-path-empty? iri)(iri-path-rfc-abempty? iri)(iri-path-rfc-absolute? iri)(iri-path-rfc-rootless? iri)(iri-path-rfc-noscheme? iri)Helper procedures which call the path library’s corresponding procedures for path shape of an IRI. Unlike the equivalent path predicates which return #f, these raise an assertion violation with iri as irritant if it is not an IRI. Examples:
(define rfc-abempty-example (string->iri "urn://myhost//some/where/place"))
(define rfc-absolute-example (string->iri "urn://myhost/some/where/place"))
(define rfc-rootless-example (string->iri "urn:some:thing/where/place"))
(define rfc-noscheme-example (string->iri "urn:some/where/place"))
(define relative-empty-example (string->iri "urn:"))
(define absolute-empty-example (string->iri "urn:/"))
(define all
(list rfc-abempty-example rfc-absolute-example
rfc-rootless-example rfc-noscheme-example
relative-empty-example absolute-empty-example))
(map iri-path-string all)
→ ("//some/where/place" "/some/where/place"
"some:thing/where/place" "some/where/place"
"" "/")
(map iri-path-absolute? all) → (#t #t #f #f #f #t)
(map iri-path-relative? all) → (#f #f #t #t #t #f)
(map iri-path-empty? all) → (#f #f #f #f #t #f)
(map iri-path-rfc-abempty? all) → (#t #t #f #f #t #t) ; empty path is also rfc-path-abempty?
(map iri-path-rfc-absolute? all) → (#f #t #f #f #f #f) ; zero segments is not rfc-path-absolute?
(map iri-path-rfc-rootless? all) → (#f #f #t #t #f #f) ; empty path is not rfc-path-rootless?
(map iri-path-rfc-noscheme? all) → (#f #f #f #t #f #f)
(srfi :275 iri)(iri-path-segments iri)(iri-path-segment-ref iri k)(iri-path-segment-update iri k string)Procedures which effectively wrap path-segments, path-ref and path-update respectively. These are offered to expose path internals without requiring to import the path sub-library. iri-path-segment-update has additional error behaviour beyond path-update: characters not permissible within an IRI path segment will be automatically escaped as sequences of percent-encoded bytes, comparable to the behaviour of update-iri-path on segments.
(srfi :275 uri)(uri-path-absolute? uri)(uri-path-relative? uri)(uri-path-empty? uri)(uri-path-rfc-abempty? uri)(uri-path-rfc-absolute? uri)(uri-path-rfc-rootless? uri)(uri-path-rfc-noscheme? uri)Helper procedures which call the path library’s corresponding procedures for path shape of an URI. Unlike the equivalent path predicates which return #f, these raise an assertion violation with uri as irritant if it is not an URI.
(srfi :275 uri)(uri-path-segments uri)(uri-path-segment-ref uri k)(uri-path-segment-update uri k string)Procedures which effectively wrap path-segments, path-ref and path-update respectively. In uri-path-segment-update, characters not permissible within an URI path segment will be automatically escaped.
Split strings retrieved from an IRI or an URI’s user component into username and password components. Again, a colon which is escaped is not considered a delimiter between username and password. Examples:
(username+password (iri-user (string->iri"//foo:bar:qux@host"))) → "foo" "bar:qux" (username+password (iri-user (string->iri"//foo%3Abar:qux@host"))) → "foo%3Abar" "qux" (username+password (iri-user (string->iri"//@host"))) → "" #f (username+password (iri-user (string->iri "//"))) → #f #f (username+password (iri-user (string->iri "//foo@host"))) → "foo" #f
(srfi :275 utils)(user-settable? ident obj)(host-settable? ident obj)(port-settable? ident obj)(path-settable? ident obj)Helper procedures which hold providing that updating the respective component of IRI or URI ident to obj is legal. This directly reflects the RFC 3986 and 3987 ABNF, but it’s convenient to provide a single predicate as the conditions to check are somewhat elaborate with respect to path shape. These conditions are described in the error behaviour of setters section. For efficiency, obj is not parsed and the only check is that it is not #f. This means that subsequent procedures called on these arguments may error. An assertion violation is raised if the first argument is not an IRI or URI, with that argument as irritant.
The following section describes SRFI 14 character sets [14]. Note that (srfi :275 iri) and (srfi :275 uri) export some of the same identifiers where the set of characters is the same in RFC 3986 and 3987: e.g. char-set:reserved. Character sets are named for the RFC 3986 and 3987 ABNF productions, e.g. char-set:uri-userinfo.
RFC 3986 unreserved character set, and the repertoire of all unescaped characters permissible in an URI.
RFC 3986 gen-delims and sub-delims character sets, and their union, the reserved range. These character sets are also exported by the (srfi :275 iri) library for convenience.
(srfi :275 uri)char-set:schemechar-set:uri-userinfochar-set:uri-reg-namechar-set:uri-segmentchar-set:uri-querychar-set:uri-fragmentRFC 3986 component-specific character sets. In RFC 3986, but not RFC 3987, query and fragment share the same repertoire.
RFC 3987 iunreserved character set, which is the union of char-set:uri-unreserved and most of the universal character set. The permissible ranges in the UCS are exported as char-set:ucschar, and the additional char-set:iri-private range exports the additional characters permissible in RFC 3987 query components. Finally, char-set:iri is the repertoire of all characters permissible in an IRI.
(srfi :275 iri)char-set:schemechar-set:iri-userinfochar-set:iri-reg-namechar-set:iri-segmentchar-set:iri-querychar-set:iri-fragmentRFC 3987 component-specific character sets. The scheme component is the same as in RFC 3986. Query is more expansive in RFC 3987 and includes the char-set:iri-private range, unlike fragment.
Union of char-set:uri and char-set:ucschar described above. While this is minimally restritive, it does contain (a few) characters disallowed within IRI segments (like ‘#’). However, any illegal characters will be escaped in the path setters for URIs and IRIs.
This section specifies test cases and the particular behaviour they evaluate. The test suite is included for a consistent implementation of normalization as RFC 3986 and 3987 only provide examples for relative reference resolution. It is expected that these test cases would support a more comprehensive property-based test suite, e.g. the entire range of reserved and unreserved characters in a particular component.
The following test cases must succeed for both URIs and IRIs, using normalize-uri-case and normalize-iri-case respectively.
<http://example.org/ex#test>
→ <http://example.org/ex#test><HttP://example.org/ex#test>
→ <http://example.org/ex#test><http://MySelf@example.org/Examp#test>
→ <http://MySelf@example.org/Examp#test><http://Example.ORG/ex#test>
→ <http://example.org/ex#test><http://example.org/Examp#test>
→ <http://example.org/Examp#test><http://example.org/examp?Qua#test>
→ <http://example.org/examp?Qua#test><http://example.org/examp#TeSt>
→ <http://example.org/examp#TeSt><http://%aA@%AA%AB%AC%AD%AE/some/where/place>
→ <http://%AA@%AA%AB%AC%AD%AE/some/where/place><http://%aa%Ab%AC%aD%AE/some/where/place>
→ <http://%AA%AB%AC%AD%AE/some/where/place><http://myname@example.org/%Fa/%FB/%fC>
→ <http://myname@example.org/%FA/%FB/%FC><http://myname@example.org/%FA/%FB/%FC?%ff>
→ <http://myname@example.org/%FA/%FB/%FC?%FF><http://myname@example.org/%FA/%FB/%FC#%ff>
→ <http://myname@example.org/%FA/%FB/%FC#%FF>IRIs additionally have the condition that only U.S. ASCII is case-insensitive:
<http://CRÊPES.example.org>
→ <http://crÊpes.example.org>
No equivalent for scheme as that only contains ASCII, even in IRIsThe following test cases must succeed for both URIs and IRIs, using normalize-uri-escape and normalize-iri-escape respectively.
<http://my!name@example.org/ex#test>
→ <http://my!name@example.org/ex#test><http://myname@!example.org/ex#test>
→ <http://myname@!example.org/ex#test><http://myname@example.org/ex!#test>
→ <http://myname@example.org/ex!#test><http://myname@example.org/ex?!a#test>
→ <http://myname@example.org/ex?!a#test><http://myname@example.org/ex?a#!test>
→ <http://myname@example.org/ex?a#!test><http://my%40name@example.org/ex#test>
→ <http://my%40name@example.org/ex#test><http://myname@ex%40ample.org/ex#test>
→ <http://myname@ex%40ample.org/ex#test><http://myname@example.org/e%40x#test>
→ <http://myname@example.org/e%40x#test><http://myname@example.org/ex?a%40#test>
→ <http://myname@example.org/ex?a%40#test><http://myname@example.org/ex?a#t%40est>
→ <http://myname@example.org/ex?a#t%40est><http://my%2Ename@example.org/ex?a#test>
→ <http://my.name@example.org/ex?a#test><http://myname@example%2Eorg/ex?a#test>
→ <http://myname@example.org/ex?a#test><http://myname@example.org/misc%2Etxt#test>
→ <http://myname@example.org/misc.txt#test><http://myname@example.org/misc.txt?%2E%2E%2E>
→ <http://myname@example.org/misc.txt?...><http://myname@example.org/misc.txt#line%31%30>
→ <http://myname@example.org/misc.txt#line10>The following test cases only apply to URIs:
<http://dosh£@crepes.example.org>
→ <http://dosh%C2%A3@crepes.example.org><http://crêpes.example.org>
→ <http://cr%C3%AApes.example.org><http://crepes.example.org/in/Rhône>
→ <http://crepes.example.org/in/Rh%C3%B4ne><http://crepes.example.org/in/Rennes?Dim.‥Sam.>
→ <http://crepes.example.org/in/Rennes?Dim.%E2%80%A5Sam.><http://crepes.example.org/in/Rennes#L'Étage>
→ <http://crepes.example.org/in/Rennes#L'%C3%89tage>The following test cases only apply to IRIs:
<http://dosh%C2%A3@crepes.example.org>
→ <http://dosh£@crepes.example.org><http://cr%C3%AApes.example.org>
→ <http://crêpes.example.org><http://crepes.example.org/in/Rh%C3%B4ne>
→ <http://crepes.example.org/in/Rhône><http://crepes.example.org/in/Rennes?Dim.%E2%80%A5Sam.>
→ <http://crepes.example.org/in/Rennes?Dim.‥Sam.><http://crepes.example.org/in/Rennes#L'%C3%89tage>
→ <http://crepes.example.org/in/Rennes#L'Étage><https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82>
→ <https://en.wiktionary.org/wiki/Ῥόδος><https://example.org/music/%C3%89irigh'sCuirOrtDoChuid%C3%89adaigh>
→ <https://example.org/music/Éirigh'sCuirOrtDoChuidÉadaigh><https://en.wiktionary.org/wiki/Ῥόδος>
→ <https://en.wiktionary.org/wiki/Ῥόδος><https://en.wiktionary.org/wiki/Ῥόδος>
→ <https://en.wiktionary.org/wiki/Ῥόδος>
In this test case, normalize-iri-escape should be called multiple times, i.e. (compose normalize-iri-escape normalize-iri-escape)The following test cases must succeed for both URIs and IRIs, using normalize-uri-path and normalize-iri-path respectively.
<http://example.org/some/where/place>
→ <http://example.org/some/where/place><urn:/some/where/place>
→ <urn:/some/where/place><urn:some/where/place>
→ <urn:some/where/place><urn:/some/./where/././place/./>
→ <urn:/some/where/place/><urn:some/./where/././place/./>
→ <urn:some/where/place/><urn:/some//where//place//>
→ <urn:/some//where//place//><urn:some//where//place//>
→ <urn:some//where//place//></>
→ </><////>
→ <////>
While a single slash in the previous test is a bare path, // is bare authority, rather than an expected absolute path</a/b/../../c>
→ </a/b/../../c></a/b/././c>
→ </a/b/././c></a/b/../c/././d>
→ </a/b/../c/././d><a/b/../../c>
→ <a/b/../../c><a/b/././c>
→ <a/b/././c><a/b/../c/././d>
→ <a/b/../c/././d><./def>
→ <./def><./abc:def>
→ <./abc:def><../../abc/./def>
→ <../../abc/./def><foo:a/b/../.././../../e>
→ <foo:e>
From Haskell network-uri [6]<http://example.com////../..>
→ <http://example.com//>
From Webkit [13]<http://example.com/foo/bar//../..>
→ <http://example.com/foo/>
From Webkit [13]<http://example.com/foo/bar//..>
→ <http://example.com/foo/bar/>
From Webkit [13]<http://example/a/b/../../c>
→ <http://example/c>
From Haskell network-uri [6]<http://example/a/b/c/../../>
→ <http://example/a/>
From Haskell network-uri [6]<http://example/a/b/c/./>
→ <http://example/a/b/c/>
From Haskell network-uri [6]<http://example/a/b/c/.././>
→ <http://example/a/b/>
From Haskell network-uri [6]<http://example/a/b/c/d/../../../../e>
→ <http://example/e>
From Haskell network-uri [6]<http://example/a/b/c/d/../.././../../e>
→ <http://example/e>
From Haskell network-uri [6]<http://example/a/b/../.././../../e>
→ <http://example/e>
From Haskell network-uri [6]Using uri->iri:
<https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82>
→ <https://en.wiktionary.org/wiki/Ῥόδος><https://example.org/ceol/%C3%89irigh'sCuirOrtDoChuid%C3%89adaigh>
→ <https://example.org/ceol/Éirigh'sCuirOrtDoChuidÉadaigh>Using iri->uri:
<https://en.wiktionary.org/wiki/Ῥόδος>
→ <https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82><https://example.org/ceol/Éirigh'sCuirOrtDoChuidÉadaigh>
→ <https://example.org/ceol/%C3%89irigh'sCuirOrtDoChuid%C3%89adaigh>In these tests, resolve-iri and resolve-uri respectively are called, using a base IRI or URI, and a relative reference. The test cases do not have any IRI-specific encoding, so the output is expected to be verbatim for both IRIs and URIs, and for brevity, only a “base IRI” is described.
The following test cases from RDF 1.1 Turtle must succeed, which include cases from RFC 3986 / 3987, as well as additional cases such as a base IRI with a fragment. Test cases are RDF triples, in which the subject and predicate components are absolute IRIs, but where the object component is a relative IRI. A base IRI is declared at the top of the Turtle (.ttl) file, and the corresponding N-Triples (.nt) file lists the resolved triples. In the SRFI 275 sample implementation, we inline these tests, with the objects extracted and resolved against the base IRI explicitly.
<http://a/bb/ccc/d;p?q>)<http://a/bb/ccc/d/>)<file:///a/bb/ccc/d;p?q>)We require the following abnormal test cases from Chicken uri-generic [7], when resolved against base IRI <http://a/b/c/d;p?q>, to yield the following results:
..’ traversal is clamped at root, not an error<../../../g> → <http://a/g><../../../../g> → <http://a/g><../../../..> → <http://a/><../../../../> → <http://a/></./g> → <http://a/g></../g> → <http://a/g><g..> → <http://a/b/c/g..><..g> → <http://a/b/c/..g><g?y/./x> → <http://a/b/c/g?y/./x><g?y/../x> → <http://a/b/c/g?y/../x><g#s/./x> → <http://a/b/c/g#s/./x><g#s/../x> → <http://a/b/c/g#s/../x>Reference resolution must ensure that a path is never elicited which would be mistaken for the double slash after a scheme’s colon. (The selected strategy is to prefix a ./ to the path, making it relative, but in principle prepending a slash to the segments i.e. after the initial slash, /.///, is equivalent.)
<f:/a><.//g> → <f:.///g><f:/a/><..//g> → <f:.///g>The following test cases are derived from the SWAP project’s [12] uripath.py. These tests are formatted here as base URI or IRI, relative reference, and expected result.
<foo:xyz><bar:abc> → <bar:abc><http://example/x/y/z><../abc> → <http://example/x/abc><http://example2/x/y/z><//example/x/abc> → <http://example/x/abc><http://ex/x/y/z><../r> → <http://ex/x/r><http://ex/x/y><q/r> → <http://ex/x/q/r><q/r#s> → <http://ex/x/q/r#s><q/r#s/t> → <http://ex/x/q/r#s/t><ftp://ex/x/q/r> → <ftp://ex/x/q/r><y> → <http://ex/x/y><http://ex/x/y/><.> → <http://ex/x/y/><z/> → <http://ex/x/y/z/><http://ex/x/y/pdq><pdq> → <http://ex/x/y/pdq><file:/swap/test/animal.rdf><animal.rdf#Animal> → <file:/swap/test/animal.rdf#Animal><file:/e/x/y/z><../abc> → <file:/e/x/abc><file:/example2/x/y/z><../../../example/x/abc> → <file:/example/x/abc><file:/ex/x/y/z><../r> → <file:/ex/x/r><../../../r> → <file:/r><file:/ex/x/y><q/r> → <file:/ex/x/q/r><q/r#s> → <file:/ex/x/q/r#s><q/r#> → <file:/ex/x/q/r#><q/r#s/t> → <file:/ex/x/q/r#s/t><ftp://ex/x/q/r> → <ftp://ex/x/q/r><y> → <file:/ex/x/y><file:/ex/x/y/pdq><pdq> → <file:/ex/x/y/pdq><file:/ex/x/y/><.> → <file:/ex/x/y/><z/> → <file:/ex/x/y/z/><file:/devel/WWW/2000/10/swap/test/reluri-1.n3><//meetings.example.com/cal#m1> → <file://meetings.example.com/cal#m1><file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3><//meetings.example.com/cal#m1> → <file://meetings.example.com/cal#m1><file:/some/dir/foo><.#blort> → <file:/some/dir/#blort><.#> → <file:/some/dir/#><http://example/x/y%2Fz> (see here)<abc> → <http://example/x/abc><../x%2Fabc> → <http://example/x%2Fabc><http://example/x/y/z> (see here)<../../x%2Fabc> → <http://example/x%2Fabc><http://example/x%2Fy/z> (see here)<abc> → <http://example/x%2Fy/abc><http://example/x/abc.efg><.> → <http://example/x/>The following test cases are derived from the Haskell network-uri [6] package. These tests are formatted here as base URI or IRI, relative reference, and expected result. The first set of cases are tricky cases of non-relative paths together with query and fragment.
<mailto:local1@domain1?query1><local2@domain2> → <mailto:local2@domain2><local2@domain2?query2> → <mailto:local2@domain2?query2><mailto:local1@domain1><local2@domain2?query2> → <mailto:local2@domain2?query2><mailto:local@domain?query1><?query2> → <mailto:local@domain?query2><?query2> → <mailto:local@domain?query2><mailto:?query1><local@domain?query2> → <mailto:local@domain?query2><foo:bar><http://example/a/b?c/../d> → <http://example/a/b?c/../d><http://example/a/b#c/../d> → <http://example/a/b#c/../d>The remaining set of tests from network-uri [6] are with respect to dealing with the final segment when inverting (see next section). See here. These test cases are not exactly invertible, however as they do not all produce the exact same reference due to relative segments.
<http://www.example.com/data/limit/..><test.xml> → <http://www.example.com/data/limit/test.xml><file:/some/dir/foo><./#blort> → <file:/some/dir/#blort><./#> → <file:/some/dir/#><file:/some/dir/..><./#blort> → <file:/some/dir/#blort><http://example.org/base/uri><http:this> → <http:this><http:base><http:this> → <http:this><f://example.org/base/a><b/c//d/e> → <f://example.org/base/b/c//d/e><mid:m@example.ord/c@example.org><m2@example.ord/c2@example.org> → <mid:m@example.ord/m2@example.ord/c2@example.org><file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/><mini1.xml> → <file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml><foo:a/y/z><../b/c> → <foo:a/b/c>The relativize-iri and relativize-uri procedures are effectively the inverse of resolve-iri and resolve-uri. However, not all of the test cases described above succeed verbatim, as e.g. the references may include dotted segments which would be normalized during the relative reference resolution process. Of the test cases descibed above, we specifically require that the test cases derived from the Python SWAP project as all of these test cases are invertible exactly.
Recall that test data above are formatted as a base URI or IRI, a relative reference, and an expected result of reference resolution. A test of relativization succeeds if given the base IRI or URI and the expected result of resolution, the relative reference is produced. As for the reference resolution cases, the test cases are expected to succeed verbatim for both IRIs and URIs, but only a “base IRI” is described for brevity.
The following inversion must hold, but note that we elicit relative reference </g>, which is only the same as previous reference <.//g> after calling remove-dot-segments.
<f:/a><f:.///g> → </g> <f:/a/><f:.///g> → <..//g>Finally, the following test cases included with Chicken’s uri-generic library [7] must hold for base <http://a/b/c/d;p?q>:
<http://a/b/c> → <../c></>, but that’s not convenient<http://a/> → <../..><http://a> → <//a><ftp://a/b/c/d;p?q> → <ftp://a/b/c/d;p?q><ftp://x/y/z;a?b> → <ftp://x/y/z;a?b><http://a/b/c/d;p?q> → <d;p><http://a/b/c/e> → <e><http://a/b/c/> → <.><http://a/b/e> → <../e><http://a/b/> → <..><http://b> → <//b><http://b/> → <//b/><http://b/c> → <//b/c>encode-string and decode-string should account for the following cases:
(encode-string "a béc♂d😎e" (char-set-complement char-set:uri))
→ "a%20b%C3%A9c%E2%99%82d%F0%9F%98%8Ee"♂’ (U+2642, encoded as "%E2%99%82")(decode-string "a%20b%C3%A9c%E2%99%82d%F0%9F%98%8Ee" (char-set-complement char-set:uri))
→ "a béc%E2%99%82d😎e"(encode-string "foo%20bar" (char-set-complement char-set:uri))
→ "foo%2520bar"(decode-string "foo%2520bar" char-set:full)
→ "foo%20bar"The following are negative test cases which must be rejected by decode-string (recall encode-string does not interpret escapes). These go beyond the RFC 3986 and 3987 specifications which permit percent-encoded bytes which may correspond to invalid UTF-8 octet sequences. No encodings other than UTF-8 are supported. The same sequences must be rejected in every component in which percent-encoded bytes are interpreted during parsing and updating (every component except scheme and port).
U+0020 (space)"a%C0%A0b"U+0020"a%E0%80%A0b"U+0020"a%E0%80%80%A0b"U+00E9 (‘é’)"a%E0%80%A9b""a%C0b"U+2642 / ‘♂’)"a%E2%99"U+1F60E / ‘😎’)"a%F0%9F%98""a%F0%9F%98x""a%A9"We expect the following edge-cases to have the corresponding hostname verbatim:
<http://[::]/> → "[::]"<http://[::1]/> → "[::1]"::’<http://[1::]/"> → "[1::]"::’, prefix<http://[2001:db8::]/> → "[2001:db8::]"::’<http://[::2001:db8]/> → "[::2001:db8]"<http://[::ffff:192.0.2.1]/> → "[::ffff:192.0.2.1]"<http://[64:ff9b::192.0.2.1]/> → "[64:ff9b::192.0.2.1]"<http://[2001:db8::1]:8080/path?query#fragment> → "[2001:db8::1]"Negative test cases which must be rejected:
::’ compression marker<http://[2001:db8:::1]/><http://[2001:db8:a:b:c:d:e:f:1]/>::’ (too few)<http://[2001:db8:a:b:c:d:e]/><http://[2001:db8::fffff]/><http://[2001:00db8::0001]/><http://[2001:db8::192.0.2]/>::’ markers<http://[2001:db8:a::b::c]/>::’<http://[2001:db8]/><http://[2001:db8>| scheme | user | host | port | path | query | fragment |
|---|---|---|---|---|---|---|
Empty URI: <> | ||||||
| N/A | #f | #f | #f | #f | #f | #f |
Empty authority: <//> | ||||||
| N/A | #f | "" | #f | #f | #f | #f |
Empty user: <//@> | ||||||
| N/A | "" | #f | #f | #f | #f | #f |
Empty port: <//:> | ||||||
| N/A | #f | "" | #f | #f | #f | #f |
Empty query: <?> | ||||||
| N/A | #f | #f | #f | #f | "" | #f |
Empty fragment: <#> | ||||||
| N/A | #f | #f | #f | #f | #f | "" |
Path which looks like a hostname: <example.org> | ||||||
| N/A | #f | #f | #f | "example.org" | #f | #f |
URN-like: <urn:something> | ||||||
"urn" | #f | #f | #f | "something" | #f | #f |
URN-like, path looks like hostname: <urn:example.org> | ||||||
"urn" | #f | #f | #f | "example.org" | #f | #f |
Path which looks like a URN: <./urn:something> | ||||||
| N/A | #f | #f | #f | "./urn:something" | #f | #f |
User with colon segment: <http://a:b@c:29> | ||||||
"http" | "a:b" | "c" | 29 | #f | #f | #f |
User-like component appears as path: <http::@c:29> | ||||||
"http" | #f | #f | #f | ":@c:29" | #f | #f |
Host-like component appears as user: <http://example.org:b@d/> | ||||||
"http" | "example.org:b" | "d" | #f | "/" | #f | #f |
Padded port as numeric value: <http://example.org:000080> | ||||||
"http" | #f | "example.org" | 80 | #f | #f | #f |
Query component with question mark: <http://example.org/abcd?efgh?ijkl> | ||||||
"http" | #f | "example.org" | #f | "/abcd" | "efgh?ijkl" | #f |
Fragment component with question mark: <http://example.org/abcd#efgh?ijkl> | ||||||
"http" | #f | "example.org" | #f | "/abcd" | #f | "efgh?ijkl" |
Path where first segment looks like host: <http:///some/where/place> | ||||||
"http" | #f | "" | #f | "/some/where/place" | #f | #f |
Scheme with nil host: <foo:> | ||||||
"foo" | #f | #f | #f | #f | #f | #f |
Scheme with path, empty host: <foo:////g> | ||||||
"foo" | #f | "" | #f | "//g" | #f | #f |
Scheme with path, nil host: <foo:.///g> | ||||||
"foo" | #f | #f | #f | ".///g" | #f | #f |
Scheme with non-empty host: <foo://g> | ||||||
"foo" | #f | "g" | #f | #f | #f | #f |
All components filled out: <http://user@example.org:80/some/where/place?qua#ought> | ||||||
"http" | "user" | "example.org" | 80 | "/some/where/place" | "qua" | "ought" |
All components except user filled out: <http://example.org:80/some/where/place?qua#ought> | ||||||
"http" | #f | "example.org" | 80 | "/some/where/place" | "qua" | "ought" |
All components except host filled out: <http://user@:80/some/where/place?qua#ought> | ||||||
"http" | "user" | #f | 80 | "/some/where/place" | "qua" | "ought" |
All components except port filled out: <http://user@example.org/some/where/place?qua#ought> | ||||||
"http" | "user" | "example.org" | #f | "/some/where/place" | "qua" | "ought" |
All components except path filled out: <http://user@example.org:80?qua#ought> | ||||||
"http" | "user" | "example.org" | 80 | #f | "qua" | "ought" |
All components except query filled out: <http://user@example.org:80/some/where/place#ought> | ||||||
"http" | "user" | "example.org" | 80 | "/some/where/place" | #f | "ought" |
All components except fragment filled out: <http://user@example.org:80/some/where/place?qua> | ||||||
"http" | "user" | "example.org" | 80 | "/some/where/place" | "qua" | #f |
Empty host, nil user/port: <http:///some/where/place?qua#ought> | ||||||
"http" | #f | "" | #f | "/some/where/place" | "qua" | "ought" |
Empty user, nil host/port: <http://@/some/where/place?qua#ought> | ||||||
"http" | "" | #f | #f | "/some/where/place" | "qua" | "ought" |
Empty port implies empty host: <http://:/some/where/place?qua#ought> | ||||||
"http" | #f | "" | #f | "/some/where/place" | "qua" | "ought" |
Relative reference, nil host: <////g> | ||||||
| N/A | #f | "" | #f | "//g" | #f | #f |
Relative reference, path, nil host: <.///g> | ||||||
| N/A | #f | #f | #f | ".///g" | #f | #f |
Relative reference, non-empty host: <//g> | ||||||
| N/A | #f | "g" | #f | #f | #f | #f |
Path which looks like a query: <./p=q:r> | ||||||
| N/A | #f | #f | #f | "./p=q:r" | #f | #f |
Relative reference, all components filled out: <//user@example.org:80/some/where/place?qua#ought> | ||||||
| N/A | "user" | "example.org" | 80 | "/some/where/place" | "qua" | "ought" |
Relative reference, all components except user filled out: <//example.org:80/some/where/place?qua#ought> | ||||||
| N/A | #f | "example.org" | 80 | "/some/where/place" | "qua" | "ought" |
Relative reference, all components except host filled out: <//user@:80/some/where/place?qua#ought> | ||||||
| N/A | "user" | #f | 80 | "/some/where/place" | "qua" | "ought" |
Relative reference, all components except port filled out: <//user@example.org/some/where/place?qua#ought> | ||||||
| N/A | "user" | "example.org" | #f | "/some/where/place" | "qua" | "ought" |
Relative reference, all components except path filled out: <//user@example.org:80?qua#ought> | ||||||
| N/A | "user" | "example.org" | 80 | #f | "qua" | "ought" |
Relative reference, all components except query filled out: <//user@example.org:80/some/where/place#ought> | ||||||
| N/A | "user" | "example.org" | 80 | "/some/where/place" | #f | "ought" |
Relative reference, all components except fragment filled out: <//user@example.org:80/some/where/place?qua> | ||||||
| N/A | "user" | "example.org" | 80 | "/some/where/place" | "qua" | #f |
Relative reference empty host, nil user/port: <///some/where/place?qua#ought> | ||||||
| N/A | #f | "" | #f | "/some/where/place" | "qua" | "ought" |
Relative reference empty user, nil host/port: <//@/some/where/place?qua#ought> | ||||||
| N/A | "" | #f | #f | "/some/where/place" | "qua" | "ought" |
Relative reference empty port implies empty host: <//:/some/where/place?qua#ought> | ||||||
| N/A | #f | "" | #f | "/some/where/place" | "qua" | "ought" |
The sample implementation targets Chez Scheme and is written in (mostly) portable R6RS. It imports various SRFIs from the Chez-SRFI grab-bag. At the time of writing, the only external dependency is the sample implementation of the draft SRFI 262 pattern matcher [17].
network-uri packageuri-genericuri-commonpath-expected.txt978-0-521-19399-3Thanks to Ivan Raikov and Peter Bex for suggestions on improvements, especially with respect to rejecting invalid UTF-8 sequences, and the relativization procedures.
The majority of test cases beyond RFC 3986 and 3987 are drawn from Graham Klyne's network-uri Haskell library [6], with additional test cases from the Chicken uri-generic library [7] suggested by Peter Bex and Ivan Raikov during the SRFI drafting process.
HTML formatting is derived from SRFI 276 by Peter McGoron.
© 2026 Duncan Guthrie.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
(srfi :275 iri)string->iri
string->non-relative-iri
string->rfc-absolute-iriget-iri
get-non-relative-iri
get-rfc-absolute-iriempty-iriiri?
relative-iri?
non-relative-iri?
rfc-absolute-iri?iri-equal?
string->iri
iri->stringiri-schemeiri-user
iri-host
iri-port
iri-authority
iri-username+passwordiri-path
iri-path-stringiri-query
iri-fragmentupdate-iri-schemeupdate-iri-user
update-iri-host
update-iri-port
update-iri-authorityupdate-iri-path
update-iri-query
update-iri-fragmentiri-path-relative?
iri-path-absolute?
iri-path-empty?iri-path-rfc-rootless?
iri-path-rfc-noscheme?iri-path-rfc-abempty?
iri-path-rfc-absolute?
iri-path-segment-ref
iri-path-segment-update
iri-path-segmentschar-set:iri
char-set:iri-unreserved
char-set:ucschar
char-set:iri-privatechar-set:gen-delims
char-set:sub-delims
char-set:reservedchar-set:scheme
char-set:iri-userinfo
char-set:iri-reg-namechar-set:iri-segment
char-set:iri-query
char-set:iri-fragment
(srfi :275 uri)string->uri
string->non-relative-uri
string->rfc-absolute-uriget-uri
get-non-relative-uri
get-rfc-absolute-uriempty-uriuri?
relative-uri?
non-relative-uri?
rfc-absolute-uri?uri-equal?
string->uri
uri->stringuri-schemeuri-user
uri-host
uri-port
uri-authority
uri-username+passworduri-path
uri-path-stringuri-query
uri-fragmentupdate-uri-schemeupdate-uri-user
update-uri-host
update-uri-port
update-uri-authorityupdate-uri-path
update-uri-query
update-uri-fragmenturi-path-relative?
uri-path-absolute?
uri-path-empty?uri-path-rfc-rootless?
uri-path-rfc-noscheme?uri-path-rfc-abempty?
uri-path-rfc-absolute?uri-path-segment-ref
uri-path-segment-update
uri-path-segmentschar-set:uri
char-set:uri-unreservedchar-set:gen-delims
char-set:sub-delims
char-set:reservedchar-set:scheme
char-set:uri-userinfo
char-set:uri-reg-namechar-set:uri-segment
char-set:uri-query
char-set:uri-fragment
(srfi :275 normalize)iri->uri
uri->iriresolve-iri
relativize-iriresolve-uri
relativize-urinormalize-iri-case
normalize-iri-escape
normalize-iri-pathnormalize-uri-case
normalize-uri-escape
normalize-uri-path(srfi :275 path)empty-relative-path
empty-absolute-path
build-pathvector->relative-path
vector->absolute-pathstring->path
string->relative-path
string->absolute-pathvector->rfc-path-rootless
vector->rfc-path-noschemevector->rfc-path-abempty
vector->rfc-path-absolutestring->rfc-path-rootless
string->rfc-path-noschemestring->rfc-path-abempty
string->rfc-path-absolutepath?
path-string?
relative-path?
absolute-path?
empty-path?rfc-path-rootless?
rfc-path-noscheme?rfc-path-abempty?
rfc-path-absolute?path-length
path-ref
path-update
path-equal?remove-dot-segments
merge-pathspath->string
path-segmentschar-set:path(srfi :275 utils)encode-string
decode-stringusername+passworduser-settable?
host-settable?
port-settable?
path-settable?