// ============================================================
// 类型定义
// ============================================================
///|
/// 子标签位置,左闭右开 [start, end)
pub type TagSpan = (Int, Int)
///|
/// 扩展 (单字符标识符 + 多个子标签)
/// 例如: u-nu-hanidec → ident='u', subtags=["nu", "hanidec"]
pub struct Extension {
ident : TagSpan // 单字符标识符的位置
subtags : Array[TagSpan] // 后续子标签
} derive(Debug, Eq)
///|
pub impl Show for Extension with fn output(self : Extension, logger : &Logger) -> Unit {
logger.write("Extension:")
logger.write(" { ")
logger.write(" ident: ")
logger.write(@debug.to_string(self.ident))
logger.write(", ")
logger.write(" subtags: ")
logger.write(@debug.to_string(self.subtags))
logger.write(" }")
}
///|
/// 私有使用 (x- 前缀 + 多个子标签)
/// 例如: x-mycorp-private → prefix='x', subtags=["mycorp", "private"]
pub struct PrivateUse {
prefix : TagSpan // "x" 的位置
subtags : Array[TagSpan]
} derive(Debug, Eq)
///|
pub impl Show for PrivateUse with fn output(self : PrivateUse, logger : &Logger) -> Unit {
logger.write("PrivateUse:")
logger.write(" { ")
logger.write(" prefix: ")
logger.write(@debug.to_string(self.prefix))
logger.write(", ")
logger.write(" subtags: ")
logger.write(@debug.to_string(self.subtags))
logger.write(" }")
}
///|
/// 语言标签 (RFC 5646 / BCP 47)
/// 结构: language ["-" extlang] ["-" script] ["-" region] *("-" variant) *("-" extension) ["-" privateuse]
pub struct LanguageTag {
tag : StringView // 原始输入
state : TagState
} derive(Debug, Eq)
///|
pub impl Show for LanguageTag with fn output(
self : LanguageTag,
logger : &Logger,
) -> Unit {
logger.write("LanguageTag:")
logger.write("\n")
logger.write(" { ")
logger.write(" tag: ")
logger.write(self.tag)
logger.write("\n")
logger.write(" state: ")
logger.write(self.state)
logger.write("\n")
logger.write(" } ")
}
///|
pub struct TagState {
// 必选 + 位置固定的部分
language : TagSpan // 主语言 (2-3位 或 5-8位字母)
extlang : TagSpan? // 扩展语言范围 (多个3位字母,可选)
script : TagSpan? // 脚本 (4位字母,首字母大写)
region : TagSpan? // 区域 (2位大写字母 或 3位数字)
// 可变部分
variants : Array[TagSpan] // 变体 (多个)
extensions : Array[Extension] // 扩展 (多个)
private_use : PrivateUse? // 私有使用
// 元信息
is_grandfathered : Bool // 是否是祖父标签
} derive(Debug, Eq)
///|
// pub fn TagState::new(tag : StringView) -> TagState {
// ...
// }
///|
pub impl Show for TagState with fn output(self : TagState, logger : &Logger) -> Unit {
logger.write("TagState:")
logger.write("\n")
logger.write(" { ")
logger.write("\n")
logger.write(" language: ")
logger.write(@debug.to_string(self.language))
logger.write(", ")
logger.write(" extlang: ")
logger.write(@debug.to_string(self.extlang))
logger.write(", ")
logger.write(" script: ")
logger.write(@debug.to_string(self.script))
logger.write(", ")
logger.write(" region: ")
logger.write(@debug.to_string(self.region))
logger.write(" variants: ")
logger.write(@debug.to_string(self.variants))
logger.write(", ")
logger.write(" extensions: ")
logger.write(@debug.to_string(self.extensions))
logger.write(", ")
logger.write(" private_use: ")
logger.write(@debug.to_string(self.private_use))
logger.write("\n")
logger.write(" }")
}
///|
pub fn LanguageTag::to_tagstr(self : LanguageTag) -> String {
self.tag.to_owned()
}
// ============================================================
// 辅助函数
// ============================================================
///|
/// 从 StringView 提取 TagSpan 范围的子字符串
fn sub(s : StringView, span : TagSpan) -> StringView {
if span.1 <= span.0 {
return ""
}
let start = if span.0 < 0 { 0 } else { span.0 }
let end = if span.1 > s.length() { s.length() } else { span.1 }
s[start:end]
}
///|
/// 计算 TagSpan 长度
fn span_len(span : TagSpan) -> Int {
span.1 - span.0
}
///|
/// 检查 TagSpan 是否有效
// fn span_is_valid(span : TagSpan) -> Bool {
// span.1 > span.0
// }
///|
/// 提取范围内的所有子标签
fn split_by_span(
s : StringView,
start : Int,
end : Int,
delim : Char,
) -> Array[StringView] {
let view = sub(s, (start, end))
if view.is_empty() {
return []
}
view.split(StringView::from_array([delim])).collect()
}
///|
/// 数字常量
/// 'A'=65 'Z'=90 'a'=97 'z'=122 '0'=48 '9'=57
const UPPER_A : Int = 65
///|
const UPPER_Z : Int = 90
///|
const LOWER_A : Int = 97
///|
const LOWER_Z : Int = 122
///|
const DIGIT_0 : Int = 48
///|
const DIGIT_9 : Int = 57
///|
/// 字符分类
fn is_alpha(c : Int) -> Bool {
(c >= LOWER_A && c <= LOWER_Z) || (c >= UPPER_A && c <= UPPER_Z)
}
///|
fn is_digit(c : Int) -> Bool {
c >= DIGIT_0 && c <= DIGIT_9
}
///|
fn is_alphanumeric_char(c : Int) -> Bool {
is_alpha(c) || is_digit(c)
}
///|
// fn is_lower(c : Int) -> Bool {
// c >= LOWER_A && c <= LOWER_Z
// }
///|
fn is_upper(c : Int) -> Bool {
c >= UPPER_A && c <= UPPER_Z
}
///|
/// 检查 StringView 范围是否全为字母
fn is_alphabetic_span(s : StringView, span : TagSpan) -> Bool {
let mut i = span.0
while i < span.1 {
if !is_alpha(s[i].to_int()) {
return false
}
i += 1
}
true
}
///|
/// 检查 StringView 范围是否全为字母数字
fn is_alphanumeric_span(s : StringView, span : TagSpan) -> Bool {
let mut i = span.0
while i < span.1 {
let c = s[i].to_int().to_char().unwrap()
if c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || c > 'z' {
return false
}
i += 1
}
true
}
///|
/// 检查是否是脚本 (4位,首字母大写,其余小写)
fn is_script(s : StringView, span : TagSpan) -> Bool {
if span_len(span) != 4 {
return false
}
// 首字符必须是字母
let c0 = s[span.0].to_int()
if !is_alpha(c0) {
return false
}
// 后3个字符必须是字母
let mut i = span.0 + 1
while i < span.1 {
if !is_alpha(s[i].to_int()) {
return false
}
i += 1
}
true
}
///|
/// 检查是否是区域 (2位大写字母 或 3位数字)
fn is_region(s : StringView, span : TagSpan) -> Bool {
let len = span_len(span)
if len == 2 {
// 2位字母(大小写均可)
return is_alphabetic_span(s, span)
}
if len == 3 {
let mut i = span.0
while i < span.1 {
if !is_digit(s[i].to_int()) {
return false
}
i += 1
}
return true
}
false
}
///|
/// 检查是否是变体 (5-8位字母数字,或4位数字首位非0,或旧式3字母变体)
fn is_variant(
s : StringView,
span : TagSpan,
legacy : Array[StringView],
) -> Bool {
let len = span_len(span)
if len >= 5 {
return is_alphanumeric_span(s, span)
}
if len == 4 {
let c0 = s[span.0].to_int()
if is_digit(c0) && c0 != DIGIT_0 {
return is_alphanumeric_span(s, span)
}
}
// 旧式3字母变体
let subtag = sub(s, span)
for l in legacy {
if subtag.to_lower() == l.to_lower() {
return true
}
}
false
}
///|
/// 检查是否是扩展标识符 (单字符,非 x/X)
fn is_extension_ident(s : StringView, span : TagSpan) -> Bool {
if span_len(span) != 1 {
return false
}
let c = s[span.0].to_int()
let cl = if is_upper(c) { c + 32 } else { c } // 转小写
cl != 'x'.to_int() && is_alphanumeric_char(c)
}
///|
/// 检查是否是私有使用前缀 (单字符 x 或 X)
fn is_private_use_prefix(s : StringView, span : TagSpan) -> Bool {
if span_len(span) != 1 {
return false
}
let c = s[span.0].to_int()
let cl = if is_upper(c) { c + 32 } else { c }
cl == 'x'.to_int()
}
// ============================================================
// 旧式3字母变体 (RFC 5646 允许)
// ============================================================
///|
let legacy_3letter_variants : Array[StringView] = [
"BIS", "NIS", "PIN", "SIN", "KNI", "QUI", "JAN", "FON", "SAA", "SBE", "SCO", "SGU",
"WUU",
]
// ============================================================
// 祖父标签 (不区分大小写)
// ============================================================
///|
let grandfathereds : Array[StringView] = [
"art-lojban", "cel-gaulish", "en-GB-oed", "i-ami", "i-bnn", "i-default", "i-enochian",
"i-hak", "i-klingon", "i-lux", "i-mingo", "i-navajo", "i-pwn", "i-tao", "i-tay",
"i-tsu", "no-bok", "no-nyn", "sgn-BE-FR", "sgn-BE-NL", "sgn-CH-DE", "zh-guoyu",
"zh-hakka", "zh-min", "zh-min-nan", "zh-xiang",
]
// 预计算小写版本
// 祖父标签(预计算小写,用于比较)
///|
let grandfathereds_lower : Array[StringView] = [
"art-lojban", "cel-gaulish", "en-gb-oed", "i-ami", "i-bnn", "i-default", "i-enochian",
"i-hak", "i-klingon", "i-lux", "i-mingo", "i-navajo", "i-pwn", "i-tao", "i-tay",
"i-tsu", "no-bok", "no-nyn", "sgn-be-fr", "sgn-be-nl", "sgn-ch-de", "zh-guoyu",
"zh-hakka", "zh-min", "zh-min-nan", "zh-xiang",
]
///|
/// 检查是否是祖父标签,返回原始格式的 span
/// 检查是否是祖父标签
fn is_grandfathered(tag : StringView) -> Bool {
let lower = tag.to_lower()
let mut i = 0
while i < grandfathereds_lower.length() {
if lower == grandfathereds_lower[i] {
return true
}
i += 1
}
false
}
// ============================================================
// 解析(祖父标签)
// ============================================================
///|
/// 创建祖父标签(返回小写标准格式)
fn make_grandfathered(tag : StringView) -> LanguageTag {
// 找到祖父标签的标准格式
let lower = tag.to_lower()
let mut i = 0
let mut canonical = tag // 默认用原输入
while i < grandfathereds_lower.length() {
if lower == grandfathereds_lower[i] {
canonical = grandfathereds[i] // 使用标准格式
break
}
i += 1
}
{
tag: canonical,
state: {
language: (0, canonical.length()),
extlang: None,
script: None,
region: None,
variants: [],
extensions: [],
private_use: None,
is_grandfathered: true,
},
}
}
// ============================================================
// 解析入口
// ============================================================
///|
/// 解析语言标签
pub fn LanguageTag::parse(tag : StringView) -> Result[LanguageTag, TagParseErr] {
if tag.is_empty() {
return Err(TagParseErr::EmptyTag)
}
// 检查祖父标签
if is_grandfathered(tag) {
return Ok(make_grandfathered(tag))
}
if tag.length() >= 2 && tag[0] == 'x' && tag[1] == '-' {
return parse_private_use_tag(tag)
}
parse_tag(tag)
}
///|
/// 纯解析(不含祖父标签检查)
fn parse_tag(tag : StringView) -> Result[LanguageTag, TagParseErr] {
// 第一步:扫描所有子标签
let subtags = scan_subtags(tag)
if subtags.is_empty() {
return Err(TagParseErr::EmptySubtag)
}
// 先验证字符
match validate_tag_chars(tag) {
Err(e) => return Err(e)
_ => ()
}
// 验证格式
match validate_tag_format(tag) {
Err(e) => return Err(e)
_ => ()
}
let mut i = 0
// 1. 语言(必选,第一个子标签)
let lang = subtags[0]
let lang_len = span_len(lang)
// 检查长度:2-3 位 或 5-8 位(4 位无效)
let is_valid_length = (lang_len >= 2 && lang_len <= 3) ||
(lang_len >= 5 && lang_len <= 8)
if !is_valid_length {
return Err(TagParseErr::InvalidLanguageLength)
}
if !is_alphabetic_span(tag, lang) {
return Err(TagParseErr::ForbiddenChar)
}
i += 1
// 2. 扩展语言(可选,2-3位语言后可跟最多3个3位字母)
let mut extlang_start = lang.1
let mut extlang_end = lang.1
let mut extlang_count = 0
// debug(
// "extlang check: lang_len=\{lang_len}, i=\{i}, subtags[1]=\{subtags.get(1)}",
// )
while i < subtags.length() && lang_len <= 3 && extlang_count < 3 {
let ext = subtags[i]
// debug(("extlang loop: ext=\{ext}, ext.0=\{ext.0}, ext.1=\{ext.1}")
if span_len(ext) != 3 || !is_alphabetic_span(tag, ext) {
// debug((" break: ! alphabetic")
break
}
let ext_view = sub(tag, ext)
let mut is_legacy = false
for l in legacy_3letter_variants {
if ext_view.to_lower() == l.to_lower() {
is_legacy = true
break
}
}
if is_legacy {
// debug((" break: legacy variant")
break
}
if extlang_count == 0 {
extlang_start = ext.0
// debug((" set extlang_start = \{ext.0}")
}
extlang_count += 1
extlang_end = ext.1
i += 1
}
// debug(
// "extlang result: start=\{extlang_start}, end=\{extlang_end}, count=\{extlang_count}",
// )
let extlang : TagSpan? = if extlang_count > 0 {
Some((extlang_start, extlang_end)) // (3, 6)
} else {
None
}
// 3. 脚本(可选,4位字母,首字母大写)
let mut script : TagSpan? = None
if i < subtags.length() && span_len(subtags[i]) == 4 {
let s = subtags[i]
// let cand = sub(tag, s)
let is_s = is_script(tag, s)
// debug(("@@script candidate: '\{cand}' is_script: \{is_s}")
if is_s {
script = Some(s)
i += 1
}
}
// 4. 区域(2位大写 或 3位数字)
let mut region : TagSpan? = None
if i < subtags.length() {
// debug(("@@region check: '\{sub(tag, subtags[i])}' i=\{i}")
}
if i < subtags.length() && is_region(tag, subtags[i]) {
region = Some(subtags[i])
i += 1
}
// 5. 变体(可选,多个)
let variants : Array[TagSpan] = []
while i < subtags.length() {
let s = subtags[i]
// let cand = sub(tag, s)
let is_v = is_variant(tag, s, legacy_3letter_variants)
// debug(("@@variant check: '\{cand}' is_variant=\{is_v}")
if is_v {
variants.push(s)
i += 1
} else {
break
}
}
// 6. 扩展(可选,多个)
let extensions : Array[Extension] = []
while i < subtags.length() && is_extension_ident(tag, subtags[i]) {
let ident = subtags[i]
i += 1
let ext_subtags : Array[TagSpan] = []
// 收集后续子标签直到遇到下一个扩展标识符、私有使用或结束
while i < subtags.length() &&
!is_extension_ident(tag, subtags[i]) &&
!is_private_use_prefix(tag, subtags[i]) {
let s = subtags[i]
// 验证扩展子标签:1-8位字母数字
if !is_valid_private_use_subtag(tag, s) {
return Err(TagParseErr::InvalidSubtag(s, sub(tag, s)))
}
ext_subtags.push(subtags[i])
i += 1
}
if ext_subtags.is_empty() {
return Err(TagParseErr::EmptyExtension)
}
extensions.push({ ident, subtags: ext_subtags })
}
// 7. 私有使用(可选)
let private_use : PrivateUse? = if i < subtags.length() &&
is_private_use_prefix(tag, subtags[i]) {
let prefix = subtags[i]
i += 1
let subs : Array[TagSpan] = []
while i < subtags.length() {
subs.push(subtags[i])
i += 1
}
Some({ prefix, subtags: subs })
} else {
None
}
Ok({
tag,
state: TagState::{
language: lang,
extlang,
script,
region,
variants,
extensions,
private_use,
is_grandfathered: false,
},
})
}
///|
/// 扫描所有子标签,返回位置数组
fn scan_subtags(tag : StringView) -> Array[TagSpan] {
let mut pos = 0
let subtags : Array[TagSpan] = []
// debug(("scan_subtags: tag='\{tag}', length=\{tag.length()}")
while pos < tag.length() {
if tag[pos] == '-' {
// debug((" skip '-' at pos \{pos}")
pos += 1
continue
}
let start = pos
while pos < tag.length() && tag[pos] != '-' {
pos += 1
}
let end = pos
// let subtag = tag[start:end]
// debug((" subtag '\{subtag}' at (\{start}, \{end})")
subtags.push((start, end))
if pos < tag.length() {
pos += 1
}
}
subtags
}
///|
/// 解析并归一化语言标签
pub fn LanguageTag::parse_and_normalize(
tag : StringView,
) -> Result[LanguageTag, TagParseErr] {
match LanguageTag::parse(tag) {
Ok(parsed) => Ok(parsed.normalize())
Err(e) => Err(e)
}
}
///|
fn LanguageTag::normalize(self : LanguageTag) -> LanguageTag {
if self.state.is_grandfathered {
// debug("normalize: grandfathered")
return self
}
// 私有使用标签:整体小写
if self.state.private_use is Some(_) {
// debug("normalize: private use")
return { tag: self.tag.to_lower(), state: self.state }
}
let result = StringBuilder::new()
// debug(("normalize input \{self}")
// 1. 主语言 (小写)
let lang = sub(self.tag, self.state.language).to_lower()
// debug("normalize: language = '\{lang}'")
result.write(lang)
// 2. 扩展语言 (小写)
match self.state.extlang {
Some(span) => {
// span.0 是主语言结束位置(包含 '-')
// 需要跳过这个 '-'
let ext_start = span.0 + 1
let parts = split_by_span(
self.tag,
self.state.language.1 + 1,
span.1,
'-',
)
// debug("normalize: extlang parts = \{parts}")
if ext_start < span.1 {
for part in parts {
result.write("-")
result.write(part.to_lower())
}
}
}
None => ()
}
// 3. 脚本 (Title Case)
// debug("normalize: checking script = \{self.state.script}")
match self.state.script {
Some(span) => {
// debug("normalize: script span = \{span}")
let s = sub(self.tag, span)
let s_len = span_len(span)
if s_len > 0 {
let first = s[0].to_int().to_char().unwrap()
let first_upper = if first >= 'a' && first <= 'z' {
(first.to_int() - 32).to_char().unwrap()
} else {
first
}
let rest = s[1:].to_lower()
result.write("-")
result.write_char(first_upper)
result.write(rest)
}
}
None => ()
}
// 4. 区域 (大写)
// debug("normalize: checking region = \{self.state.region}")
match self.state.region {
Some(span) => {
// debug("normalize: region span = \{span}")
let r = sub(self.tag, span).to_upper()
// debug("normalize: region written = '\{r}'")
result.write("-")
result.write(r)
}
None => () // debug("normalize: no region")
}
// 5. 变体 (小写)
// debug("normalize: checking variants, count = \{self.state.variants.length()}")
for v in self.state.variants {
// debug("normalize: variant span = \{v}")
let s = sub(self.tag, v).to_lower()
// debug("normalize: variant written = '\{s}'")
result.write("-")
result.write(s)
}
// 6. 扩展 (小写)
// debug(
// "normalize: checking extensions, count = \{self.state.extensions.length()}",
// )
for ext in self.state.extensions {
// debug("normalize: extension ident = \{ext.ident}")
result.write("-")
result.write(sub(self.tag, ext.ident).to_lower())
for sub_tag in ext.subtags {
let s = sub(self.tag, sub_tag).to_lower()
// debug("normalize: extension subtag = '\{s}'")
result.write("-")
result.write(s)
}
}
// 7. 私有使用 (小写)
match self.state.private_use {
Some(p) => {
result.write("-")
result.write(sub(self.tag, p.prefix).to_lower())
for subtag in p.subtags {
result.write("-")
result.write(sub(self.tag, subtag).to_lower())
}
}
None => ()
}
// 重新解析归一化后的标签
let normalized = result.to_string()
// debug(("normalized: \{normalized}") // 添加调试
match LanguageTag::parse(normalized) {
Ok(t) => t
Err(_) => self
}
}
///|
fn parse_private_use_tag(tag : StringView) -> Result[LanguageTag, TagParseErr] {
let subtags = scan_subtags(tag)
if subtags.is_empty() {
return Err(TagParseErr::EmptyPrivateUse)
}
let prefix = subtags[0]
if !is_private_use_prefix(tag, prefix) {
return Err(TagParseErr::InvalidPrivateUsePrefix)
}
let subs : Array[TagSpan] = []
let mut i = 1
while i < subtags.length() {
let s = subtags[i]
if !is_valid_private_use_subtag(tag, s) {
return Err(TagParseErr::InvalidSubtag(s, sub(tag, s)))
}
subs.push(s)
i += 1
}
if subs.is_empty() {
return Err(TagParseErr::EmptyPrivateUse)
}
Ok({
tag,
state: {
language: (0, tag.length()), // 整个标签作为 language
extlang: None,
script: None,
region: None,
variants: [],
extensions: [],
private_use: Some({ prefix, subtags: subs }),
is_grandfathered: false,
},
})
}
// ============================================================
// 访问器
// ============================================================
///|
/// 主语言(原始格式)
/// 主语言(原始格式)
pub fn LanguageTag::primary_language(self : LanguageTag) -> StringView {
// 普通标签直接返回 language span
// 检查是否是纯私有使用标签(language 覆盖整个标签)
if self.state.language.1 == self.tag.length() {
self.tag // 纯私有使用标签,返回整个标签
} else {
sub(self.tag, self.state.language) // 普通标签,返回第一个子标签
}
}
///|
/// 扩展语言子标签列表
pub fn LanguageTag::extended_language_subtags(
self : LanguageTag,
) -> Array[StringView] {
match self.state.extlang {
Some(span) =>
// debug("extended_language_subtags \{span}")
// span.0 是主语言结束位置(包含 '-'),跳过它
split_by_span(self.tag, span.0, span.1, '-')
None => []
}
}
///|
/// 主语言 + 扩展语言(完整)
pub fn LanguageTag::full_language(self : LanguageTag) -> StringView {
match self.state.extlang {
Some(span) =>
// debug("full_language \{span}")
sub(self.tag, (0, span.1))
None => sub(self.tag, self.state.language)
}
}
///|
/// 脚本
pub fn LanguageTag::script(self : LanguageTag) -> StringView? {
self.state.script.map(fn(s) { sub(self.tag, s) })
}
///|
/// 区域
pub fn LanguageTag::region(self : LanguageTag) -> StringView? {
self.state.region.map(fn(r) { sub(self.tag, r) })
}
///|
/// 变体子标签列表
pub fn LanguageTag::variant_subtags(self : LanguageTag) -> Array[StringView] {
self.state.variants.iter().map(fn(v) { sub(self.tag, v) }).collect()
}
///|
/// 扩展迭代器
pub fn LanguageTag::extensions(self : LanguageTag) -> ExtensionIter {
ExtensionIter::{ tag: self.tag, extensions: self.state.extensions, index: 0 }
}
///|
/// 扩展语言(完整字符串,如 "cmn-hans",不含前缀 "-")
pub fn LanguageTag::extended_language(self : LanguageTag) -> StringView? {
match self.state.extlang {
Some(span) => {
// span.0 是主语言结束位置(包含 '-'),跳过它
let start = span.0 + 1
if start >= span.1 {
return None
}
Some(sub(self.tag, span))
}
None => None
}
}
///|
/// 私有使用子标签列表
pub fn LanguageTag::private_use_subtags(
self : LanguageTag,
) -> Array[StringView] {
match self.state.private_use {
None => []
Some(p) => p.subtags.iter().map(fn(s) { sub(self.tag, s) }).collect()
}
}
///|
/// 是否有扩展
pub fn LanguageTag::has_extensions(self : LanguageTag) -> Bool {
!self.state.extensions.is_empty()
}
///|
/// 是否有私有使用
pub fn LanguageTag::has_private_use(self : LanguageTag) -> Bool {
self.state.private_use is Some(_)
}
// ============================================================
// 扩展迭代器
// ============================================================
///|
pub struct ExtensionIter {
tag : StringView
extensions : Array[Extension]
index : Int
}
///|
/// 获取下一个扩展
/// 返回 (标识符, 子标签列表, 下一个迭代器)
fn ExtensionIter::next(
self : ExtensionIter,
) -> (StringView, Array[StringView], ExtensionIter)? {
match self.extensions.get(self.index) {
None => None
Some(ext) => {
let ident = sub(self.tag, ext.ident)
let subtags = ext.subtags.iter().map(fn(s) { sub(self.tag, s) }).collect()
let next = {
tag: self.tag,
extensions: self.extensions,
index: self.index + 1,
}
Some((ident, subtags, next))
}
}
}
///|
pub fn LanguageTag::extension_subtags(self : LanguageTag) -> ExtensionIter {
ExtensionIter::{ tag: self.tag, extensions: self.state.extensions, index: 0 }
}
///|
/// 转换为数组
pub fn ExtensionIter::to_array(
self : ExtensionIter,
) -> Array[(StringView, Array[StringView])] {
let result : Array[(StringView, Array[StringView])] = []
let mut iter = self
for ;; {
match iter.next() {
None => break
Some((ident, subtags, next)) => {
result.push((ident, subtags))
iter = next
}
}
}
result
}
///|
/// 验证标签格式(无连续 '-',无首尾 '-')
fn validate_tag_format(tag : StringView) -> Result[Unit, TagParseErr] {
if tag.length() == 0 {
return Ok(())
}
if tag[0] == '-' || tag[tag.length() - 1] == '-' {
return Err(TagParseErr::EmptySubtag)
}
let mut i = 1
while i < tag.length() {
if tag[i] == '-' && tag[i - 1] == '-' {
return Err(TagParseErr::EmptySubtag)
}
i += 1
}
Ok(())
}
///|
/// 检查字符是否有效(字母、数字、'-')
fn is_valid_char(c : Int) -> Bool {
is_alphanumeric_char(c) || c == '-'.to_int()
}
///|
/// 验证标签字符(只允许字母、数字、'-')
fn validate_tag_chars(tag : StringView) -> Result[Unit, TagParseErr] {
let mut i = 0
while i < tag.length() {
if !is_valid_char(tag[i].to_int()) {
return Err(TagParseErr::ForbiddenChar)
}
i += 1
}
Ok(())
}
///|
/// 检查私有使用子标签长度(1-8位)
fn is_valid_private_use_subtag(tag : StringView, span : TagSpan) -> Bool {
let len = span_len(span)
// 私有使用子标签:1-8位字母数字
len >= 1 && len <= 8 && is_alphanumeric_span(tag, span)
}