///|
struct Iri {
base_iri : String
mut state : ParserState
} derive(Debug)
///|
impl Show for Iri with fn to_string(self) {
self.base_iri
}
///|
struct IriRef(Ref[Iri]) derive(Debug)
///|
pub struct IriPositions {
mut scheme_end : Int
mut authority_end : Int
mut path_end : Int
mut query_end : Int
} derive(Default, Debug)
///|
// fn IriRef::of_ref(iri : Iri) -> IriRef {
// IriRef(Ref::Ref(iri))
// }
///|
fn IriRef::new_unchecked(iri : Iri) -> IriRef {
// guard !iri.base_iri.is_empty()
IriRef(Ref::Ref(iri))
}
///|
fn IriRef::into_owned(iri_ref : IriRef) -> Iri {
{ base_iri: iri_ref.val.base_iri, state: iri_ref.val.state }
}
///|
priv struct ParserState {
mut pos : Int // 当前解析位置(字节索引)
positions : IriPositions // 位置记录
} derive(Debug)
///|
fn ParserState::ParserState(
pos~ : Int,
positions~ : IriPositions,
) -> ParserState {
{ pos, positions }
}
///|
fn IriPositions::IriPositions(
scheme_end~ : Int,
authority_end~ : Int,
path_end~ : Int,
query_end~ : Int,
) -> IriPositions {
{ scheme_end, authority_end, path_end, query_end }
}
///|
pub fn Iri::Iri(base_iri : String) -> Iri {
{
base_iri,
state: ParserState::ParserState(
pos=0,
positions=IriPositions::IriPositions(
scheme_end=0,
authority_end=0,
path_end=0,
query_end=0,
),
),
}
}
///|
pub fn Iri::new(base_iri : String) -> IriRef {
Iri::Iri(base_iri).as_ref()
}
///|
pub fn Iri::as_ref(iri : Self) -> IriRef {
// IriRef(Ref::Ref(iri))
IriRef::new_unchecked(iri)
}
///|
pub fn IriRef::peek_match(self : IriRef, pattern : CharMatch) -> Bool {
let mut current_pos = self.val.state.pos
let input_len = self.val.as_stringview().length()
for i in 0..= input_len {
return false
}
match StringView::get_char(self.val.base_iri, current_pos) {
Some(c) => {
if c != pattern[i] {
return false
}
current_pos += c.utf16_len()
}
None => return false
}
}
true
}
///|
pub fn IriRef::try_match_range(
self : IriRef,
pattern : Array[CharMatchRule],
) -> Bool {
let mut current_pos = self.val.state.pos
for rule in pattern {
if current_pos >= self.val.as_stringview().length() {
return false
}
match StringView::get_char(self.val.base_iri, current_pos) {
Some(c) => {
let (expected, range_end) = rule
let is_match = match range_end {
Some(end) => c >= expected && c <= end
None => c == expected
}
if !is_match {
return false
}
current_pos += c.utf16_len()
}
None => return false
}
}
// 匹配成功,更新状态
self.val.state.pos = current_pos
true
}
///|
pub fn IriRef::peek_match_range(
self : IriRef,
pattern : Array[CharMatchRule],
) -> Int? {
let mut current_pos = self.val.state.pos
let input_len = self.val.as_stringview().length()
for rule in pattern {
// 检查是否越界
if current_pos >= input_len {
return None
}
// 获取当前字符
match StringView::get_char(self.val.base_iri, current_pos) {
Some(c) => {
let (expected, range_end) = rule
let is_match = match range_end {
Some(end) =>
// 范围匹配: expected <= c <= end
// 注意:Char 的比较在 MoonBit 中基于 Unicode 码点
c >= expected && c <= end
None =>
// 精确匹配
c == expected
}
if !is_match {
return None
}
// 移动位置到下一个字符的起始字节
current_pos += c.utf16_len() // 假设 StringView 基于 UTF-16 或 UTF-8,需与实际实现一致
}
None =>
// 无效字符序列
return None
}
}
// 返回消耗的字节数
Some(current_pos - self.val.state.pos)
}
///|
/// 严格解析 IRI。如果格式不符合规范,返回错误。
pub fn IriRef::parse(self : IriRef) -> Result[Iri, IriError] {
self.parse_impl(unchecked=false)
}
///|
/// 宽松解析 IRI。忽略非致命错误,尽力解析并返回结果。
pub fn IriRef::parse_unchecked(self : IriRef) -> Iri {
match self.parse_impl(unchecked=true) {
Ok(iri) => iri
Err(_) =>
// 在 unchecked 模式下,parse_impl 理论上不应返回 Err,
// 但为了类型安全,如果发生意外错误,返回一个包含原始字符串的默认 Iri
self.into_owned()
}
}
///|
fn IriRef::parse_impl(
self : IriRef,
unchecked? : Bool = false,
) -> Result[Iri, IriError] {
// 保存起始位置,以便在无 scheme 时重置
let start_pos = self.val.state.pos
// 1. 解析 scheme
let has_scheme = match self.parse_scheme() {
Ok(has) => has
Err(e) =>
if unchecked {
// 在 unchecked 模式下,如果 scheme 解析出错(极少见,因为 parse_scheme 内部主要返回 Ok(false)),
// 我们假设没有 scheme 并重置位置,或者根据具体错误策略处理。
// 这里为了稳健,如果 parse_scheme 报错,我们在 unchecked 下视为无 scheme
self.val.state.pos = start_pos
false
} else {
return Err(e)
}
}
if has_scheme {
// 期望 ':'
match self.expect(':') {
Ok(_) => self.val.state.positions.scheme_end = self.val.state.pos
Err(e) =>
if unchecked {
// 如果缺少 ':' 但识别出了 scheme 字符,在 unchecked 模式下可能意味着这不是一个标准的 scheme
// 或者我们强制认为它是 scheme 的一部分直到遇到非合法字符。
// 简单策略:重置 scheme 标记,当作相对引用处理,或者保留当前位置但不标记 scheme_end
// 这里选择重置,退化为相对路径解析
self.val.state.pos = start_pos
// 注意:此时 has_scheme 逻辑上应视为 false,但位置已移动。
// 更安全的 unchecked 做法是:如果 expect 失败,不回退 pos,但也不记录 scheme_end
} else {
return Err(e)
}
}
// 检查是否为 authority (//)
// 只有在确实解析了 scheme 且后面跟着 :// 时才解析 authority
// 注意:上面的 if has_scheme 块中,如果 expect(':') 失败且 unchecked,我们重置了 pos。
// 所以需要重新检查当前状态是否真的拥有 scheme。
// 简化逻辑:如果 scheme_end > 0,说明 scheme 解析成功
if self.val.state.positions.scheme_end > 0 && self.peek_match(['/', '/']) {
self.val.state.pos += 2 // 跳过 "//"
match self.parse_authority() {
Ok(_) => ()
Err(e) => if !unchecked { return Err(e) }
// unchecked: 忽略 authority 解析错误,继续解析路径
}
}
} else {
// 没有 scheme,重置位置到开始,作为相对引用解析
self.val.state.pos = start_pos
}
// 2. 解析 path
match self.parse_path() {
Ok(_) => ()
Err(e) => if !unchecked { return Err(e) }
// unchecked: 忽略 path 错误
}
self.val.state.positions.path_end = self.val.state.pos
// 3. 解析 query (可选)
if self.peek_match(['?']) {
match self.parse_query() {
Ok(_) => ()
Err(e) => if !unchecked { return Err(e) }
}
}
self.val.state.positions.query_end = self.val.state.pos
// 4. 解析 fragment (可选)
if self.peek_match(['#']) {
match self.parse_fragment() {
Ok(_) => ()
Err(e) => if !unchecked { return Err(e) }
}
}
// 始终返回 Ok,因为在 unchecked 模式下我们忽略了错误
// 在 checked 模式下,如果到达这里,说明所有步骤都成功了
Ok({ base_iri: self.val.base_iri, state: self.val.state })
}
///|
pub fn IriRef::parse_scheme(self : IriRef) -> Result[Bool, IriError] {
let start_pos = self.val.state.pos
// 获取第一个字符
let first = match self.peek() {
Some(c) => c
None => return Ok(false)
}
// scheme 必须以字母开头
if !first.is_ascii_alphabetic() {
return Ok(false)
}
// 移动位置
self.val.state.pos += first.utf16_len()
// 继续解析 scheme 字符
while self.peek() is Some(c) {
if c.is_ascii_alphabetic() ||
c.is_ascii_digit() ||
c == '+' ||
c == '-' ||
c == '.' {
self.val.state.pos += c.utf16_len()
} else if c == ':' {
// 找到 scheme 结束
return Ok(true)
} else {
// 无效字符,回退
self.val.state.pos = start_pos
return Ok(false)
}
}
// 没有找到 ':',回退
self.val.state.pos = start_pos
Ok(false)
}
// 解析 authority
///|
pub fn IriRef::parse_authority(self : IriRef) -> Result[Unit, IriError] {
// self.val.state.positions.authority_start = self.val.state.pos
// 解析 userinfo (可选)
while self.peek() is Some(c) {
if c == '@' {
self.val.state.pos += c.utf16_len()
break
} else if c == '/' || c == '?' || c == '#' {
break
}
self.val.state.pos += c.utf16_len()
}
// 解析 host
match self.parse_host() {
Ok(_) => ()
Err(e) => return Err(e)
}
// 解析 port (可选)
if self.peek_match([':']) {
self.val.state.pos += 1
while self.peek() is Some(c) {
if c.is_ascii_digit() {
self.val.state.pos += c.utf16_len()
} else {
break
}
}
}
self.val.state.positions.authority_end = self.val.state.pos
Ok(())
}
///|
pub fn IriRef::parse_host(
self : IriRef,
unchecked? : Bool = false,
) -> Result[Unit, IriError] {
if self.peek_match(['[']) {
match self.parse_ipv6_host() {
Ok(_) => ()
Err(e) => if !unchecked { return Err(e) }
// unchecked: 忽略 IPv6 解析错误,继续解析域名
}
} else {
match self.parse_domain_host() {
Ok(_) => ()
Err(e) => if !unchecked { return Err(e) }
// unchecked: 忽略域名解析错误,继续解析 IPv4
}
}
Ok(())
}
///|
pub fn IriRef::parse_ipv6_host(self : IriRef) -> Result[Unit, IriError] {
self.val.state.pos += 1 // 跳过 '['
while self.peek() is Some(c) {
self.val.state.pos += c.utf16_len()
if c == ']' {
break
}
}
Ok(())
}
///|
pub fn IriRef::parse_domain_host(self : IriRef) -> Result[Unit, IriError] {
while self.peek() is Some(c) {
if c == ':' || c == '/' || c == '?' || c == '#' {
break
}
self.val.state.pos += c.utf16_len()
}
Ok(())
}
///|
pub fn IriRef::parse_path(self : IriRef) -> Result[Unit, IriError] {
// self.val.state.positions.path_start = self.val.state.pos
while self.peek() is Some(c) {
if c == '?' || c == '#' {
break
}
self.val.state.pos += c.utf16_len()
}
self.val.state.positions.path_end = self.val.state.pos
Ok(())
}
///|
pub fn IriRef::parse_query(self : IriRef) -> Result[Unit, IriError] {
self.val.state.pos += 1 // 跳过 '?'
// let query_start = self.val.state.pos
// 修复:使用 is_some() 检查是否存在字符,替代 while let
while self.peek() is Some(c) {
// let c = self.peek().unwrap()
if c == '#' {
break
}
self.val.state.pos += c.utf16_len()
}
self.val.state.positions.query_end = self.val.state.pos
Ok(())
}
// 解析 fragment
///|
pub fn IriRef::parse_fragment(self : IriRef) -> Result[Unit, IriError] {
self.val.state.pos += 1 // 跳过 '#'
// self.val.state.positions.fragment_start = self.val.state.pos
while self.peek() is Some(c) {
self.val.state.pos += c.utf16_len()
}
// self.val.state.positions.query_end = self.val.state.pos // fragment 结束后,query_end 也指向这里
Ok(())
}
// 辅助函数
///|
pub fn IriRef::peek(self : IriRef) -> Char? {
if self.val.state.pos >= self.val.as_stringview().length() {
None
} else {
// 使用 try? 或 match 更优雅地处理错误
match StringView::get_char(self.val.base_iri, self.val.state.pos) {
Some(c) => Some(c)
None => None // 无效的 UTF-16 序列
}
}
}
///|
pub fn IriRef::peek_next(self : IriRef) -> Char? {
if self.val.state.pos + 1 >= self.val.as_stringview().length() {
None
} else {
// 需要找到下一个字符的起始位置
// 不能直接用 self.val.state.pos + 1,因为当前字符可能占多个字节
let mut next_pos = self.val.state.pos
// 跳过当前字符的所有字节
match StringView::get_char(self.val.base_iri, next_pos) {
Some(c) => next_pos += c.utf16_len()
None => return None
}
// 获取下一个字符
if next_pos >= self.val.as_stringview().length() {
None
} else {
StringView::get_char(self.val.base_iri, next_pos)
}
}
}
///|
pub fn IriRef::expect(self : IriRef, expected : Char) -> Result[Unit, IriError] {
if self.peek() == Some(expected) {
self.val.state.pos += expected.utf16_len()
Ok(())
} else {
Err(IriError::Expected(expected, self.val.state.pos))
}
}
///|
pub fn IriRef::parse_percent_encoded(self : IriRef) -> Result[Char, IriError] {
if self.peek() != Some('%') {
return Err(IriError::Expected('%', self.val.state.pos))
}
self.val.state.pos += 1 // 跳过 '%'
// 安全地获取第一个十六进制字符
let c1 = match self.peek() {
Some(c) => c
None => return Err(IriError::InvalidPercentEncoding(self.val.state.pos))
}
// 安全地获取第二个十六进制字符
let c2 = match self.peek_next() {
Some(c) => c
None => return Err(IriError::InvalidPercentEncoding(self.val.state.pos))
}
if c1.is_ascii_hexdigit() && c2.is_ascii_hexdigit() {
// 验证百分比编码,但不解码(保持原始形式)
self.val.state.pos += 2 // 跳过两个十六进制字符
Ok('%') // 返回 '%' 表示遇到了编码
} else {
Err(IriError::InvalidPercentEncoding(self.val.state.pos))
}
}
// 解析单个路径段
///|
pub fn IriRef::parse_path_segment(self : IriRef) -> Result[String, IriError] {
let start = self.val.state.pos
while self.peek() is Some(c) {
match c {
'/' | '?' | '#' => break
'%' => {
let _ = self.parse_percent_encoded() // 验证百分比编码,但不解码
}
_ =>
if is_pchar(c) {
self.val.state.pos += c.utf16_len()
} else {
return Err(IriError::RelativePathSegment(c, self.val.state.pos))
}
}
}
Ok(self.val.base_iri.sub(start~, end=self.val.state.pos).to_owned())
}
// 2. Iri 结构中的完整实现
///|
pub fn Iri::remove_dot_segments(_self : Iri, path : StringView) -> String {
// 特殊处理空路径
if path.length() == 0 {
return ""
}
let parts_iter = path.split("/")
let parts : Array[String] = parts_iter.map(fn(s) { s.to_owned() }).to_array()
// 检测是否是绝对路径 (以 / 开头)
let is_absolute = path.has_prefix("/")
let cleaned_segments = remove_dot_segments_from_array(parts)
// 重新构建字符串
let result = cleaned_segments.join("/")
if is_absolute {
// 确保绝对路径以 / 开头
if result == "" {
return "/"
} else if !result.has_prefix("/") {
return "/" + result
} else {
return result
}
// 相对路径
} else if result == "" {
return "./" // 或者 "",取决于具体需求
} else {
return result
}
}
///|
pub fn IriRef::validate(self : IriRef) -> Result[Unit, IriError] {
// 保存原始状态
let saved_state = self.val.state
// 尝试解析(会修改 state)
match self.parse() {
Ok(_) => {
// 解析成功,恢复原始状态(不保留解析结果)
self.val.state = saved_state
Ok(())
}
Err(e) => {
// 解析失败,恢复原始状态
self.val.state = saved_state
Err(e)
}
}
}
///|
pub fn IriRef::resolve(
self : IriRef,
ref_iri_str : String,
remove_dot_segments? : Bool = true,
) -> Result[Iri, IriError] {
// 1. 解析参考 IRI (Relative Reference)
let ref_parser = Iri::new(ref_iri_str)
// let ref_parser = IriRef::of_ref(ref_parser)
let parsed_ref = ref_parser.parse_unchecked()
// 2. 如果参考 IRI 是绝对的,直接返回它
// 利用 trait 方法 is_absolute
if parsed_ref.is_absolute() {
return Ok(parsed_ref)
}
// 3. 解析 Base IRI (当前 self)
// 注意:self 可能尚未解析,或者状态已脏。为了安全,重新解析 self
let base_parser = Iri::new(self.val.base_iri)
// let base_parser = IriRef::of_ref(base_parser)
let parsed_base = match base_parser.parse() {
Ok(iri) => iri
Err(e) => return Err(e)
}
// 4. 提取组件 (使用 IriComponents trait)
let base_scheme = parsed_base.scheme()
let base_authority = parsed_base.authority()
let base_path = parsed_base.path()
let ref_scheme = parsed_ref.scheme()
let ref_authority = parsed_ref.authority()
let ref_path = parsed_ref.path()
let ref_query = parsed_ref.query()
let ref_fragment = parsed_ref.fragment()
// 5. 构建目标 IRI 的各个部分
// Scheme: 如果参考 IRI 有 scheme,则使用它;否则使用 Base 的 scheme
let target_scheme = match ref_scheme {
Some(s) => Some(s)
None => base_scheme
}
// Authority: 如果参考 IRI 有 authority,则使用它;
// 如果参考 IRI 没有 authority 但有 scheme,则无 authority;
// 否则使用 Base 的 authority
let target_authority = match ref_authority {
Some(auth) => Some(auth)
None => if ref_scheme != None { None } else { base_authority }
}
// Path: 核心合并逻辑
let target_path = match ref_path {
Some(r_path) =>
if r_path == "" {
// 如果参考路径为空字符串(例如 ?query 或 #frag),直接继承基路径
match base_path {
Some(p) => p
None => ""
}
} else if r_path.has_prefix("/") {
// 如果参考路径是绝对的,直接使用,并去除点段
self.into_owned().remove_dot_segments(r_path)
} else {
// 如果参考路径是相对的,需要与 Base 路径合并
let merged_raw = match base_path {
Some(b_path) =>
if b_path == "" && base_scheme != None && base_authority == None {
// Base 是 http: 这种形式,路径为空,相对路径前加 /
"/" + r_path
} else {
// 找到 Base 路径的最后一个 /
let last_slash_idx = b_path.rev_find("/")
match last_slash_idx {
Some(idx) => {
// 保留目录部分
let base_dir = b_path.sub(start=0, end=idx + 1)
base_dir.to_owned() + r_path
}
None =>
// Base 没有 /,说明是整个文件名,替换它
// 但如果 base_path 本身为空(例如 base 是 http://host),则直接拼接
if b_path == "" {
"/" + r_path
} else {
r_path
}
}
}
None =>
// Base 没有路径部分
"/" + r_path
}
if remove_dot_segments {
self.into_owned().remove_dot_segments(merged_raw)
} else {
merged_raw
}
}
None =>
// 参考 IRI 没有路径,使用 Base 路径
match base_path {
Some(p) => p
None => ""
}
}
let target_query = match ref_query {
Some(q) => Some(q)
None => parsed_base.query()
}
// Fragment: 类似 Query
let target_fragment = match ref_fragment {
Some(f) => Some(f)
None => parsed_base.fragment()
}
// 6. 重组字符串
let sb = StringBuilder::new()
// Scheme
match target_scheme {
Some(s) => {
sb.write(s)
sb.write(":")
}
None => ()
}
// Authority
match target_authority {
Some(auth) => {
sb.write("//")
sb.write(auth)
}
None => ()
}
// Path
sb.write(target_path)
// Query
match target_query {
Some(q) => {
sb.write("?")
sb.write(q)
}
None => ()
}
// Fragment
match target_fragment {
Some(f) => {
sb.write("#")
sb.write(f)
}
None => ()
}
let final_str = sb.to_string()
// 7. 返回新的 Iri 对象
let final_parser = Iri::new(final_str)
// let final_parser = IriRef::of_ref(final_parser)
final_parser.parse()
}
///|
pub fn IriRef::resolve_unchecked(
self : IriRef,
ref_iri_str : String,
remove_dot_segments? : Bool = true,
) -> Iri {
// 1. 解析参考 IRI (Relative Reference) - Unchecked
let ref_parser = Iri::new(ref_iri_str)
// let ref_parser = IriRef::of_ref(ref_parser)
let parsed_ref = ref_parser.parse_unchecked()
// 2. 如果参考 IRI 是绝对的,直接返回它 (Unchecked)
// 注意:is_absolute 依赖于 scheme 的解析。如果 parse 失败,scheme 可能为空。
if parsed_ref.is_absolute() {
return parsed_ref
}
// 3. 解析 Base IRI (当前 self) - Unchecked
let base_parser = Iri::new(self.val.base_iri)
// let base_parser = IriRef::of_ref(base_parser)
let parsed_base = base_parser.parse_unchecked()
// 4. 提取组件
// 由于是 unchecked,这些方法应该能安全返回 Option[String],即使内部状态不完全正确
let base_scheme = parsed_base.scheme()
let base_authority = parsed_base.authority()
let base_path = parsed_base.path()
let ref_scheme = parsed_ref.scheme()
let ref_authority = parsed_ref.authority()
let ref_path = parsed_ref.path()
let ref_query = parsed_ref.query()
let ref_fragment = parsed_ref.fragment()
// 5. 构建目标 IRI 的各个部分
// Scheme
let target_scheme = match ref_scheme {
Some(s) => Some(s)
None => base_scheme
}
// Authority
let target_authority = match ref_authority {
Some(auth) => Some(auth)
None => if ref_scheme != None { None } else { base_authority }
}
// Path: 核心合并逻辑
let target_path = match ref_path {
Some(r_path) =>
if r_path.has_prefix("/") {
// 绝对路径,去除点段
self.into_owned().remove_dot_segments(r_path)
} else {
// 相对路径,与 Base 合并
let merged_raw = match base_path {
Some(b_path) =>
if b_path == "" && base_scheme != None && base_authority == None {
"/" + r_path
} else {
let last_slash_idx = b_path.rev_find("/")
match last_slash_idx {
Some(idx) => {
let base_dir = b_path.sub(start=0, end=idx + 1)
base_dir.to_owned() + r_path
}
None => if b_path == "" { "/" + r_path } else { r_path }
}
}
None => "/" + r_path
}
if remove_dot_segments {
self.into_owned().remove_dot_segments(merged_raw)
} else {
merged_raw
}
}
None =>
match base_path {
Some(p) => p
None => ""
}
}
// Query
let target_query = match ref_query {
Some(q) => Some(q)
None => parsed_base.query()
}
// Fragment
let target_fragment = match ref_fragment {
Some(f) => Some(f)
None => parsed_base.fragment()
}
// 6. 重组字符串
let sb = StringBuilder::new()
match target_scheme {
Some(s) => {
sb.write(s)
sb.write(":")
}
None => ()
}
match target_authority {
Some(auth) => {
sb.write("//")
sb.write(auth)
}
None => ()
}
sb.write(target_path)
match target_query {
Some(q) => {
sb.write("?")
sb.write(q)
}
None => ()
}
match target_fragment {
Some(f) => {
sb.write("#")
sb.write(f)
}
None => ()
}
let final_str = sb.to_string()
// 7. 直接返回新的 Iri 对象,不进行严格解析验证
Iri::Iri(final_str)
}
///|
pub fn IriRef::resolve_into(
self : IriRef,
relative_ref : String,
buffer : StringBuilder,
) -> Result[Unit, IriError] {
let relative_ref = Iri::new(relative_ref)
// 1. 解析相对引用
let parsed_relative = match IriRef::parse(relative_ref) {
Ok(r) => r
Err(e) => return Err(e)
}
// 2. 如果相对引用是绝对的(包含 scheme),直接将其写入 buffer
if parsed_relative.is_absolute() {
buffer.write_string(parsed_relative.into_string())
return Ok(())
}
// 3. 处理相对路径合并
// 获取基础 IRI 的各个部分
let base_scheme = self.scheme()
let base_authority = self.authority()
let base_path = self.path().unwrap_or("")
// 开始构建结果
// 写入 Scheme
match base_scheme {
Some(s) => {
buffer.write_string(s)
buffer.write_char(':')
}
None => ()
}
// 写入 Authority
match base_authority {
Some(a) => {
buffer.write_string("//")
buffer.write_string(a)
}
None => ()
}
// 计算合并后的路径
let merged_path = match parsed_relative.path() {
Some(rel_path) =>
if !rel_path.is_empty() {
if rel_path.has_prefix("/") {
// 如果相对路径以 / 开头,它替换基础路径
rel_path
} else {
// 否则,合并路径
// 找到基础路径的最后一个 /
let last_slash_idx = base_path.find("/") // 注意:通常找最后一个 slash 用 rfind,原代码用的是 find,这里保持原意或修正为 rfind
match last_slash_idx {
Some(idx) => {
// 保留基础路径直到最后一个 /
let base_prefix = base_path[0:idx + 1]
"\{base_prefix}\{rel_path}"
}
None =>
if base_path.is_empty() {
rel_path
} else if base_authority is Some(_) && base_path.is_empty() {
"/\{rel_path}"
} else {
// 移除基础路径的最后一段
rel_path
}
}
}
} else {
// 相对引用路径为空字符串,使用基础路径
base_path
}
None =>
// 相对引用没有路径,使用基础路径
base_path
}
// 写入路径
buffer.write_string(merged_path)
// 写入查询
match parsed_relative.query() {
Some(q) => {
buffer.write_char('?')
buffer.write_string(q)
}
None => ()
}
// 如果相对引用没有 query,但我们有基础 query,且相对引用也没有覆盖它(即 relative.query() is None)
if parsed_relative.query() is None(_) {
match self.query() {
Some(q) => {
buffer.write_char('?')
buffer.write_string(q)
}
None => ()
}
}
// 写入片段
match parsed_relative.fragment() {
Some(f) => {
buffer.write_char('#')
buffer.write_string(f)
}
None =>
if parsed_relative.fragment() is None(_) {
match self.fragment() {
Some(f) => {
buffer.write_char('#')
buffer.write_string(f)
}
None => ()
}
}
}
Ok(())
}
///|
pub fn IriRef::relativize(
base : IriRef,
target : IriRef,
) -> Result[Iri, IriError] {
let base_scheme = base.scheme()
let target_scheme = target.scheme()
let base_authority = base.authority()
let target_authority = target.authority()
// 1. 如果 Scheme 或 Authority 不同,无法相对化,返回目标的字符串表示作为绝对 IRI
if base_scheme != target_scheme || base_authority != target_authority {
return IriRef::parse(target)
}
let base_path = base.path().unwrap_or("")
let target_path = target.path().unwrap_or("")
// 2. 计算相对路径
let relative_path = if target_path == base_path {
// 路径完全相同
""
} else if target_path.has_prefix(base_path) {
// 目标是基础的子路径
// 确保我们是在目录边界上切割
// 例如 base: /a/b/, target: /a/b/c -> c
// base: /a/b, target: /a/bc -> 不应该匹配前缀,除非 base 是 /a/b/
// 简单处理:如果 base 以 / 结尾,或者 target 在 base 长度处的字符是 /
let is_base_empty = base_path.is_empty()
let is_base_suffix_slash = base_path.has_suffix("/")
let is_next_char_slash = if target_path.length() > base_path.length() {
match target_path.get_char(base_path.length()) {
Some(c) => c == '/'
None => false // 如果无法获取字符,视为前缀无效
}
} else {
false
}
let is_prefix_valid = is_base_empty ||
is_base_suffix_slash ||
is_next_char_slash
if is_prefix_valid {
// 截取剩余部分
let suffix_start = if base_path.is_empty() {
0
} else {
base_path.length()
}
let rel_p = target_path[suffix_start:]
if !base_path.is_empty() && !base_path.has_suffix("/") {
// 跳过前面的 /
if rel_p.has_prefix("/") {
rel_p[1:].to_owned()
} else {
rel_p.to_owned()
}
} else {
rel_p.to_owned()
}
} else {
// 前缀匹配但不是目录边界,无法相对化
return IriRef::parse(target)
}
} else {
// 简单实现:返回绝对路径
return IriRef::parse(target)
}
// 3. 构建相对 IRI 字符串
let result = StringBuilder::new()
// 写入相对路径
if !relative_path.is_empty() {
result.write_string(relative_path)
} else {
// 如果路径相同,检查 query 和 fragment
result.write_string("")
}
// 4. 处理 Query
let base_query = base.query()
let target_query = target.query()
if target_query != base_query {
match target_query {
Some(q) => {
result.write_char('?')
result.write_string(q)
}
None => ()
// RFC 3986: If the reference's query component is empty, it means the resulting URI has no query.
// But our Option doesn't distinguish empty vs undefined easily.
// Assuming None means undefined (inherit), Some("") means empty.
// If we want to REMOVE the base query, we should write "?"?
// Let's assume if target_query is None, we don't write anything (inherits).
// If target_query is Some(""), we write "?"?
// For simplicity, if they differ, and target has Some(q), we write it.
}
}
// 5. 处理 Fragment
let base_fragment = base.fragment()
let target_fragment = target.fragment()
if target_fragment != base_fragment {
match target_fragment {
Some(f) => {
result.write_char('#')
result.write_string(f)
}
None => ()
}
}
let final_str = result.to_string()
let final_iri = Iri::new(final_str)
// parse 空字符串应该返回一个有效的相对 IriRef。
IriRef::parse(final_iri)
}
///|
pub impl IriComponents for Iri with fn query(self : Iri) -> String? {
if self.state.positions.query_end > self.state.positions.path_end + 1 {
Some(
self.base_iri
.sub(
start=self.state.positions.path_end + 1,
end=self.state.positions.query_end,
)
.to_owned(),
)
} else {
None
}
}
///|
pub impl IriComponents for Iri with fn is_absolute(self : Iri) -> Bool {
self.state.positions.scheme_end > 0
}
///|
pub impl IriComponents for Iri with fn scheme(self : Iri) -> String? {
if self.state.positions.scheme_end > 0 {
Some(
self.base_iri
.sub(start=0, end=self.state.positions.scheme_end - 1)
.to_owned(),
)
} else {
None
}
}
///|
pub impl IriComponents for Iri with fn authority(self : Iri) -> String? {
if self.state.positions.authority_end > self.state.positions.scheme_end + 2 {
Some(
self.base_iri
.sub(
start=self.state.positions.scheme_end + 2,
end=self.state.positions.authority_end,
)
.to_owned(),
)
} else {
None
}
}
///|
pub impl IriComponents for Iri with fn path(self : Iri) -> String? {
Some(
self.base_iri
.sub(
start=self.state.positions.authority_end,
end=self.state.positions.path_end,
)
.to_owned(),
)
}
///|
pub impl IriComponents for Iri with fn fragment(self : Iri) -> String? {
if self.state.positions.query_end + 1 < self.base_iri.length() { // 简单判断是否有 fragment
if self.state.positions.query_end + 1 > 0 {
Some(
self.base_iri
.sub(
start=self.state.positions.query_end + 1,
end=self.base_iri.length(),
)
.to_owned(),
)
} else {
None
}
} else {
None
}
}
///|
pub impl IriComponents for IriRef with fn query(self : IriRef) -> String? {
if self.val.state.positions.query_end > self.val.state.positions.path_end + 1 {
Some(
self.val.base_iri
.sub(
start=self.val.state.positions.path_end + 1,
end=self.val.state.positions.query_end,
)
.to_owned(),
)
} else {
None
}
}
///|
pub impl IriComponents for IriRef with fn is_absolute(self : IriRef) -> Bool {
self.val.state.positions.scheme_end > 0
}
///|
pub impl IriComponents for IriRef with fn scheme(self : IriRef) -> String? {
if self.val.state.positions.scheme_end > 0 {
Some(
self.val.base_iri
.sub(start=0, end=self.val.state.positions.scheme_end - 1)
.to_owned(),
)
} else {
None
}
}
///|
pub impl IriComponents for IriRef with fn authority(self : IriRef) -> String? {
if self.val.state.positions.authority_end >
self.val.state.positions.scheme_end + 2 {
Some(
self.val.base_iri
.sub(
start=self.val.state.positions.scheme_end + 2,
end=self.val.state.positions.authority_end,
)
.to_owned(),
)
} else {
None
}
}
///|
pub impl IriComponents for IriRef with fn path(self : IriRef) -> String? {
Some(
self.val.base_iri
.sub(
start=self.val.state.positions.authority_end,
end=self.val.state.positions.path_end,
)
.to_owned(),
)
}
///|
pub impl IriComponents for IriRef with fn fragment(self : IriRef) -> String? {
if self.val.state.positions.query_end + 1 < self.val.base_iri.length() { // 简单判断是否有 fragment
if self.val.state.positions.query_end + 1 > 0 {
Some(
self.val.base_iri
.sub(
start=self.val.state.positions.query_end + 1,
end=self.val.base_iri.length(),
)
.to_owned(),
)
} else {
None
}
} else {
None
}
}