///| // IRI 解析器 - RFC 3987
///|
// IRI 结构: scheme://authority/path?query#fragment
pub(all) struct Iri {
scheme : StringView
authority : StringView
path : StringView
query : StringView
fragment : StringView
} derive(Debug, Eq, Default)
///|
pub fn Iri::Iri(
scheme : StringView,
authority : StringView,
path : StringView,
query : StringView,
fragment : StringView,
) -> Iri {
Iri::{ scheme, authority, path, query, fragment }
}
///|
// 解析中间状态: 存储各组件的 (偏移, 长度)
pub(all) struct IriSpan {
mut scheme : Span
mut authority : Span
mut path : Span
mut query : Span
mut fragment : Span
mut pos : Int // 当前解析位置
} derive(Eq, Default)
// ============================================================
// 公共 API
// ============================================================
///|
/// 解析 IRI
pub fn parse(view : BytesView) -> Result[Iri, ParseError] {
match parse_iri(view, 0) {
Ok(span) => Ok(span.build(view))
Err(e) => Err(ParseError::IriErr(e.message()))
}
}
///|
/// 解析 IRI,忽略错误
pub fn parse_unchecked(view : BytesView) -> Iri {
let span = IriSpan::default()
span.pos = 0
span.parse_scheme(view)
span.parse_authority(view)
span.parse_path(view)
span.parse_query(view)
span.parse_fragment(view)
span.build(view)
}
///|
/// 合并 base IRI 和参考 IRI (RFC 3986 5.2.2)
pub fn Iri::resolve(
self : Iri,
ref_iri : BytesView,
remove_dot_segments? : Bool = true,
) -> Result[Iri, ParseError] {
let ref_parsed = match parse(ref_iri) {
Ok(r) => r
Err(e) => return Err(e)
}
if ref_parsed.scheme.length() > 0 {
return Ok(ref_parsed)
}
let scheme = self.scheme
let authority = if ref_parsed.authority.length() > 0 {
ref_parsed.authority
} else {
self.authority
}
let path = if ref_parsed.path.length() > 0 && ref_parsed.path[0] == '/' {
ref_parsed.path
} else if ref_parsed.path.length() > 0 {
let base_path = self.path
let last_slash = base_path.rev_find("/")
match last_slash {
Some(idx) => base_path[0:idx + 1].to_owned() + ref_parsed.path.to_owned()
None =>
if base_path.is_empty() {
ref_parsed.path
} else {
"/" + ref_parsed.path.to_owned()
}
}
} else {
self.path
}
let path = if remove_dot_segments {
fn_remove_dot_segments(path)
} else {
path
}
let query = if ref_parsed.query.length() > 0 {
ref_parsed.query
} else {
self.query
}
let fragment = if ref_parsed.fragment.length() > 0 {
ref_parsed.fragment
} else {
self.fragment
}
Ok(Iri::{ scheme, authority, path, query, fragment })
}
///|
/// 计算相对 IRI
pub fn Iri::relativize(self : Iri, target : Iri) -> Result[Iri, ParseError] {
if self.scheme != target.scheme || self.authority != target.authority {
return Ok(target)
}
let path = if self.path == target.path {
""
} else if target.path.has_prefix(self.path) {
let suffix = target.path[self.path.length():].to_owned()
if !suffix.has_prefix("/") && suffix != "" {
"./" + suffix
} else {
suffix
}
} else {
target.path.to_owned()
}
Ok(Iri::{
scheme: "",
authority: "",
path,
query: target.query,
fragment: target.fragment,
})
}
///|
/// 合并 IRI,忽略错误
pub fn Iri::resolve_unchecked(
self : Iri,
ref_iri : BytesView,
remove_dot_segments? : Bool = true,
) -> Iri {
let ref_parsed = parse_unchecked(ref_iri)
if ref_parsed.scheme.length() > 0 {
return ref_parsed
}
let scheme = self.scheme
let authority = if ref_parsed.authority.length() > 0 {
ref_parsed.authority
} else {
self.authority
}
let path = if ref_parsed.path.has_prefix("/") {
ref_parsed.path
} else if ref_parsed.path.length() > 0 {
let base_path = self.path
let last_slash = base_path.rev_find("/")
match last_slash {
Some(idx) =>
if idx == 0 {
base_path.to_owned() + "/" + ref_parsed.path.to_owned()
} else {
base_path[0:idx + 1].to_owned() + ref_parsed.path.to_owned()
}
None => ref_parsed.path.to_owned()
}
} else {
self.path
}
let path = if remove_dot_segments {
fn_remove_dot_segments(path)
} else {
path
}
let query = if ref_parsed.query.length() > 0 {
ref_parsed.query
} else {
self.query
}
let fragment = ref_parsed.fragment
Iri::{ scheme, authority, path, query, fragment }
}
///|
/// 序列化为字符串
pub fn Iri::into_string(self : Iri) -> String {
let s = StringBuilder::new()
if self.scheme.length() > 0 {
s.write_view(self.scheme)
s.write_char(':')
}
if self.authority.length() > 0 {
s.write_string("//")
s.write_view(self.authority)
}
s.write_view(self.path)
if self.query.length() > 0 {
s.write_char('?')
s.write_view(self.query)
}
if self.fragment.length() > 0 {
s.write_char('#')
s.write_view(self.fragment)
}
s.to_string()
}
///|
/// 规范化路径: 处理 . 和 .. (RFC 3986 5.2.4)
pub fn fn_remove_dot_segments(path : StringView) -> StringView {
if path.is_empty() {
return path
}
let starts_with_slash = path[0] == '/'
let parts = path.split("/")
let result : Array[StringView] = []
for part in parts {
match part {
"" | "." => ()
".." =>
if starts_with_slash {
if result.length() > 0 && result[result.length() - 1] != ".." {
let _ = result.pop()
}
} else if result.length() > 0 && result[result.length() - 1] != ".." {
let _ = result.pop()
}
_ => result.push(part)
}
}
if result.is_empty() {
return if starts_with_slash { "/" } else { "" }
}
let final_path = result.join("/")
if starts_with_slash {
"/" + final_path
} else {
final_path
}
}
// ============================================================
// 字符集
// ============================================================
///|
/// scheme 字符: [0-9A-Za-z+-.]
let is_scheme_char : FixedArray[Bool] = {
let arr = FixedArray::make(256, false)
for c in b'0'.to_int()..<=b'9'.to_int() {
arr[c] = true
}
for c in b'A'.to_int()..<=b'Z'.to_int() {
arr[c] = true
}
for c in b'a'.to_int()..<=b'z'.to_int() {
arr[c] = true
}
arr[b'+'.to_int()] = true
arr[b'-'.to_int()] = true
arr[b'.'.to_int()] = true
arr
}
///|
/// authority 字符 (排除空格、#、?、/)
let is_auth_char : FixedArray[Bool] = {
let arr = FixedArray::make(256, false)
for c in b'!'.to_int()..<=b'~'.to_int() {
arr[c] = true
}
arr[b' '.to_int()] = false
arr[b'#'.to_int()] = false
arr[b'?'.to_int()] = false
arr[b'/'.to_int()] = false
arr[b'\n'.to_int()] = true
arr
}
///|
/// path 字符 (排除 #、?、空格、换行、制表符)
let is_path_char : FixedArray[Bool] = {
let arr = FixedArray::make(256, false)
for c in b'!'.to_int()..<=b'~'.to_int() {
arr[c] = true
}
arr[b' '.to_int()] = false
arr[b'#'.to_int()] = false
arr[b'?'.to_int()] = false
arr[b'\n'.to_int()] = false
arr[b'\t'.to_int()] = false
arr
}
// ============================================================
// 解析函数
// ============================================================
///|
/// 解析 scheme
fn IriSpan::parse_scheme(self : IriSpan, data : BytesView) -> Unit {
let start = self.pos
let len = data.length()
while self.pos < len && is_scheme_char[data[self.pos].to_int()] {
self.pos += 1
}
if self.pos > start && self.pos < len && data[self.pos] == b':' {
self.scheme = (start, self.pos - start)
self.pos += 1
} else {
self.pos = start
}
}
///|
/// 解析 authority (//host:port)
fn IriSpan::parse_authority(self : IriSpan, data : BytesView) -> Unit {
let len = data.length()
if self.pos + 1 < len && data[self.pos] == b'/' && data[self.pos + 1] == b'/' {
self.pos += 2
let start = self.pos
while self.pos < len && is_auth_char[data[self.pos].to_int()] {
self.pos += 1
}
if self.pos > start {
self.authority = (start, self.pos - start)
}
}
}
///|
/// 解析 path
fn IriSpan::parse_path(self : IriSpan, data : BytesView) -> Unit {
let start = self.pos
let len = data.length()
while self.pos < len && is_path_char[data[self.pos].to_int()] {
self.pos += 1
}
if self.pos > start {
self.path = (start, self.pos - start)
}
}
///|
/// 解析 query (?key=value)
fn IriSpan::parse_query(self : IriSpan, data : BytesView) -> Unit {
let len = data.length()
if self.pos < len && data[self.pos] == b'?' {
self.pos += 1
let start = self.pos
while self.pos < len &&
data[self.pos] != b'#' &&
data[self.pos] != b' ' &&
data[self.pos] != b'\n' {
self.pos += 1
}
if self.pos > start {
self.query = (start, self.pos - start)
}
}
}
///|
/// 解析 fragment (#anchor)
fn IriSpan::parse_fragment(self : IriSpan, data : BytesView) -> Unit {
let len = data.length()
if self.pos < len && data[self.pos] == b'#' {
self.pos += 1
let start = self.pos
while self.pos < len &&
data[self.pos] != b' ' &&
data[self.pos] != b'\n' &&
data[self.pos] != b'\t' {
self.pos += 1
}
if self.pos > start {
self.fragment = (start, self.pos - start)
}
}
}
///|
/// 主解析函数
pub fn parse_iri(data : BytesView, start : Int) -> Result[IriSpan, ParseError] {
let span = IriSpan::default()
span.pos = start
let len = data.length()
span.parse_scheme(data)
span.parse_authority(data)
span.parse_path(data)
span.parse_query(data)
span.parse_fragment(data)
if span.query.1 == 0 && span.fragment.1 == 0 && span.pos < len {
span.path = (span.path.0, len - span.path.0)
span.pos = len
}
Ok(span)
}
///|
/// 从字节位置构建 Iri
fn IriSpan::build(self : IriSpan, data : BytesView) -> Iri {
let scheme = if self.scheme.1 > 0 {
data[self.scheme.0:self.scheme.0 + self.scheme.1]
} else {
[]
}
let authority = if self.authority.1 > 0 {
data[self.authority.0:self.authority.0 + self.authority.1]
} else {
[]
}
let path = if self.path.1 > 0 {
data[self.path.0:self.path.0 + self.path.1]
} else {
[]
}
let query = if self.query.1 > 0 {
data[self.query.0:self.query.0 + self.query.1]
} else {
[]
}
let fragment = if self.fragment.1 > 0 {
data[self.fragment.0:self.fragment.0 + self.fragment.1]
} else {
[]
}
Iri::{
scheme: @utf8.decode_lossy(scheme),
authority: @utf8.decode_lossy(authority),
path: @utf8.decode_lossy(path),
query: @utf8.decode_lossy(query),
fragment: @utf8.decode_lossy(fragment),
}
}