Skip to content

Commit e7a701b

Browse files
authored
perf: faster escapeAllSwigTags (#5699)
1 parent b151d98 commit e7a701b

File tree

1 file changed

+57
-62
lines changed

1 file changed

+57
-62
lines changed

lib/hexo/post.ts

Lines changed: 57 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ const rSwigTag = /(\{\{.+?\}\})|(\{#.+?#\})|(\{%.+?%\})/s;
1818
const rSwigPlaceHolder = /(?:<|&lt;)!--swig\uFFFC(\d+)--(?:>|&gt;)/g;
1919
const rCodeBlockPlaceHolder = /(?:<|&lt;)!--code\uFFFC(\d+)--(?:>|&gt;)/g;
2020

21-
const STATE_PLAINTEXT = Symbol('plaintext');
22-
const STATE_SWIG_VAR = Symbol('swig_var');
23-
const STATE_SWIG_COMMENT = Symbol('swig_comment');
24-
const STATE_SWIG_TAG = Symbol('swig_tag');
25-
const STATE_SWIG_FULL_TAG = Symbol('swig_full_tag');
21+
const STATE_PLAINTEXT = 0;
22+
const STATE_SWIG_VAR = 1;
23+
const STATE_SWIG_COMMENT = 2;
24+
const STATE_SWIG_TAG = 3;
25+
const STATE_SWIG_FULL_TAG = 4;
2626

2727
const isNonWhiteSpaceChar = (char: string) => char !== '\r'
2828
&& char !== '\n'
@@ -31,22 +31,6 @@ const isNonWhiteSpaceChar = (char: string) => char !== '\r'
3131
&& char !== '\v'
3232
&& char !== ' ';
3333

34-
class StringBuilder {
35-
private parts: string[];
36-
37-
constructor() {
38-
this.parts = [];
39-
}
40-
41-
append(str: string): void {
42-
this.parts.push(str);
43-
}
44-
45-
toString(): string {
46-
return this.parts.join('');
47-
}
48-
}
49-
5034
class PostRenderEscape {
5135
public stored: string[];
5236
public length: number;
@@ -87,13 +71,16 @@ class PostRenderEscape {
8771
*/
8872
escapeAllSwigTags(str: string) {
8973
let state = STATE_PLAINTEXT;
90-
let buffer = '';
91-
const output = new StringBuilder();
74+
let buffer_start = -1;
75+
let plain_text_start = 0;
76+
let output = '';
9277

9378
let swig_tag_name_begin = false;
9479
let swig_tag_name_end = false;
9580
let swig_tag_name = '';
96-
let swig_full_tag_start_buffer = '';
81+
82+
let swig_full_tag_start_start = -1;
83+
let swig_full_tag_start_end = -1;
9784
// current we just consider one level of string quote
9885
let swig_string_quote = '';
9986

@@ -102,11 +89,24 @@ class PostRenderEscape {
10289
let idx = 0;
10390

10491
// for backtracking
105-
const swig_start_idx = {
106-
[STATE_SWIG_VAR]: 0,
107-
[STATE_SWIG_COMMENT]: 0,
108-
[STATE_SWIG_TAG]: 0,
109-
[STATE_SWIG_FULL_TAG]: 0
92+
const swig_start_idx = [0, 0, 0, 0, 0];
93+
94+
const flushPlainText = (end: number) => {
95+
if (plain_text_start !== -1 && end > plain_text_start) {
96+
output += str.slice(plain_text_start, end);
97+
}
98+
plain_text_start = -1;
99+
};
100+
101+
const ensurePlainTextStart = (position: number) => {
102+
if (plain_text_start === -1) {
103+
plain_text_start = position;
104+
}
105+
};
106+
107+
const pushAndReset = (value: string) => {
108+
output += value;
109+
plain_text_start = -1;
110110
};
111111

112112
while (idx < length) {
@@ -115,29 +115,33 @@ class PostRenderEscape {
115115
const next_char = str[idx + 1];
116116

117117
if (state === STATE_PLAINTEXT) { // From plain text to swig
118+
ensurePlainTextStart(idx);
118119
if (char === '{') {
119120
// check if it is a complete tag {{ }}
120121
if (next_char === '{') {
122+
flushPlainText(idx);
121123
state = STATE_SWIG_VAR;
122124
idx++;
125+
buffer_start = idx + 1;
123126
swig_start_idx[state] = idx;
124127
} else if (next_char === '#') {
128+
flushPlainText(idx);
125129
state = STATE_SWIG_COMMENT;
126130
idx++;
131+
buffer_start = idx + 1;
127132
swig_start_idx[state] = idx;
128133
} else if (next_char === '%') {
134+
flushPlainText(idx);
129135
state = STATE_SWIG_TAG;
130136
idx++;
137+
buffer_start = idx + 1;
138+
swig_full_tag_start_start = idx + 1;
139+
swig_full_tag_start_end = idx + 1;
131140
swig_tag_name = '';
132-
swig_full_tag_start_buffer = '';
133141
swig_tag_name_begin = false; // Mark if it is the first non white space char in the swig tag
134142
swig_tag_name_end = false;
135143
swig_start_idx[state] = idx;
136-
} else {
137-
output.append(char);
138144
}
139-
} else {
140-
output.append(char);
141145
}
142146
} else if (state === STATE_SWIG_TAG) {
143147
if (char === '"' || char === '\'') {
@@ -152,24 +156,23 @@ class PostRenderEscape {
152156
// From swig back to plain text
153157
swig_tag_name = '';
154158
state = STATE_PLAINTEXT;
155-
output.append(`{%${buffer}${char}`);
156-
buffer = '';
159+
pushAndReset(`{%${str.slice(buffer_start, idx)}${char}`);
157160
} else if (char === '%' && next_char === '}' && swig_string_quote === '') { // From swig back to plain text
158161
idx++;
159162
if (swig_tag_name !== '' && str.includes(`end${swig_tag_name}`)) {
160163
state = STATE_SWIG_FULL_TAG;
164+
buffer_start = idx + 1;
165+
// since we have already move idx to next char of '}', so here is idx -1
166+
swig_full_tag_start_end = idx - 1;
161167
swig_start_idx[state] = idx;
162168
} else {
163169
swig_tag_name = '';
164170
state = STATE_PLAINTEXT;
165-
output.append(PostRenderEscape.escapeContent(this.stored, 'swig', `{%${buffer}%}`));
171+
// since we have already move idx to next char of '}', so here is idx -1
172+
pushAndReset(PostRenderEscape.escapeContent(this.stored, 'swig', `{%${str.slice(buffer_start, idx - 1)}%}`));
166173
}
167174

168-
buffer = '';
169175
} else {
170-
buffer = buffer + char;
171-
swig_full_tag_start_buffer = swig_full_tag_start_buffer + char;
172-
173176
if (isNonWhiteSpaceChar(char)) {
174177
if (!swig_tag_name_begin && !swig_tag_name_end) {
175178
swig_tag_name_begin = true;
@@ -197,21 +200,17 @@ class PostRenderEscape {
197200
if (char === '}' && next_char !== '}' && swig_string_quote === '') {
198201
// From swig back to plain text
199202
state = STATE_PLAINTEXT;
200-
output.append(`{{${buffer}${char}`);
201-
buffer = '';
203+
pushAndReset(`{{${str.slice(buffer_start, idx)}${char}`);
202204
} else if (char === '}' && next_char === '}' && swig_string_quote === '') {
205+
pushAndReset(PostRenderEscape.escapeContent(this.stored, 'swig', `{{${str.slice(buffer_start, idx)}}}`));
203206
idx++;
204207
state = STATE_PLAINTEXT;
205-
output.append(PostRenderEscape.escapeContent(this.stored, 'swig', `{{${buffer}}}`));
206-
buffer = '';
207-
} else {
208-
buffer = buffer + char;
209208
}
210209
} else if (state === STATE_SWIG_COMMENT) { // From swig back to plain text
211210
if (char === '#' && next_char === '}') {
212211
idx++;
213212
state = STATE_PLAINTEXT;
214-
buffer = '';
213+
plain_text_start = -1;
215214
}
216215
} else if (state === STATE_SWIG_FULL_TAG) {
217216
if (char === '{' && next_char === '%') {
@@ -234,16 +233,10 @@ class PostRenderEscape {
234233

235234
if (swig_full_tag_found && swig_full_tag_end_buffer.includes(`end${swig_tag_name}`)) {
236235
state = STATE_PLAINTEXT;
237-
output.append(PostRenderEscape.escapeContent(this.stored, 'swig', `{%${swig_full_tag_start_buffer}%}${buffer}{%${swig_full_tag_end_buffer}%}`));
236+
pushAndReset(PostRenderEscape.escapeContent(this.stored, 'swig', `{%${str.slice(swig_full_tag_start_start, swig_full_tag_start_end)}%}${str.slice(buffer_start, idx)}{%${swig_full_tag_end_buffer}%}`));
238237
idx = _idx;
239-
swig_full_tag_start_buffer = '';
240238
swig_full_tag_end_buffer = '';
241-
buffer = '';
242-
} else {
243-
buffer += char;
244239
}
245-
} else {
246-
buffer += char;
247240
}
248241
}
249242
idx++;
@@ -252,19 +245,21 @@ class PostRenderEscape {
252245
break;
253246
}
254247
// If the swig tag is not closed, then it is a plain text, we need to backtrack
255-
idx = swig_start_idx[state];
256-
buffer = '';
257-
swig_string_quote = '';
258248
if (state === STATE_SWIG_FULL_TAG) {
259-
output.append(`{%${swig_full_tag_start_buffer}%`);
249+
pushAndReset(`{%${str.slice(swig_full_tag_start_start, swig_full_tag_start_end)}%`);
260250
} else {
261-
output.append('{');
251+
pushAndReset('{');
262252
}
263-
swig_full_tag_start_buffer = '';
253+
idx = swig_start_idx[state];
254+
swig_string_quote = '';
264255
state = STATE_PLAINTEXT;
265256
}
266257

267-
return output.toString();
258+
if (plain_text_start !== -1 && plain_text_start < length) {
259+
output += str.slice(plain_text_start);
260+
}
261+
262+
return output;
268263
}
269264
}
270265

0 commit comments

Comments
 (0)