@wdprlib/render 2.1.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2344 -1668
- package/dist/index.d.cts +15 -13
- package/dist/index.d.ts +15 -13
- package/dist/index.js +2375 -1699
- package/package.json +1 -1
- package/src/context/attributes.ts +14 -0
- package/src/context/bibliography.ts +109 -0
- package/src/context/counters.ts +51 -0
- package/src/context/image-urls.ts +31 -0
- package/src/context/index.ts +285 -0
- package/src/context/output.ts +17 -0
- package/src/context/page-urls.ts +81 -0
- package/src/context/style-slots.ts +29 -0
- package/src/context/urls.ts +2 -0
- package/src/elements/bibliography/block.ts +27 -0
- package/src/elements/bibliography/cite.ts +23 -0
- package/src/elements/bibliography/ids.ts +9 -0
- package/src/elements/bibliography/index.ts +9 -0
- package/src/elements/code/contents.ts +18 -0
- package/src/elements/code/index.ts +29 -0
- package/src/elements/collapsible/index.ts +31 -0
- package/src/elements/collapsible/labels.ts +35 -0
- package/src/elements/collapsible/link.ts +11 -0
- package/src/elements/collapsible/sections.ts +39 -0
- package/src/elements/container/attributes.ts +28 -0
- package/src/elements/container/header.ts +27 -0
- package/src/elements/container/index.ts +35 -0
- package/src/elements/container/string-container.ts +40 -0
- package/src/elements/container/string-types.ts +63 -0
- package/src/elements/container/wrappers.ts +32 -0
- package/src/elements/date/format.ts +20 -0
- package/src/elements/{date.ts → date/index.ts} +4 -29
- package/src/elements/date/output.ts +6 -0
- package/src/elements/embed/iframe.ts +8 -0
- package/src/elements/embed/index.ts +28 -0
- package/src/elements/embed/providers.ts +43 -0
- package/src/elements/embed/validation.ts +15 -0
- package/src/elements/embed-block/allowlist.ts +60 -0
- package/src/elements/embed-block/boolean-attributes.ts +38 -0
- package/src/elements/embed-block/iframe.ts +33 -0
- package/src/elements/embed-block/index.ts +31 -0
- package/src/elements/embed-block/sanitize-config.ts +22 -0
- package/src/elements/embed-block/sanitize.ts +44 -0
- package/src/elements/expr/branch.ts +29 -0
- package/src/elements/expr/index.ts +63 -0
- package/src/elements/expr/result.ts +19 -0
- package/src/elements/footnote/body.ts +11 -0
- package/src/elements/footnote/index.ts +35 -0
- package/src/elements/footnote/ref.ts +16 -0
- package/src/elements/html/attributes.ts +24 -0
- package/src/elements/html/index.ts +39 -0
- package/src/elements/html/url.ts +19 -0
- package/src/elements/iframe/attributes.ts +28 -0
- package/src/elements/iframe/index.ts +22 -0
- package/src/elements/iftags/condition.ts +42 -0
- package/src/elements/iftags/index.ts +39 -0
- package/src/elements/iftags/style-slot.ts +23 -0
- package/src/elements/iftags/tokens.ts +36 -0
- package/src/elements/image/alignment.ts +44 -0
- package/src/elements/image/attributes.ts +10 -0
- package/src/elements/image/img-attributes.ts +26 -0
- package/src/elements/image/index.ts +36 -0
- package/src/elements/image/link-href.ts +24 -0
- package/src/elements/image/link.ts +13 -0
- package/src/elements/image/source.ts +16 -0
- package/src/elements/{include.ts → include/index.ts} +5 -13
- package/src/elements/include/missing.ts +15 -0
- package/src/elements/link/anchor-name.ts +6 -0
- package/src/elements/link/anchor.ts +27 -0
- package/src/elements/link/attributes.ts +47 -0
- package/src/elements/link/index.ts +26 -0
- package/src/elements/link/label.ts +23 -0
- package/src/elements/link/target.ts +20 -0
- package/src/elements/list/attributes.ts +19 -0
- package/src/elements/list/definition-list.ts +16 -0
- package/src/elements/list/index.ts +48 -0
- package/src/elements/list/item-rendering.ts +38 -0
- package/src/elements/list/items.ts +61 -0
- package/src/elements/list/no-marker.ts +53 -0
- package/src/elements/list/paragraphs.ts +34 -0
- package/src/elements/list/trim.ts +38 -0
- package/src/elements/math/block.ts +29 -0
- package/src/elements/math/equation-ref.ts +12 -0
- package/src/elements/math/index.ts +14 -0
- package/src/elements/math/inline.ts +19 -0
- package/src/elements/math/latex.ts +27 -0
- package/src/elements/math/source.ts +18 -0
- package/src/elements/module/backlinks.ts +2 -1
- package/src/elements/module/categories.ts +2 -2
- package/src/elements/module/empty-container.ts +10 -0
- package/src/elements/module/index.ts +2 -4
- package/src/elements/module/join-markup.ts +10 -0
- package/src/elements/module/join.ts +2 -7
- package/src/elements/module/listpages.ts +2 -2
- package/src/elements/module/listusers.ts +2 -2
- package/src/elements/module/page-tree.ts +2 -2
- package/src/elements/module/rate-markup.ts +10 -0
- package/src/elements/module/rate.ts +4 -13
- package/src/elements/module/unknown.ts +11 -0
- package/src/elements/tab-view/ids.ts +16 -0
- package/src/elements/tab-view/index.ts +31 -0
- package/src/elements/tab-view/navigation.ts +15 -0
- package/src/elements/tab-view/panels.ts +16 -0
- package/src/elements/table/attributes.ts +23 -0
- package/src/elements/table/cell-attributes.ts +62 -0
- package/src/elements/table/cell.ts +13 -0
- package/src/elements/table/index.ts +27 -0
- package/src/elements/text/email.ts +20 -0
- package/src/elements/text/index.ts +11 -0
- package/src/elements/text/plain.ts +11 -0
- package/src/elements/text/raw.ts +20 -0
- package/src/elements/toc/body.ts +12 -0
- package/src/elements/toc/entries.ts +34 -0
- package/src/elements/toc/frame.ts +27 -0
- package/src/elements/toc/index.ts +17 -0
- package/src/elements/toc/link.ts +26 -0
- package/src/elements/user/index.ts +40 -0
- package/src/elements/user/markup.ts +34 -0
- package/src/elements/user/resolve.ts +6 -0
- package/src/escape/attribute-allowlists.ts +101 -0
- package/src/escape/attributes.ts +62 -0
- package/src/escape/css-color-functions.ts +18 -0
- package/src/escape/css-colors.ts +183 -0
- package/src/escape/css-danger.ts +22 -0
- package/src/escape/css-normalize.ts +54 -0
- package/src/escape/css-style.ts +78 -0
- package/src/escape/css-urls.ts +76 -0
- package/src/escape/css.ts +4 -0
- package/src/escape/email.ts +22 -0
- package/src/escape/html.ts +68 -0
- package/src/escape/index.ts +15 -0
- package/src/escape/url.ts +18 -0
- package/src/libs/highlighter/engine/end-pattern.ts +26 -0
- package/src/libs/highlighter/engine/html.ts +19 -0
- package/src/libs/highlighter/engine/index.ts +3 -0
- package/src/libs/highlighter/engine/keywords.ts +22 -0
- package/src/libs/highlighter/engine/parts.ts +36 -0
- package/src/libs/highlighter/engine/preprocess.ts +10 -0
- package/src/libs/highlighter/engine/render.ts +31 -0
- package/src/libs/highlighter/engine/token.ts +7 -0
- package/src/libs/highlighter/engine/tokenizer.ts +266 -0
- package/src/libs/highlighter/engine/utils.ts +38 -0
- package/src/render/collected-styles.ts +22 -0
- package/src/render/dispatch.ts +181 -0
- package/src/render/index.ts +28 -0
- package/src/render/primitives.ts +17 -0
- package/src/render/style-tag.ts +6 -0
- package/src/render/style.ts +15 -0
- package/src/types.ts +6 -2
- package/src/context.ts +0 -422
- package/src/elements/bibliography.ts +0 -123
- package/src/elements/code.ts +0 -49
- package/src/elements/collapsible.ts +0 -105
- package/src/elements/container.ts +0 -302
- package/src/elements/embed-block.ts +0 -327
- package/src/elements/embed.ts +0 -166
- package/src/elements/expr.ts +0 -102
- package/src/elements/footnote.ts +0 -76
- package/src/elements/html.ts +0 -79
- package/src/elements/iframe.ts +0 -44
- package/src/elements/iftags.ts +0 -118
- package/src/elements/image.ts +0 -154
- package/src/elements/link.ts +0 -201
- package/src/elements/list.ts +0 -241
- package/src/elements/math.ts +0 -177
- package/src/elements/tab-view.ts +0 -75
- package/src/elements/table.ts +0 -101
- package/src/elements/text.ts +0 -57
- package/src/elements/toc.ts +0 -147
- package/src/elements/user.ts +0 -79
- package/src/escape.ts +0 -829
- package/src/libs/highlighter/engine.ts +0 -352
- package/src/render.ts +0 -231
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* Tokenizer and renderer for the Text_Highlighter-compatible syntax
|
|
4
|
-
* highlighting engine. This is a faithful TypeScript port of the
|
|
5
|
-
* PEAR Text_Highlighter 0.5.1 PHP library's `_getToken` algorithm and
|
|
6
|
-
* HTML renderer.
|
|
7
|
-
*
|
|
8
|
-
* The engine processes source code through a state-machine-based tokenizer
|
|
9
|
-
* that assigns CSS class names to each token, then renders the tokens as
|
|
10
|
-
* `<span class="hl-*">` elements.
|
|
11
|
-
*
|
|
12
|
-
* @module
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { LanguageDefinition } from "./types";
|
|
16
|
-
|
|
17
|
-
/** A single highlighted token with its CSS class and text content. */
|
|
18
|
-
interface Token {
|
|
19
|
-
/** CSS class name suffix (used as `hl-{class}`). */
|
|
20
|
-
class: string;
|
|
21
|
-
/** The literal text content of this token. */
|
|
22
|
-
content: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Tokenize source code using a language definition's state machine.
|
|
27
|
-
*
|
|
28
|
-
* This is a faithful port of PEAR Text_Highlighter's `_getToken` algorithm.
|
|
29
|
-
* The key difference from PHP is that JavaScript lacks `PREG_OFFSET_CAPTURE`,
|
|
30
|
-
* so capture group positions are computed from the match result.
|
|
31
|
-
*
|
|
32
|
-
* The input is preprocessed to normalize line endings, replace tabs with
|
|
33
|
-
* spaces, and ensure empty lines have at least one space character
|
|
34
|
-
* (matching PHP's behavior).
|
|
35
|
-
*
|
|
36
|
-
* @param def - The language definition describing the state machine.
|
|
37
|
-
* @param input - Raw source code string to tokenize.
|
|
38
|
-
* @returns Array of tokens, each with a CSS class and content string.
|
|
39
|
-
*/
|
|
40
|
-
export function tokenize(def: LanguageDefinition, input: string): Token[] {
|
|
41
|
-
// Preprocess: same as PHP Html renderer's preprocess()
|
|
42
|
-
let str = input.replace(/\r\n/g, "\n");
|
|
43
|
-
// Replace empty lines with a space (PHP: preg_replace('~^$~m', " ", $str))
|
|
44
|
-
str = str.replace(/^$/gm, " ");
|
|
45
|
-
str = str.replace(/\t/g, " ");
|
|
46
|
-
// rtrim
|
|
47
|
-
str = str.replace(/\s+$/, "");
|
|
48
|
-
|
|
49
|
-
const len = str.length;
|
|
50
|
-
if (len === 0) return [];
|
|
51
|
-
|
|
52
|
-
let state = -1;
|
|
53
|
-
let pos = 0;
|
|
54
|
-
let lastinner = def.defClass;
|
|
55
|
-
let lastdelim = def.defClass;
|
|
56
|
-
let endpattern: RegExp | null = null;
|
|
57
|
-
const stateStack: {
|
|
58
|
-
state: number;
|
|
59
|
-
lastdelim: string;
|
|
60
|
-
lastinner: string;
|
|
61
|
-
endpattern: RegExp | null;
|
|
62
|
-
}[] = [];
|
|
63
|
-
const tokenStack: Token[] = [];
|
|
64
|
-
const result: Token[] = [];
|
|
65
|
-
|
|
66
|
-
function getToken(): Token | null {
|
|
67
|
-
if (tokenStack.length > 0) {
|
|
68
|
-
return tokenStack.pop()!;
|
|
69
|
-
}
|
|
70
|
-
if (pos >= len) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Check for end of current state
|
|
75
|
-
let endpos = -1;
|
|
76
|
-
let endmatch = "";
|
|
77
|
-
if (state !== -1 && endpattern) {
|
|
78
|
-
endpattern.lastIndex = pos;
|
|
79
|
-
const em = endpattern.exec(str);
|
|
80
|
-
if (em) {
|
|
81
|
-
endpos = em.index;
|
|
82
|
-
endmatch = em[0];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Try to match patterns for current state
|
|
87
|
-
const reg = def.regs[state];
|
|
88
|
-
if (reg) {
|
|
89
|
-
reg.lastIndex = pos;
|
|
90
|
-
const m = reg.exec(str);
|
|
91
|
-
|
|
92
|
-
if (m) {
|
|
93
|
-
// Find which pattern (alternative) matched by checking capture groups
|
|
94
|
-
const countsArr = def.counts[state]!;
|
|
95
|
-
const statesArr = def.states[state]!;
|
|
96
|
-
const delimArr = def.delim[state]!;
|
|
97
|
-
const innerArr = def.inner[state]!;
|
|
98
|
-
let n = 1;
|
|
99
|
-
for (let i = 0; i < countsArr.length; i++) {
|
|
100
|
-
const count = countsArr[i]!;
|
|
101
|
-
if (n >= m.length) break;
|
|
102
|
-
|
|
103
|
-
// PHP: $m[$n][1] > -1 means the group captured something at a valid position
|
|
104
|
-
// JS: m[n] != null means the group participated in the match (including empty string captures)
|
|
105
|
-
if (m[n] != null && (endpos === -1 || m.index < endpos)) {
|
|
106
|
-
const matchStart = m.index;
|
|
107
|
-
const matchStr = m[n]!;
|
|
108
|
-
|
|
109
|
-
// Find actual position of this specific group within the match
|
|
110
|
-
// For alternation patterns, the matched group starts at m.index
|
|
111
|
-
// because only one alternative matches at a time
|
|
112
|
-
const groupStart = findGroupPosition(str, m, n, matchStart);
|
|
113
|
-
|
|
114
|
-
if (statesArr[i] !== -1) {
|
|
115
|
-
// State transition - push delimiter token
|
|
116
|
-
tokenStack.push({ class: delimArr[i]!, content: matchStr });
|
|
117
|
-
} else {
|
|
118
|
-
// Non-transitioning match
|
|
119
|
-
let inner = innerArr[i]!;
|
|
120
|
-
|
|
121
|
-
// Check parts first
|
|
122
|
-
const partDef = def.parts[state]?.[i];
|
|
123
|
-
if (partDef) {
|
|
124
|
-
const parts: Token[] = [];
|
|
125
|
-
let partpos = groupStart;
|
|
126
|
-
for (let j = 1; j <= count; j++) {
|
|
127
|
-
const subIdx = j + n;
|
|
128
|
-
if (subIdx >= m.length || m[subIdx] == null || m[subIdx] === "") continue;
|
|
129
|
-
const subStr = m[subIdx]!;
|
|
130
|
-
const subStart = str.indexOf(subStr, partpos);
|
|
131
|
-
if (subStart < 0) continue;
|
|
132
|
-
if (partDef[j]) {
|
|
133
|
-
if (subStart > partpos) {
|
|
134
|
-
parts.unshift({ class: inner, content: str.substring(partpos, subStart) });
|
|
135
|
-
}
|
|
136
|
-
parts.unshift({ class: partDef[j]!, content: subStr });
|
|
137
|
-
}
|
|
138
|
-
partpos = subStart + subStr.length;
|
|
139
|
-
}
|
|
140
|
-
if (partpos < groupStart + matchStr.length) {
|
|
141
|
-
parts.unshift({
|
|
142
|
-
class: inner,
|
|
143
|
-
content: str.substring(partpos, groupStart + matchStr.length),
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
tokenStack.push(...parts);
|
|
147
|
-
} else {
|
|
148
|
-
// Check keywords (fallback to state -1 if current state has no keyword def)
|
|
149
|
-
let kwDef = def.keywords[state]?.[i];
|
|
150
|
-
if (
|
|
151
|
-
!kwDef ||
|
|
152
|
-
kwDef === -1 ||
|
|
153
|
-
typeof kwDef !== "object" ||
|
|
154
|
-
Object.keys(kwDef).length === 0
|
|
155
|
-
) {
|
|
156
|
-
kwDef = def.keywords[-1]?.[i];
|
|
157
|
-
}
|
|
158
|
-
if (kwDef && kwDef !== -1 && typeof kwDef === "object") {
|
|
159
|
-
for (const [group, re] of Object.entries(kwDef)) {
|
|
160
|
-
if ((re as RegExp).test(matchStr)) {
|
|
161
|
-
inner = def.kwmap[group] ?? inner;
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
tokenStack.push({ class: inner, content: matchStr });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Emit text before match (pushed after so it pops first)
|
|
171
|
-
if (groupStart > pos) {
|
|
172
|
-
tokenStack.push({ class: lastinner, content: str.substring(pos, groupStart) });
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
pos = groupStart + matchStr.length;
|
|
176
|
-
|
|
177
|
-
// Handle state transition
|
|
178
|
-
if (statesArr[i] !== -1) {
|
|
179
|
-
stateStack.push({ state, lastdelim, lastinner, endpattern });
|
|
180
|
-
lastinner = innerArr[i]!;
|
|
181
|
-
lastdelim = delimArr[i]!;
|
|
182
|
-
const prevState = state;
|
|
183
|
-
state = statesArr[i]!;
|
|
184
|
-
|
|
185
|
-
// Get end pattern for new state
|
|
186
|
-
const endRe = def.end[state];
|
|
187
|
-
|
|
188
|
-
// Handle substitution in end pattern (requires new RegExp)
|
|
189
|
-
if (def.subst[prevState]?.[i] && endRe) {
|
|
190
|
-
let epSource = endRe.source;
|
|
191
|
-
for (let k = 0; k <= count; k++) {
|
|
192
|
-
const subIdx = n + k;
|
|
193
|
-
if (subIdx >= m.length || m[subIdx] == null) break;
|
|
194
|
-
const quoted = escapeRegex(m[subIdx]!);
|
|
195
|
-
epSource = epSource.replace(`%${k}%`, quoted);
|
|
196
|
-
epSource = epSource.replace(`%b${k}%`, matchingBrackets(quoted));
|
|
197
|
-
}
|
|
198
|
-
endpattern = new RegExp(epSource, endRe.flags);
|
|
199
|
-
} else {
|
|
200
|
-
// Reuse existing RegExp object (no substitution needed)
|
|
201
|
-
endpattern = endRe ?? null;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return tokenStack.pop()!;
|
|
206
|
-
}
|
|
207
|
-
n += count + 1;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Handle end of state
|
|
213
|
-
if (endpos > -1) {
|
|
214
|
-
// Always push delimiter token (even for zero-width matches) to match PHP behavior
|
|
215
|
-
tokenStack.push({ class: lastdelim, content: endmatch });
|
|
216
|
-
if (endpos > pos) {
|
|
217
|
-
tokenStack.push({ class: lastinner, content: str.substring(pos, endpos) });
|
|
218
|
-
}
|
|
219
|
-
const prev = stateStack.pop()!;
|
|
220
|
-
state = prev.state;
|
|
221
|
-
lastdelim = prev.lastdelim;
|
|
222
|
-
lastinner = prev.lastinner;
|
|
223
|
-
endpattern = prev.endpattern;
|
|
224
|
-
pos = endpos + endmatch.length;
|
|
225
|
-
if (tokenStack.length > 0) {
|
|
226
|
-
return tokenStack.pop()!;
|
|
227
|
-
}
|
|
228
|
-
// Zero-width end pattern with no preceding content: continue to next token
|
|
229
|
-
return getToken();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// No match - consume rest as default class
|
|
233
|
-
const p = pos;
|
|
234
|
-
pos = len;
|
|
235
|
-
return { class: lastinner, content: str.substring(p) };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
let token: Token | null;
|
|
239
|
-
while ((token = getToken()) !== null) {
|
|
240
|
-
result.push(token);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Find the actual position of capture group `n` within the source string.
|
|
248
|
-
*
|
|
249
|
-
* For alternation patterns (`a|b|c`), the matched alternative starts at
|
|
250
|
-
* the overall match position (`m.index`). This function locates the
|
|
251
|
-
* capture group's substring within the source, searching from `matchStart`.
|
|
252
|
-
*
|
|
253
|
-
* @param str - The full source string.
|
|
254
|
-
* @param m - The regex match result.
|
|
255
|
-
* @param n - The capture group index.
|
|
256
|
-
* @param matchStart - The starting position of the overall match.
|
|
257
|
-
* @returns The position of the capture group within the source string.
|
|
258
|
-
*/
|
|
259
|
-
function findGroupPosition(str: string, m: RegExpExecArray, n: number, matchStart: number): number {
|
|
260
|
-
// The overall match m[0] starts at m.index
|
|
261
|
-
// The capture group m[n] is a substring of m[0]
|
|
262
|
-
// Find where m[n] starts within the string, searching from matchStart
|
|
263
|
-
const groupStr = m[n]!;
|
|
264
|
-
const idx = str.indexOf(groupStr, matchStart);
|
|
265
|
-
return idx >= 0 ? idx : matchStart;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Render an array of tokens to HTML with `hl-*` class spans.
|
|
270
|
-
*
|
|
271
|
-
* This is a faithful port of Text_Highlighter's HTML renderer:
|
|
272
|
-
* - Adjacent tokens with the same class are merged into a single `<span>`.
|
|
273
|
-
* - All text is wrapped in spans (no unwrapped text nodes).
|
|
274
|
-
* - The output is wrapped in `<div class="hl-main"><pre>...</pre></div>`.
|
|
275
|
-
*
|
|
276
|
-
* @param tokens - Array of tokens produced by {@link tokenize}.
|
|
277
|
-
* @returns Complete HTML string for the highlighted code block.
|
|
278
|
-
*/
|
|
279
|
-
export function renderTokens(tokens: Token[]): string {
|
|
280
|
-
if (tokens.length === 0) return "";
|
|
281
|
-
|
|
282
|
-
let html = "";
|
|
283
|
-
let lastClass = "";
|
|
284
|
-
|
|
285
|
-
for (const token of tokens) {
|
|
286
|
-
if (token.content.length === 0) continue;
|
|
287
|
-
const escaped = escapeHtml(token.content);
|
|
288
|
-
if (token.class !== lastClass) {
|
|
289
|
-
if (lastClass) {
|
|
290
|
-
html += "</span>";
|
|
291
|
-
}
|
|
292
|
-
html += `<span class="hl-${token.class}">`;
|
|
293
|
-
lastClass = token.class;
|
|
294
|
-
}
|
|
295
|
-
html += escaped;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (lastClass) {
|
|
299
|
-
html += "</span>";
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return `<div class="hl-main"><pre>${html}</pre></div>`;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Escape HTML special characters for use inside highlighted code spans.
|
|
307
|
-
*
|
|
308
|
-
* @param str - Raw text to escape.
|
|
309
|
-
* @returns HTML-safe string.
|
|
310
|
-
*/
|
|
311
|
-
function escapeHtml(str: string): string {
|
|
312
|
-
return str
|
|
313
|
-
.replace(/&/g, "&")
|
|
314
|
-
.replace(/</g, "<")
|
|
315
|
-
.replace(/>/g, ">")
|
|
316
|
-
.replace(/"/g, """);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Escape regex special characters in a string for safe use in `new RegExp()`.
|
|
321
|
-
*
|
|
322
|
-
* @param str - Raw string to escape.
|
|
323
|
-
* @returns Regex-safe string.
|
|
324
|
-
*/
|
|
325
|
-
function escapeRegex(str: string): string {
|
|
326
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Swap bracket characters to their matching counterparts.
|
|
331
|
-
*
|
|
332
|
-
* Used for end-pattern substitution where the closing delimiter is the
|
|
333
|
-
* mirror of the opening delimiter (e.g., `<` becomes `>`).
|
|
334
|
-
*
|
|
335
|
-
* @param str - String containing bracket characters.
|
|
336
|
-
* @returns String with each bracket replaced by its counterpart.
|
|
337
|
-
*/
|
|
338
|
-
function matchingBrackets(str: string): string {
|
|
339
|
-
return str.replace(/[()<>[\]{}]/g, (c) => {
|
|
340
|
-
const map: Record<string, string> = {
|
|
341
|
-
"(": ")",
|
|
342
|
-
")": "(",
|
|
343
|
-
"<": ">",
|
|
344
|
-
">": "<",
|
|
345
|
-
"[": "]",
|
|
346
|
-
"]": "[",
|
|
347
|
-
"{": "}",
|
|
348
|
-
"}": "{",
|
|
349
|
-
};
|
|
350
|
-
return map[c] ?? c;
|
|
351
|
-
});
|
|
352
|
-
}
|
package/src/render.ts
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import type { Element, SyntaxTree } from "@wdprlib/ast";
|
|
2
|
-
import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
|
|
3
|
-
import { RenderContext } from "./context";
|
|
4
|
-
import { escapeStyleContent } from "./escape";
|
|
5
|
-
import type { RenderOptions } from "./types";
|
|
6
|
-
import { renderContainer } from "./elements/container";
|
|
7
|
-
import { renderText, renderRaw, renderEmail } from "./elements/text";
|
|
8
|
-
import { renderLink, renderAnchor, renderAnchorName } from "./elements/link";
|
|
9
|
-
import { renderImage } from "./elements/image";
|
|
10
|
-
import { renderList, renderDefinitionList } from "./elements/list";
|
|
11
|
-
import { renderTable } from "./elements/table";
|
|
12
|
-
import { renderCollapsible } from "./elements/collapsible";
|
|
13
|
-
import { renderCode } from "./elements/code";
|
|
14
|
-
import { renderTabView } from "./elements/tab-view";
|
|
15
|
-
import { renderFootnoteRef, renderFootnoteBlock } from "./elements/footnote";
|
|
16
|
-
import { renderMath, renderMathInline, renderEquationRef } from "./elements/math";
|
|
17
|
-
import { renderModule } from "./elements/module/index";
|
|
18
|
-
import { renderEmbed } from "./elements/embed";
|
|
19
|
-
import { renderEmbedBlock } from "./elements/embed-block";
|
|
20
|
-
import { renderUser } from "./elements/user";
|
|
21
|
-
import { renderBibliographyCite, renderBibliographyBlock } from "./elements/bibliography";
|
|
22
|
-
import { renderTableOfContents } from "./elements/toc";
|
|
23
|
-
import { renderLineBreaks } from "./elements/line-break";
|
|
24
|
-
import { renderClearFloat } from "./elements/clear-float";
|
|
25
|
-
import { renderIframe } from "./elements/iframe";
|
|
26
|
-
import { renderHtmlBlock } from "./elements/html";
|
|
27
|
-
import { renderInclude } from "./elements/include";
|
|
28
|
-
import { renderIfTags } from "./elements/iftags";
|
|
29
|
-
import { renderColor } from "./elements/color";
|
|
30
|
-
import { renderDate } from "./elements/date";
|
|
31
|
-
import { renderExpr, renderIf, renderIfExpr } from "./elements/expr";
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Render a {@link SyntaxTree} to an HTML string.
|
|
35
|
-
*
|
|
36
|
-
* This is the main entry point of `@wdprlib/render`. It walks the AST
|
|
37
|
-
* produced by `@wdprlib/parser`, serialises each element to HTML, and
|
|
38
|
-
* appends any collected `[[module CSS]]` styles at the end (when
|
|
39
|
-
* `WikitextSettings.allowStyleElements` is `true`).
|
|
40
|
-
*
|
|
41
|
-
* @param tree - Parsed AST (from `parse()` or `resolveModules()`)
|
|
42
|
-
* @param options - Rendering configuration
|
|
43
|
-
* @returns Complete HTML string
|
|
44
|
-
*
|
|
45
|
-
* @group Render
|
|
46
|
-
*/
|
|
47
|
-
export function renderToHtml(tree: SyntaxTree, options: RenderOptions = {}): string {
|
|
48
|
-
const ctx = new RenderContext(tree, options);
|
|
49
|
-
renderElements(ctx, tree.elements);
|
|
50
|
-
|
|
51
|
-
// Append styles (with tag breakout prevention).
|
|
52
|
-
// Sentinel entries (STYLE_SLOT_PREFIX) mark positions where unresolved
|
|
53
|
-
// iftags styles should be spliced in, preserving source order.
|
|
54
|
-
if (ctx.settings.allowStyleElements && tree.styles?.length) {
|
|
55
|
-
for (const style of tree.styles) {
|
|
56
|
-
if (style.startsWith(STYLE_SLOT_PREFIX)) {
|
|
57
|
-
const slotId = parseInt(style.slice(STYLE_SLOT_PREFIX.length), 10);
|
|
58
|
-
for (const css of ctx.getStyleSlotContents(slotId)) {
|
|
59
|
-
ctx.push(`<style>${escapeStyleContent(css)}</style>`);
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
ctx.push(`<style>${escapeStyleContent(style)}</style>`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return ctx.getOutput();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Render a list of sibling AST elements in document order.
|
|
72
|
-
*
|
|
73
|
-
* Used internally by container renderers that need to emit their
|
|
74
|
-
* children. Not exported from the package barrel — call
|
|
75
|
-
* {@link renderToHtml} instead for top-level rendering.
|
|
76
|
-
*/
|
|
77
|
-
export function renderElements(ctx: RenderContext, elements: Element[]): void {
|
|
78
|
-
for (const element of elements) {
|
|
79
|
-
renderElement(ctx, element);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Dispatch a single AST element to its type-specific renderer.
|
|
85
|
-
*
|
|
86
|
-
* The switch covers every `ElementName` value defined by
|
|
87
|
-
* `@wdprlib/ast`. Unknown element types are silently ignored.
|
|
88
|
-
*/
|
|
89
|
-
export function renderElement(ctx: RenderContext, element: Element): void {
|
|
90
|
-
switch (element.element) {
|
|
91
|
-
case "text":
|
|
92
|
-
ctx.pushEscaped(element.data);
|
|
93
|
-
break;
|
|
94
|
-
case "raw":
|
|
95
|
-
renderRaw(ctx, element.data);
|
|
96
|
-
break;
|
|
97
|
-
case "variable":
|
|
98
|
-
renderText(ctx, element.data);
|
|
99
|
-
break;
|
|
100
|
-
case "email":
|
|
101
|
-
renderEmail(ctx, element.data);
|
|
102
|
-
break;
|
|
103
|
-
case "container":
|
|
104
|
-
renderContainer(ctx, element.data);
|
|
105
|
-
break;
|
|
106
|
-
case "link":
|
|
107
|
-
renderLink(ctx, element.data);
|
|
108
|
-
break;
|
|
109
|
-
case "anchor":
|
|
110
|
-
renderAnchor(ctx, element.data);
|
|
111
|
-
break;
|
|
112
|
-
case "anchor-name":
|
|
113
|
-
renderAnchorName(ctx, element.data);
|
|
114
|
-
break;
|
|
115
|
-
case "image":
|
|
116
|
-
renderImage(ctx, element.data);
|
|
117
|
-
break;
|
|
118
|
-
case "list":
|
|
119
|
-
renderList(ctx, element.data);
|
|
120
|
-
break;
|
|
121
|
-
case "definition-list":
|
|
122
|
-
renderDefinitionList(ctx, element.data);
|
|
123
|
-
break;
|
|
124
|
-
case "table":
|
|
125
|
-
renderTable(ctx, element.data);
|
|
126
|
-
break;
|
|
127
|
-
case "collapsible":
|
|
128
|
-
renderCollapsible(ctx, element.data);
|
|
129
|
-
break;
|
|
130
|
-
case "code":
|
|
131
|
-
renderCode(ctx, element.data);
|
|
132
|
-
break;
|
|
133
|
-
case "tab-view":
|
|
134
|
-
renderTabView(ctx, element.data);
|
|
135
|
-
break;
|
|
136
|
-
case "footnote":
|
|
137
|
-
renderFootnoteRef(ctx, ctx.nextFootnoteIndex() + 1);
|
|
138
|
-
break;
|
|
139
|
-
case "footnote-ref":
|
|
140
|
-
renderFootnoteRef(ctx, element.data);
|
|
141
|
-
break;
|
|
142
|
-
case "footnote-block":
|
|
143
|
-
renderFootnoteBlock(ctx, element.data);
|
|
144
|
-
break;
|
|
145
|
-
case "bibliography-cite":
|
|
146
|
-
renderBibliographyCite(ctx, element.data);
|
|
147
|
-
break;
|
|
148
|
-
case "bibliography-block":
|
|
149
|
-
renderBibliographyBlock(ctx, element.data, renderElements);
|
|
150
|
-
break;
|
|
151
|
-
case "table-of-contents":
|
|
152
|
-
renderTableOfContents(ctx, element.data);
|
|
153
|
-
break;
|
|
154
|
-
case "math":
|
|
155
|
-
renderMath(ctx, element.data);
|
|
156
|
-
break;
|
|
157
|
-
case "math-inline":
|
|
158
|
-
renderMathInline(ctx, element.data);
|
|
159
|
-
break;
|
|
160
|
-
case "module":
|
|
161
|
-
renderModule(ctx, element.data);
|
|
162
|
-
break;
|
|
163
|
-
case "embed":
|
|
164
|
-
renderEmbed(ctx, element.data);
|
|
165
|
-
break;
|
|
166
|
-
case "embed-block":
|
|
167
|
-
renderEmbedBlock(ctx, element.data);
|
|
168
|
-
break;
|
|
169
|
-
case "user":
|
|
170
|
-
renderUser(ctx, element.data);
|
|
171
|
-
break;
|
|
172
|
-
case "date":
|
|
173
|
-
renderDate(ctx, element.data);
|
|
174
|
-
break;
|
|
175
|
-
case "color":
|
|
176
|
-
renderColor(ctx, element.data);
|
|
177
|
-
break;
|
|
178
|
-
case "html":
|
|
179
|
-
renderHtmlBlock(ctx, element.data);
|
|
180
|
-
break;
|
|
181
|
-
case "iframe":
|
|
182
|
-
renderIframe(ctx, element.data);
|
|
183
|
-
break;
|
|
184
|
-
case "include":
|
|
185
|
-
renderInclude(ctx, element.data);
|
|
186
|
-
break;
|
|
187
|
-
case "if-tags":
|
|
188
|
-
renderIfTags(ctx, element.data);
|
|
189
|
-
break;
|
|
190
|
-
case "style":
|
|
191
|
-
// Styles are collected into tree.styles during resolve and rendered
|
|
192
|
-
// at the end of renderToHtml. Style elements remaining in the AST
|
|
193
|
-
// (inside unresolved iftags) are either collected into a style slot
|
|
194
|
-
// (preserving source order) or rendered inline as a fallback.
|
|
195
|
-
if (ctx.renderInlineStyles && ctx.settings.allowStyleElements) {
|
|
196
|
-
if (ctx.hasActiveStyleSlot()) {
|
|
197
|
-
ctx.pushToStyleSlot(element.data);
|
|
198
|
-
} else {
|
|
199
|
-
ctx.push(`<style>${escapeStyleContent(element.data)}</style>`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
break;
|
|
203
|
-
case "line-break":
|
|
204
|
-
ctx.push("<br />");
|
|
205
|
-
break;
|
|
206
|
-
case "line-breaks":
|
|
207
|
-
renderLineBreaks(ctx, element.data);
|
|
208
|
-
break;
|
|
209
|
-
case "clear-float":
|
|
210
|
-
renderClearFloat(ctx, element.data);
|
|
211
|
-
break;
|
|
212
|
-
case "horizontal-rule":
|
|
213
|
-
ctx.push("<hr />");
|
|
214
|
-
break;
|
|
215
|
-
case "content-separator":
|
|
216
|
-
ctx.push(`<div class="content-separator" style="display: none:"></div>`);
|
|
217
|
-
break;
|
|
218
|
-
case "expr":
|
|
219
|
-
renderExpr(ctx, element.data);
|
|
220
|
-
break;
|
|
221
|
-
case "if":
|
|
222
|
-
renderIf(ctx, element.data);
|
|
223
|
-
break;
|
|
224
|
-
case "ifexpr":
|
|
225
|
-
renderIfExpr(ctx, element.data);
|
|
226
|
-
break;
|
|
227
|
-
case "equation-reference":
|
|
228
|
-
renderEquationRef(ctx, element.data);
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
}
|