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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
use arrayvec::ArrayVec;
use pulldown_cmark::{
Event::{self, *},
Options,
Tag::*,
};
use pulldown_cmark_to_cmark::Options as OutOptions;
use std::mem::{replace, take};
#[derive(Debug)]
pub struct Md<'e> {
/// 解析 md 文件的事件
events: Vec<Event<'e>>,
/// 内部缓冲。有两个用途:
/// 1. 提取的原文段落;
/// 2. 原文填充翻译内容之后的 md 文本。
///
/// 为了减少分配,小于 1024B 的文本以 1024B 字节长度初始化;
/// 大于 1024B 的文本以原文 2 倍字节长度初始化。
buffer: String,
/// 提取的原文段落的 bytes 分布
bytes: Vec<usize>,
/// 提取的原文段落的 chars 分布
chars: Vec<usize>,
/// 用于段落分批
limit: Limit,
}
impl<'e> Md<'e> {
/// 构造函数。
pub fn new(md: &'e str) -> Self {
Self { events: pulldown_cmark::Parser::new_ext(md, cmark_opt()).collect(),
buffer: {
const MINIMUM_CAPACITY: usize = 1 << 10;
let capacity = md.len();
let capacity = if capacity < MINIMUM_CAPACITY {
MINIMUM_CAPACITY
} else {
capacity * 2
};
String::with_capacity(capacity)
},
bytes: Vec::with_capacity(128), // 预先分配 128 个段落
chars: Vec::with_capacity(128), // 预先分配 128 个段落
limit: Limit::default(), }
}
/// 提取原文的段落文本,并以字符为单位记录段落分布。
///
/// TODO: 尽可能保存原样式/结构
fn extract_with_chars(&mut self) {
fn inner(m: &mut Md) {
let not_codeblock = &mut true;
let table = &mut false;
let buf = &mut m.buffer;
let len = &mut 0;
let cnt = &mut 0;
let bytes = &mut m.bytes;
let chars = &mut m.chars;
#[rustfmt::skip]
m.events.iter().for_each(|e| { extract_with_chars(e, not_codeblock, table, buf, len, bytes, cnt, chars) });
}
if self.buffer.is_empty() {
inner(self);
} else if self.chars.is_empty() {
if !self.bytes.is_empty() {
self.bytes.clear();
}
self.buffer.clear();
inner(self);
}
}
/// 以字符数量分割段落批次。
/// 分批策略如下:
/// - 当返回 `Some` 时,意味着返回完整的段落,且至少返回一个段落;
/// - 当返回 `None` 时,意味着已经返回所有段落。
/// - 每次返回的段落有两种情况:
///
/// 1. 不多于 limit 字符大小的完整段落(至少一个完整段落);
/// 2. 字符大小超过 limit 的**一个**段落。
///
/// ## 注意
/// - 本方法比 [`bytes_paragraph`] 多做了一件事:计算和记录每个段落的字符长度。
/// - 需要每个段落的字符或字节长度,请再调用: [`chars`][`Md::chars`] 或
/// [`bytes`][`Md::bytes`]。
/// - 此方法可以多次调用:这在需要不同 limit 的分批时很有用。但是注意:
/// - 调用 [`extract`] 之后再调用此方法会重复提取段落;
/// - 调用 [`bytes_paragraph`] 之后再调用此方法会重复提取段落;
/// - 多次调用此方法不会重复提取段落;
///
/// [`bytes_paragraph`]: `Md::bytes_paragraph`
/// [`extract`]: `Md::extract`
pub fn chars_paragraph(&mut self, limit: usize) -> impl Iterator<Item = &str> {
self.extract_with_chars();
self.limit = Limit::new(limit);
let limit = &mut self.limit;
let f = |(c, l): (&usize, &usize)| {
if let Some(i) = limit.chars(*c, *l) {
self.buffer.get(i)
} else {
None
}
};
let iter = std::iter::once(&usize::MAX);
self.chars
.iter()
.chain(iter.clone())
.zip(self.bytes.iter().chain(iter))
.filter_map(f)
}
/// 提取原文的段落文本,并以字节为单位记录段落分布。
/// # 注意
/// - 本方法比 [`extract`][`Md::extract`] 多做了一件事:计算和记录每个段落的字节长度。
/// - 需要每个段落的字节长度,请再调用:[`bytes`][`Md::bytes`]。
///
/// TODO: 尽可能保存原样式/结构
fn extract_with_bytes(&mut self) {
fn inner(m: &mut Md) {
let not_codeblock = &mut true;
let table = &mut false;
let buf = &mut m.buffer;
let len = &mut 0;
let bytes = &mut m.bytes;
m.events
.iter()
.for_each(|event| extract_with_bytes(event, not_codeblock, table, buf, len, bytes));
}
if self.buffer.is_empty() {
inner(self);
} else if self.bytes.is_empty() {
self.buffer.clear();
inner(self);
}
}
/// 提取原文的段落文本,并返回以字节数量分割的段落批次。
/// 分批策略如下:
/// - 当返回 `Some` 时,意味着返回完整的段落,且至少返回一个段落;
/// - 当返回 `None` 时,意味着已经返回所有段落。
/// - 每次返回的段落有两种情况:
///
/// 1. 不多于 limit 字节大小的完整段落(至少一个完整段落);
/// 2. 字节大小超过 limit 的**一个**段落。
///
/// ## 注意
/// - 本方法比 [`extract`] 多做了一件事:计算和记录每个段落的字节长度。
/// - 需要每个段落的字节长度,请调用:[`bytes`][`Md::bytes`]。
/// - 此方法可以多次调用:这在需要不同 limit 的分批时很有用。但是注意:
/// - 调用 [`extract`] 之后再调用此方法会重复提取段落;
/// - 调用 [`chars_paragraph`] 之后再调用此方法不会重复提取段落;
/// - 多次调用此方法不会重复提取段落;
///
/// [`chars_paragraph`]: `Md::chars_paragraph`
/// [`extract`]: `Md::extract`
pub fn bytes_paragraph(&mut self, limit: usize) -> impl Iterator<Item = &str> {
self.extract_with_bytes();
self.limit = Limit::new(limit);
let limit = &mut self.limit;
let f = |l: &usize| {
if let Some(i) = limit.bytes(*l) {
self.buffer.get(i)
} else {
None
}
};
self.bytes.iter().chain(std::iter::once(&usize::MAX)).filter_map(f)
}
/// 提取原文的段落文本。
///
/// ## 注意
/// - 此方法可以多次调用:
/// - 调用 [`bytes_paragraph`] 之后再调用此方法不会重复提取段落;
/// - 调用 [`chars_paragraph`] 之后再调用此方法不会重复提取段落;
/// - 多次调用此方法不会重复提取段落;
///
/// TODO: 尽可能保存原样式/结构
///
/// [`bytes_paragraph`]: `Md::bytes_paragraph`
/// [`chars_paragraph`]: `Md::chars_paragraph`
pub fn extract(&mut self) -> &str {
if self.buffer.is_empty() {
let not_codeblock = &mut true;
let table = &mut false;
let buf = &mut self.buffer;
self.events.iter().for_each(|event| extract(event, not_codeblock, table, buf));
}
&self.buffer
}
/// 浏览提取后的原文段落文本。
pub fn paragraphs(&self) -> &str { &self.buffer }
/// 提取的每个原文段落的字节数。
pub fn bytes(&self) -> impl Iterator<Item = usize> + '_ { self.bytes.iter().copied() }
/// 提取的每个原文段落的字符数。
pub fn chars(&self) -> impl Iterator<Item = usize> + '_ { self.chars.iter().copied() }
/// 提取的每个原文段落的字符数、字节数和字节范围。
pub fn chars_bytes_range(&self) -> impl Iterator<Item = (usize, usize, Range)> + '_ {
self.chars()
.zip(self.bytes())
.scan(0, |state, (c, l)| Some((c, l, replace(state, *state + l)..*state)))
}
/// 完成并返回写入翻译内容。参数 `paragraph` 为按段落翻译的**译文**。
pub fn done(mut self, mut paragraph: impl Iterator<Item = &'e str>) -> String {
self.buffer.clear();
let table = &mut false;
let output = self.events.into_iter().flat_map(|e| prepend(e, table, &mut paragraph));
let opt = cmark_to_cmark_opt();
pulldown_cmark_to_cmark::cmark_with_options(output, &mut self.buffer, opt).unwrap();
// dbg!(self.output.len(),
// self.output.capacity(),
// self.raw_len * 2,
// self.output.len() <= self.raw_len * 2,
// self.output.len() <= MINIMUM_CAPACITY,
// self.output.len() <= self.raw_len * 2 || self.output.len() <= MINIMUM_CAPACITY);
self.buffer
}
}
type Range = std::ops::Range<usize>;
#[derive(Debug, Default)]
struct Limit {
limit: usize,
cnt: usize,
len: usize,
pos: usize,
}
impl Limit {
#[rustfmt::skip]
fn new(limit: usize) -> Self { Self { limit, cnt: 0, len: 0, pos: 0 } }
fn bytes(&mut self, len: usize) -> Option<Range> {
// dbg!(len, &self);
if let Some(add) = self.len.checked_add(len) {
if add <= self.limit {
self.len = add;
return None;
} else {
let p = self.pos;
let rhs = if self.len == 0 {
len
} else {
replace(&mut self.len, len)
};
// 到达最后一批时:当 bat=0, len=usize::Max 时会出现 Err
if let Some(add) = self.pos.checked_add(rhs) {
self.pos = add;
return Some(p..self.pos);
}
}
}
// 返回最后一批段落
if self.len == 0 {
// 当 len=0 时,最后一批段落是空串,因此需要提前结束掉
None
} else {
Some(self.pos..self.pos + self.len)
}
}
fn chars(&mut self, cnt: usize, len: usize) -> Option<Range> {
// dbg!(cnt, len, &self);
if let Some(add) = self.len.checked_add(len) {
if self.cnt + cnt <= self.limit {
self.len = add;
self.cnt += cnt;
return None;
} else {
let p = self.pos;
let rhs = if self.len == 0 {
len
} else {
self.cnt = cnt;
replace(&mut self.len, len)
};
// 到达最后一批时:当 bat=0, len=usize::Max 时会出现 Err
if let Some(add) = self.pos.checked_add(rhs) {
self.pos = add;
return Some(p..self.pos);
}
}
}
// 返回最后一批段落
if self.len == 0 {
// 当 len=0 时,最后一批段落是空串,因此需要提前结束掉
None
} else {
Some(self.pos..self.pos + self.len)
}
}
}
/// 开启 `pulldown_cmark::Options` 除 `SMART_PUNCTUATION` 之外的所有功能
pub fn cmark_opt() -> Options {
let mut options = Options::all();
options.remove(Options::ENABLE_SMART_PUNCTUATION);
options
}
/// 把 `pulldown_cmark_to_cmark::Options` 的 `code_block_backticks` 设置为 3
pub fn cmark_to_cmark_opt() -> OutOptions<'static> {
OutOptions { code_block_token_count: 3, ..OutOptions::default() }
}
const MAXIMUM_EVENTS: usize = 4;
pub fn prepend<'e>(event: Event<'e>, table: &mut bool,
paragraph: &mut impl Iterator<Item = &'e str>)
-> ArrayVec<Event<'e>, MAXIMUM_EVENTS> {
let mut arr = ArrayVec::<_, MAXIMUM_EVENTS>::new();
debug!("event: {:?}", event);
match event.clone() {
End(Paragraph) => {
// ATTENTION: `if let` guards are experimental
if let Some(p) = paragraph.next() {
// log::debug!("paragraph: {:?}", p);
// arr.push(SoftBreak); // TODO: 是否空行
arr.extend([SoftBreak, SoftBreak, Text(p.into()), event]);
} else {
log::warn!("翻译内容提前结束写入,输出文件从某处起只有原文,没有译文:\
因此可能存在 bug,如果方便的话请提交 issue 帮助排查。");
arr.extend([event]);
}
}
End(Heading(n, opt, v)) => {
if let Some(p) = paragraph.next() {
arr.extend([event,
Start(Heading(n, opt, v.clone())),
Text(p.into()),
End(Heading(n, opt, v.clone()))]);
} else {
log::warn!("翻译内容提前结束写入,输出文件从某处起只有原文,没有译文:\
因此可能存在 bug,如果方便的话请提交 issue 帮助排查。");
arr.extend([event]);
}
}
Start(Table(_)) => {
*table = true;
arr.extend([event]);
}
End(Table(_)) => {
*table = false;
arr.extend([event]);
}
Text(_) if *table => {
if let Some(p) = paragraph.next() {
arr.extend([event, Text('\t'.into()), Text(p.into())]);
} else {
log::warn!("翻译内容提前结束写入,输出文件从某处起只有原文,没有译文:\
因此可能存在 bug,如果方便的话请提交 issue 帮助排查。");
arr.extend([event]);
}
}
_ => arr.extend([event]),
}
arr
}
/// 取出需要被翻译的内容:按照段落或标题
pub fn extract(event: &Event, not_codeblock: &mut bool, table: &mut bool, buf: &mut String) {
match event {
End(Paragraph | Heading(..)) => buf.push('\n'),
Text(x) if *not_codeblock => {
buf.push_str(x.as_ref());
if *table {
buf.push('\n');
}
}
SoftBreak | HardBreak => buf.push(' '),
Code(x) => {
buf.push('`');
buf.push_str(x.as_ref());
buf.push('`');
}
Start(CodeBlock(_)) => *not_codeblock = false,
Start(Table(_)) => *table = true,
End(CodeBlock(_)) => *not_codeblock = true,
End(Table(_)) => *table = false,
_ => (),
}
}
/// 取出需要被翻译的内容:按照段落或标题。
pub fn extract_with_bytes(event: &Event, not_codeblock: &mut bool, table: &mut bool,
buf: &mut String, len: &mut usize, vec: &mut Vec<usize>) {
match event {
End(Paragraph | Heading(..)) => {
buf.push('\n');
vec.push(take(len) + 1);
}
Text(x) if *not_codeblock => {
buf.push_str(x.as_ref());
if *table {
buf.push('\n');
vec.push(take(len) + x.len() + 1);
} else {
*len += x.len();
};
}
SoftBreak | HardBreak => {
buf.push(' ');
*len += 1;
}
Code(x) => {
buf.push('`');
buf.push_str(x.as_ref());
buf.push('`');
*len += x.len() + 2;
}
Start(CodeBlock(_)) => *not_codeblock = false,
Start(Table(_)) => *table = true,
End(CodeBlock(_)) => *not_codeblock = true,
End(Table(_)) => *table = false,
_ => (),
}
}
/// 取出需要被翻译的内容:按照段落或标题
#[allow(clippy::too_many_arguments)]
pub fn extract_with_chars(event: &Event, not_codeblock: &mut bool, table: &mut bool,
buf: &mut String, len: &mut usize, bytes: &mut Vec<usize>,
cnt: &mut usize, chars: &mut Vec<usize>) {
match event {
End(Paragraph | Heading(..)) => {
buf.push('\n');
bytes.push(take(len) + 1);
chars.push(take(cnt) + 1);
}
Text(x) if *not_codeblock => {
buf.push_str(x.as_ref());
if *table {
buf.push('\n');
bytes.push(take(len) + x.len() + 1);
chars.push(take(cnt) + x.chars().count() + 1);
} else {
*len += x.len();
*cnt += x.chars().count();
}
}
SoftBreak | HardBreak => {
buf.push(' ');
*len += 1;
*cnt += 1;
}
Code(x) => {
buf.push('`');
buf.push_str(x.as_ref());
buf.push('`');
*len += x.len() + 2;
*cnt += x.chars().count() + 2;
}
Start(CodeBlock(_)) => *not_codeblock = false,
Start(Table(_)) => *table = true,
End(CodeBlock(_)) => *not_codeblock = true,
End(Table(_)) => *table = false,
_ => (),
}
}