///|
priv struct IriReference {
scheme : String?
authority : String?
path : String
query : String?
fragment : String?
}
///|
fn split_component(text : String, separator : String) -> (String, String?) {
match text.split_once(separator) {
Some((left, right)) => (left.to_owned(), Some(right.to_owned()))
None => (text, None)
}
}
///|
fn scheme_letter(c : Char) -> Bool {
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
///|
fn valid_scheme(text : String) -> Bool {
let chars = text.to_array()
!chars.is_empty() &&
scheme_letter(chars[0]) &&
chars.all(c => {
scheme_letter(c) || digit(c) || c == '+' || c == '-' || c == '.'
})
}
///|
fn IriReference::parse(value : String) -> IriReference {
let (main, fragment) = split_component(value, "#")
let (main, query) = split_component(main, "?")
let (candidate, rest) = split_component(main, ":")
let (scheme, body) = if rest is Some(tail) && valid_scheme(candidate) {
(Some(candidate), tail)
} else {
(None, main)
}
let (authority, path) = if body.has_prefix("//") {
let (host, path) = split_component(body[2:].to_owned(), "/")
(
Some(host),
match path {
Some(tail) => "/" + tail
None => ""
},
)
} else {
(None, body)
}
{ scheme, authority, path, query, fragment, }
}
///|
fn remove_dot_segments(path : String) -> String {
let parts = path.split("/").to_array()
let result = []
let rooted = path.has_prefix("/")
for i, part in parts {
let segment = part.to_owned()
if segment == ".." {
if result.length() > (if rooted { 1 } else { 0 }) {
ignore(result.pop())
}
} else if segment != "." {
result.push(segment)
continue
}
if i == parts.length() - 1 {
result.push("")
}
}
result.join("/")
}
///|
fn parent_path(path : String) -> String {
let mut end = path.length()
while end > 0 && path[end - 1] != '/' {
end -= 1
}
path[:end].to_owned()
}
///|
fn resolve_query_iri(value : String, base : String?) -> String raise QueryError {
let reference = IriReference::parse(value)
if reference.scheme is Some(_) {
check_iri(value)
return value
}
guard base is Some(base) else {
raise Unsupported(0, "relative IRI requires BASE or an explicit base URI")
}
let origin = IriReference::parse(base)
guard origin.scheme is Some(scheme) else {
raise InvalidInput("query base must have an absolute IRI scheme")
}
let authority = match reference.authority {
Some(host) => Some(host)
None => origin.authority
}
let (path, query) = if reference.authority is Some(_) ||
reference.path.has_prefix("/") {
(remove_dot_segments(reference.path), reference.query)
} else if reference.path.is_empty() {
(
origin.path,
match reference.query {
Some(q) => Some(q)
None => origin.query
},
)
} else {
let prefix = if origin.authority is Some(_) && origin.path.is_empty() {
"/"
} else {
parent_path(origin.path)
}
(remove_dot_segments(prefix + reference.path), reference.query)
}
let result = StringBuilder()
result.write_string(scheme + ":")
if authority is Some(host) {
result.write_string("//" + host)
}
result.write_string(path)
if query is Some(q) {
result.write_string("?" + q)
}
if reference.fragment is Some(fragment) {
result.write_string("#" + fragment)
}
let resolved = result.to_string()
check_iri(resolved)
resolved
}