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
//! 空白压缩变换 —— 对应 Java `freemarker.template.utility.StandardCompress`
//! (逐字符状态机 StandardCompressWriter :117-245;`<#compress>` 指令内部即
//! CompressedBlock → StandardCompress.INSTANCE,CompressedBlock.java:42)
use crate::core::environment::RunSignal;
use crate::core::{Element, Environment};
use crate::error::Result;
use crate::template::{TModel, TemplateTransformModel};
use std::collections::HashMap;
/// 空白压缩变换(对应 StandardCompress.java)
pub struct StandardCompressTransform;
impl TemplateTransformModel for StandardCompressTransform {
fn transform_with_body(
&self,
env: &mut Environment,
params: &HashMap<String, TModel>,
body: &[Element],
) -> Result<RunSignal> {
// Java getWriter 参数(StandardCompress.java:92-115):buffer_size(数值,
// 缓冲分块——v1 整段捕获等价)、single_line(布尔)
let single_line = match params.get("single_line") {
Some(m) => m
.boolean
.as_ref()
.map(|b| b.as_boolean().unwrap_or(false))
.unwrap_or(false),
None => false,
};
let (signal, captured) = env.capture(|e| e.run(body))?;
env.emit(&standard_compress_text(&captured, single_line))?;
Ok(signal)
}
}
/// StandardCompress 的逐字符状态机 —— 对应 Java `StandardCompressWriter`
/// (writeHelper :153-171 / updateLineBreakState :173-195 /
/// writeLineBreakOrSpace :197-232)。语义:
/// - 前导空白忽略(AT_BEGINNING)
/// - 换行序列 → 单个换行,**保留原换行类型**(CR / LF / CRLF)
/// - 行内空白序列 → 单个空格(INIT → ' ')
/// - 尾部空白丢弃(从未写入缓冲)
/// - single_line=true → 换行输出为空格(SINGLE_LINE 状态)
///
/// 差异:Rust `char::is_whitespace`(Unicode White_Space)vs Java
/// `Character.isWhitespace`(不含 U+00A0 等)——边界字符行为略宽(P6 可补)。
pub fn standard_compress_text(s: &str, single_line: bool) -> String {
#[derive(PartialEq, Clone, Copy)]
enum Lb {
AtBeginning,
SingleLine,
Init,
SawCr,
LineBreakCr,
LineBreakCrLf,
LineBreakLf,
}
let mut out = String::new();
let mut in_ws = true;
let mut lb = Lb::AtBeginning;
for c in s.chars() {
if c.is_whitespace() {
in_ws = true;
// Java updateLineBreakState:仅 INIT / SAW_CR 状态推进
lb = match lb {
Lb::Init => {
if c == '\r' {
Lb::SawCr
} else if c == '\n' {
Lb::LineBreakLf
} else {
Lb::Init
}
}
Lb::SawCr => {
if c == '\n' {
Lb::LineBreakCrLf
} else {
Lb::LineBreakCr
}
}
other => other,
};
} else if in_ws {
in_ws = false;
// Java writeLineBreakOrSpace
match lb {
Lb::AtBeginning => {} // 前导空白忽略
Lb::SawCr | Lb::LineBreakCr => out.push('\r'),
Lb::LineBreakCrLf => {
out.push('\r');
out.push('\n');
}
Lb::LineBreakLf => out.push('\n'),
Lb::Init | Lb::SingleLine => out.push(' '),
}
lb = if single_line {
Lb::SingleLine
} else {
Lb::Init
};
out.push(c);
} else {
out.push(c);
}
}
out
}