1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use super::{CharSequenceValue, TextUtilsError};
/// IE 条件注释解析结果。
///
/// 对应 Java:
/// `org.thymeleaf.standard.util.StandardConditionalCommentUtils.ConditionalCommentParsingResult`。
///
/// 所有 offset/len 均以原始 Java `CharSequence` 的 UTF-16 code unit 为单位。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ConditionalCommentParsingResult {
start_expression_offset: i32,
start_expression_len: i32,
content_offset: i32,
content_len: i32,
end_expression_offset: i32,
end_expression_len: i32,
}
impl ConditionalCommentParsingResult {
/// 使用六个 UTF-16 范围值创建解析结果。
///
/// 对应 Java:
/// `ConditionalCommentParsingResult#ConditionalCommentParsingResult(int,int,int,int,int,int)`。
///
/// # 参数
///
/// 参数按 Java 构造器顺序分别表示起始表达式、内容和结束表达式的 offset/len。
///
/// # 返回
///
/// 不执行额外校验、原样保存全部 Java `int` 的结果。
#[must_use]
pub const fn new(
start_expression_offset: i32,
start_expression_len: i32,
content_offset: i32,
content_len: i32,
end_expression_offset: i32,
end_expression_len: i32,
) -> Self {
Self {
start_expression_offset,
start_expression_len,
content_offset,
content_len,
end_expression_offset,
end_expression_len,
}
}
/// 返回起始条件表达式 offset。对应 Java `getStartExpressionOffset()`。
#[must_use]
pub const fn get_start_expression_offset(&self) -> i32 {
self.start_expression_offset
}
/// 返回起始条件表达式长度。对应 Java `getStartExpressionLen()`。
#[must_use]
pub const fn get_start_expression_len(&self) -> i32 {
self.start_expression_len
}
/// 返回条件注释内容 offset。对应 Java `getContentOffset()`。
#[must_use]
pub const fn get_content_offset(&self) -> i32 {
self.content_offset
}
/// 返回条件注释内容长度。对应 Java `getContentLen()`。
#[must_use]
pub const fn get_content_len(&self) -> i32 {
self.content_len
}
/// 返回结束条件表达式 offset。对应 Java `getEndExpressionOffset()`。
#[must_use]
pub const fn get_end_expression_offset(&self) -> i32 {
self.end_expression_offset
}
/// 返回结束条件表达式长度。对应 Java `getEndExpressionLen()`。
#[must_use]
pub const fn get_end_expression_len(&self) -> i32 {
self.end_expression_len
}
}
/// Thymeleaf Standard Dialect 的 IE 条件注释解析工具。
///
/// 对应 Java: `org.thymeleaf.standard.util.StandardConditionalCommentUtils`。
///
/// 解析器不先验证完整 `<!--...-->` 外壳,而是严格从 UTF-16 offset 4 和
/// `length - 4` 两端按上游顺序扫描。格式不匹配返回 `None`;动态
/// `CharSequence#length/charAt` 的异常与调用顺序原样传播。
pub struct StandardConditionalCommentUtils {
_private: (),
}
impl StandardConditionalCommentUtils {
/// 尝试把文本解析为 IE 条件注释。
///
/// 对应 Java:
/// `StandardConditionalCommentUtils#parseConditionalComment(CharSequence)`。
///
/// # 参数
///
/// - `text`:原始动态 Java `CharSequence`;`None` 对应 Java `null`。
///
/// # 返回
///
/// 格式有效时返回三个 UTF-16 范围,无效格式返回 `None`。
///
/// # 错误
///
/// `text` 为 `None` 时返回 Java `NullPointerException` 等价错误;自定义
/// `CharSequence` 的 length/charAt 异常原样传播。
pub fn parse_conditional_comment(
text: Option<&dyn CharSequenceValue>,
) -> Result<Option<ConditionalCommentParsingResult>, TextUtilsError> {
let text = text.ok_or(TextUtilsError::NullPointer)?;
let len = text.length()?;
let mut i = 4;
while i < len && is_java_whitespace(text.char_at(i)?) {
i += 1;
}
if i >= len || text.char_at(i)? != u16::from(b'[') {
return Ok(None);
}
i += 1;
let start_expression_offset = i;
while i < len && text.char_at(i)? != u16::from(b']') {
i += 1;
}
if i >= len {
return Ok(None);
}
let start_expression_len = i - start_expression_offset;
i += 1;
while i < len && is_java_whitespace(text.char_at(i)?) {
i += 1;
}
if i >= len || text.char_at(i)? != u16::from(b'>') {
return Ok(None);
}
i += 1;
let content_offset = i;
i = len.wrapping_sub(4);
while i > content_offset && is_java_whitespace(text.char_at(i)?) {
i -= 1;
}
if i <= content_offset || text.char_at(i)? != u16::from(b']') {
return Ok(None);
}
i -= 1;
let end_expression_last_pos = i + 1;
while i > content_offset && text.char_at(i)? != u16::from(b'[') {
i -= 1;
}
if i <= content_offset {
return Ok(None);
}
let end_expression_offset = i + 1;
let end_expression_len = end_expression_last_pos - end_expression_offset;
i -= 1;
while i >= content_offset && is_java_whitespace(text.char_at(i)?) {
i -= 1;
}
if i <= content_offset || text.char_at(i)? != u16::from(b'!') {
return Ok(None);
}
i -= 1;
if i <= content_offset || text.char_at(i)? != u16::from(b'<') {
return Ok(None);
}
i -= 1;
let content_len = (i + 1) - content_offset;
if content_len <= 0 || start_expression_len <= 0 || end_expression_len <= 0 {
return Ok(None);
}
Ok(Some(ConditionalCommentParsingResult::new(
start_expression_offset,
start_expression_len,
content_offset,
content_len,
end_expression_offset,
end_expression_len,
)))
}
}
fn is_java_whitespace(character: u16) -> bool {
matches!(
character,
0x0009..=0x000D
| 0x001C..=0x0020
| 0x1680
| 0x2000..=0x2006
| 0x2008..=0x200A
| 0x2028
| 0x2029
| 0x205F
| 0x3000
)
}