docrev 0.9.4 → 0.9.6

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.
Files changed (141) hide show
  1. package/dist/lib/commands/comments.d.ts.map +1 -1
  2. package/dist/lib/commands/comments.js +19 -27
  3. package/dist/lib/commands/comments.js.map +1 -1
  4. package/dist/lib/commands/context.d.ts +1 -0
  5. package/dist/lib/commands/context.d.ts.map +1 -1
  6. package/dist/lib/commands/context.js +1 -2
  7. package/dist/lib/commands/context.js.map +1 -1
  8. package/dist/lib/commands/file-ops.d.ts +11 -0
  9. package/dist/lib/commands/file-ops.d.ts.map +1 -0
  10. package/dist/lib/commands/file-ops.js +301 -0
  11. package/dist/lib/commands/file-ops.js.map +1 -0
  12. package/dist/lib/commands/index.d.ts +9 -1
  13. package/dist/lib/commands/index.d.ts.map +1 -1
  14. package/dist/lib/commands/index.js +17 -1
  15. package/dist/lib/commands/index.js.map +1 -1
  16. package/dist/lib/commands/merge-resolve.d.ts +12 -0
  17. package/dist/lib/commands/merge-resolve.d.ts.map +1 -0
  18. package/dist/lib/commands/merge-resolve.js +318 -0
  19. package/dist/lib/commands/merge-resolve.js.map +1 -0
  20. package/dist/lib/commands/preview.d.ts +11 -0
  21. package/dist/lib/commands/preview.d.ts.map +1 -0
  22. package/dist/lib/commands/preview.js +138 -0
  23. package/dist/lib/commands/preview.js.map +1 -0
  24. package/dist/lib/commands/project-info.d.ts +11 -0
  25. package/dist/lib/commands/project-info.d.ts.map +1 -0
  26. package/dist/lib/commands/project-info.js +187 -0
  27. package/dist/lib/commands/project-info.js.map +1 -0
  28. package/dist/lib/commands/quality.d.ts +11 -0
  29. package/dist/lib/commands/quality.d.ts.map +1 -0
  30. package/dist/lib/commands/quality.js +384 -0
  31. package/dist/lib/commands/quality.js.map +1 -0
  32. package/dist/lib/commands/sections.d.ts +3 -2
  33. package/dist/lib/commands/sections.d.ts.map +1 -1
  34. package/dist/lib/commands/sections.js +4 -723
  35. package/dist/lib/commands/sections.js.map +1 -1
  36. package/dist/lib/commands/sync.d.ts +11 -0
  37. package/dist/lib/commands/sync.d.ts.map +1 -0
  38. package/dist/lib/commands/sync.js +441 -0
  39. package/dist/lib/commands/sync.js.map +1 -0
  40. package/dist/lib/commands/text-ops.d.ts +11 -0
  41. package/dist/lib/commands/text-ops.d.ts.map +1 -0
  42. package/dist/lib/commands/text-ops.js +357 -0
  43. package/dist/lib/commands/text-ops.js.map +1 -0
  44. package/dist/lib/commands/utilities.d.ts +2 -4
  45. package/dist/lib/commands/utilities.d.ts.map +1 -1
  46. package/dist/lib/commands/utilities.js +3 -1605
  47. package/dist/lib/commands/utilities.js.map +1 -1
  48. package/dist/lib/commands/word-tools.d.ts +11 -0
  49. package/dist/lib/commands/word-tools.d.ts.map +1 -0
  50. package/dist/lib/commands/word-tools.js +272 -0
  51. package/dist/lib/commands/word-tools.js.map +1 -0
  52. package/dist/lib/comment-realign.d.ts.map +1 -1
  53. package/dist/lib/comment-realign.js +0 -7
  54. package/dist/lib/comment-realign.js.map +1 -1
  55. package/dist/lib/dependencies.d.ts.map +1 -1
  56. package/dist/lib/dependencies.js +11 -23
  57. package/dist/lib/dependencies.js.map +1 -1
  58. package/dist/lib/diff-engine.d.ts +25 -0
  59. package/dist/lib/diff-engine.d.ts.map +1 -0
  60. package/dist/lib/diff-engine.js +354 -0
  61. package/dist/lib/diff-engine.js.map +1 -0
  62. package/dist/lib/git.d.ts.map +1 -1
  63. package/dist/lib/git.js +18 -28
  64. package/dist/lib/git.js.map +1 -1
  65. package/dist/lib/import.d.ts +37 -117
  66. package/dist/lib/import.d.ts.map +1 -1
  67. package/dist/lib/import.js +10 -1039
  68. package/dist/lib/import.js.map +1 -1
  69. package/dist/lib/merge.d.ts.map +1 -1
  70. package/dist/lib/merge.js +29 -117
  71. package/dist/lib/merge.js.map +1 -1
  72. package/dist/lib/pdf-comments.d.ts.map +1 -1
  73. package/dist/lib/pdf-comments.js +1 -13
  74. package/dist/lib/pdf-comments.js.map +1 -1
  75. package/dist/lib/pptx-themes.d.ts.map +1 -1
  76. package/dist/lib/pptx-themes.js +0 -403
  77. package/dist/lib/pptx-themes.js.map +1 -1
  78. package/dist/lib/protect-restore.d.ts.map +1 -1
  79. package/dist/lib/protect-restore.js +34 -36
  80. package/dist/lib/protect-restore.js.map +1 -1
  81. package/dist/lib/restore-references.d.ts +35 -0
  82. package/dist/lib/restore-references.d.ts.map +1 -0
  83. package/dist/lib/restore-references.js +188 -0
  84. package/dist/lib/restore-references.js.map +1 -0
  85. package/dist/lib/slides.d.ts.map +1 -1
  86. package/dist/lib/slides.js +0 -35
  87. package/dist/lib/slides.js.map +1 -1
  88. package/dist/lib/trackchanges.d.ts.map +1 -1
  89. package/dist/lib/trackchanges.js +1 -11
  90. package/dist/lib/trackchanges.js.map +1 -1
  91. package/dist/lib/tui.d.ts +36 -45
  92. package/dist/lib/tui.d.ts.map +1 -1
  93. package/dist/lib/tui.js +92 -108
  94. package/dist/lib/tui.js.map +1 -1
  95. package/dist/lib/undo.d.ts +3 -4
  96. package/dist/lib/undo.d.ts.map +1 -1
  97. package/dist/lib/undo.js +0 -7
  98. package/dist/lib/undo.js.map +1 -1
  99. package/dist/lib/utils.d.ts +12 -0
  100. package/dist/lib/utils.d.ts.map +1 -1
  101. package/dist/lib/utils.js +26 -0
  102. package/dist/lib/utils.js.map +1 -1
  103. package/dist/lib/word-extraction.d.ts +77 -0
  104. package/dist/lib/word-extraction.d.ts.map +1 -0
  105. package/dist/lib/word-extraction.js +515 -0
  106. package/dist/lib/word-extraction.js.map +1 -0
  107. package/dist/lib/wordcomments.d.ts.map +1 -1
  108. package/dist/lib/wordcomments.js +1 -8
  109. package/dist/lib/wordcomments.js.map +1 -1
  110. package/dist/package.json +137 -0
  111. package/lib/commands/comments.ts +20 -25
  112. package/lib/commands/context.ts +1 -2
  113. package/lib/commands/file-ops.ts +372 -0
  114. package/lib/commands/index.ts +24 -0
  115. package/lib/commands/merge-resolve.ts +378 -0
  116. package/lib/commands/preview.ts +178 -0
  117. package/lib/commands/project-info.ts +244 -0
  118. package/lib/commands/quality.ts +517 -0
  119. package/lib/commands/sections.ts +3 -857
  120. package/lib/commands/sync.ts +536 -0
  121. package/lib/commands/text-ops.ts +449 -0
  122. package/lib/commands/utilities.ts +62 -2066
  123. package/lib/commands/word-tools.ts +340 -0
  124. package/lib/comment-realign.ts +0 -8
  125. package/lib/dependencies.ts +12 -20
  126. package/lib/diff-engine.ts +465 -0
  127. package/lib/git.ts +24 -31
  128. package/lib/import.ts +78 -1348
  129. package/lib/merge.ts +42 -132
  130. package/lib/pdf-comments.ts +2 -14
  131. package/lib/pptx-themes.ts +0 -413
  132. package/lib/protect-restore.ts +48 -44
  133. package/lib/restore-references.ts +240 -0
  134. package/lib/slides.ts +0 -37
  135. package/lib/trackchanges.ts +1 -12
  136. package/lib/{tui.js → tui.ts} +139 -126
  137. package/lib/undo.ts +3 -12
  138. package/lib/utils.ts +28 -0
  139. package/lib/word-extraction.ts +666 -0
  140. package/lib/wordcomments.ts +1 -9
  141. package/package.json +1 -1
@@ -5,51 +5,109 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import * as readline from 'readline';
8
+ import type { Annotation } from './types.js';
9
+ import type { DocumentSession } from './undo.js';
10
+
11
+ // =============================================================================
12
+ // Interfaces
13
+ // =============================================================================
14
+
15
+ interface BoxOptions {
16
+ title?: string;
17
+ content?: string[];
18
+ width?: number;
19
+ borderColor?: string;
20
+ }
21
+
22
+ interface TuiReviewOptions {
23
+ author?: string;
24
+ addReply?: (text: string, comment: Annotation, author: string, reply: string) => string;
25
+ setStatus?: (text: string, comment: Annotation, resolved: boolean) => string;
26
+ }
27
+
28
+ interface TuiReviewResult {
29
+ text: string;
30
+ resolved: number;
31
+ replied: number;
32
+ skipped: number;
33
+ }
34
+
35
+ // =============================================================================
36
+ // Utility Functions
37
+ // =============================================================================
38
+
39
+ /**
40
+ * Strip ANSI codes for length calculation
41
+ */
42
+ export function stripAnsi(str: string): string {
43
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
44
+ }
45
+
46
+ /**
47
+ * Word wrap text to fit within width
48
+ */
49
+ function wordWrap(text: string, width: number): string[] {
50
+ const words = text.split(/\s+/);
51
+ const lines: string[] = [];
52
+ let currentLine = '';
53
+
54
+ for (const word of words) {
55
+ if (currentLine.length + word.length + 1 <= width) {
56
+ currentLine += (currentLine ? ' ' : '') + word;
57
+ } else {
58
+ if (currentLine) lines.push(currentLine);
59
+ currentLine = word;
60
+ }
61
+ }
62
+
63
+ if (currentLine) lines.push(currentLine);
64
+ return lines;
65
+ }
66
+
67
+ // =============================================================================
68
+ // Screen Functions
69
+ // =============================================================================
8
70
 
9
71
  /**
10
72
  * Clear the terminal screen
11
73
  */
12
- export function clearScreen() {
74
+ export function clearScreen(): void {
13
75
  process.stdout.write('\x1B[2J\x1B[H');
14
76
  }
15
77
 
16
78
  /**
17
79
  * Move cursor to position
18
- * @param {number} row
19
- * @param {number} col
20
80
  */
21
- export function moveCursor(row, col) {
81
+ export function moveCursor(row: number, col: number): void {
22
82
  process.stdout.write(`\x1B[${row};${col}H`);
23
83
  }
24
84
 
25
85
  /**
26
86
  * Get terminal dimensions
27
- * @returns {{rows: number, cols: number}}
28
87
  */
29
- export function getTerminalSize() {
88
+ export function getTerminalSize(): { rows: number; cols: number } {
30
89
  return {
31
90
  rows: process.stdout.rows || 24,
32
91
  cols: process.stdout.columns || 80,
33
92
  };
34
93
  }
35
94
 
95
+ // =============================================================================
96
+ // Drawing Functions
97
+ // =============================================================================
98
+
36
99
  /**
37
100
  * Draw a box with content
38
- * @param {object} options
39
- * @param {string} options.title
40
- * @param {string[]} options.content
41
- * @param {number} options.width
42
- * @param {string} options.borderColor
43
- * @returns {string[]}
44
101
  */
45
- export function drawBox({ title = '', content = [], width = 60, borderColor = 'dim' }) {
102
+ export function drawBox({ title = '', content = [], width = 60, borderColor = 'dim' }: BoxOptions = {}): string[] {
46
103
  const border = {
47
- tl: '', tr: '', bl: '', br: '',
48
- h: '', v: '',
104
+ tl: '\u256D', tr: '\u256E', bl: '\u2570', br: '\u256F',
105
+ h: '\u2500', v: '\u2502',
49
106
  };
50
107
 
51
- const colorFn = chalk[borderColor] || chalk.dim;
52
- const lines = [];
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ const colorFn = ((chalk as any)[borderColor] as ((str: string) => string)) || chalk.dim;
110
+ const lines: string[] = [];
53
111
 
54
112
  // Top border with title
55
113
  if (title) {
@@ -81,11 +139,8 @@ export function drawBox({ title = '', content = [], width = 60, borderColor = 'd
81
139
 
82
140
  /**
83
141
  * Draw a status bar at the bottom of the screen
84
- * @param {string} left - Left-aligned text
85
- * @param {string} right - Right-aligned text
86
- * @returns {string}
87
142
  */
88
- export function statusBar(left, right = '') {
143
+ export function statusBar(left: string, right: string = ''): string {
89
144
  const { cols } = getTerminalSize();
90
145
  const leftLen = stripAnsi(left).length;
91
146
  const rightLen = stripAnsi(right).length;
@@ -96,33 +151,24 @@ export function statusBar(left, right = '') {
96
151
 
97
152
  /**
98
153
  * Draw a progress indicator
99
- * @param {number} current
100
- * @param {number} total
101
- * @param {number} width
102
- * @returns {string}
103
154
  */
104
- export function progressIndicator(current, total, width = 20) {
155
+ export function progressIndicator(current: number, total: number, width: number = 20): string {
105
156
  const ratio = current / total;
106
157
  const filled = Math.round(ratio * width);
107
158
  const empty = width - filled;
108
159
 
109
- const bar = chalk.cyan(''.repeat(filled)) + chalk.dim(''.repeat(empty));
160
+ const bar = chalk.cyan('\u2588'.repeat(filled)) + chalk.dim('\u2591'.repeat(empty));
110
161
  return `${bar} ${current}/${total}`;
111
162
  }
112
163
 
113
164
  /**
114
165
  * Format a comment for TUI display
115
- * @param {object} comment
116
- * @param {number} index
117
- * @param {number} total
118
- * @param {number} width
119
- * @returns {string[]}
120
166
  */
121
- export function formatCommentCard(comment, index, total, width = 70) {
122
- const statusIcon = comment.resolved ? chalk.green('') : chalk.yellow('');
167
+ export function formatCommentCard(comment: Annotation, index: number, total: number, width: number = 70): string[] {
168
+ const statusIcon = comment.resolved ? chalk.green('\u2713') : chalk.yellow('\u25CB');
123
169
  const author = comment.author || 'Anonymous';
124
170
 
125
- const content = [];
171
+ const content: string[] = [];
126
172
 
127
173
  // Author and status line
128
174
  content.push(chalk.blue(author) + ' ' + statusIcon);
@@ -155,56 +201,76 @@ export function formatCommentCard(comment, index, total, width = 70) {
155
201
 
156
202
  /**
157
203
  * Draw the action menu
158
- * @param {string[]} options - Array of [key, description] tuples
159
- * @returns {string}
160
204
  */
161
- export function actionMenu(options) {
205
+ export function actionMenu(options: [string, string][]): string {
162
206
  return options
163
207
  .map(([key, desc]) => chalk.bold(`[${key}]`) + chalk.dim(desc))
164
208
  .join(' ');
165
209
  }
166
210
 
211
+ // =============================================================================
212
+ // Interactive TUI Review
213
+ // =============================================================================
214
+
167
215
  /**
168
- * Word wrap text to fit within width
169
- * @param {string} text
170
- * @param {number} width
171
- * @returns {string[]}
216
+ * Prompt for a single keypress
172
217
  */
173
- function wordWrap(text, width) {
174
- const words = text.split(/\s+/);
175
- const lines = [];
176
- let currentLine = '';
218
+ function promptKey(validKeys: string[]): Promise<string> {
219
+ return new Promise((resolve) => {
220
+ const rl = readline.createInterface({
221
+ input: process.stdin,
222
+ output: process.stdout,
223
+ });
177
224
 
178
- for (const word of words) {
179
- if (currentLine.length + word.length + 1 <= width) {
180
- currentLine += (currentLine ? ' ' : '') + word;
181
- } else {
182
- if (currentLine) lines.push(currentLine);
183
- currentLine = word;
225
+ if (process.stdin.isTTY) {
226
+ process.stdin.setRawMode(true);
184
227
  }
185
- }
228
+ process.stdin.resume();
186
229
 
187
- if (currentLine) lines.push(currentLine);
188
- return lines;
230
+ process.stdin.once('data', (key: Buffer) => {
231
+ const char = key.toString();
232
+
233
+ if (process.stdin.isTTY) {
234
+ process.stdin.setRawMode(false);
235
+ }
236
+ rl.close();
237
+
238
+ if (char === '\u0003') {
239
+ // Ctrl+C
240
+ clearScreen();
241
+ process.exit(0);
242
+ }
243
+
244
+ if (validKeys.includes(char.toLowerCase()) || validKeys.includes(char)) {
245
+ resolve(char);
246
+ } else {
247
+ resolve(promptKey(validKeys));
248
+ }
249
+ });
250
+ });
189
251
  }
190
252
 
191
253
  /**
192
- * Strip ANSI codes for length calculation
193
- * @param {string} str
194
- * @returns {string}
254
+ * Prompt for text input
195
255
  */
196
- function stripAnsi(str) {
197
- return str.replace(/\x1b\[[0-9;]*m/g, '');
256
+ function promptText(prompt: string): Promise<string> {
257
+ return new Promise((resolve) => {
258
+ const rl = readline.createInterface({
259
+ input: process.stdin,
260
+ output: process.stdout,
261
+ });
262
+ rl.question(prompt, (answer: string) => {
263
+ rl.close();
264
+ resolve(answer);
265
+ });
266
+ });
198
267
  }
199
268
 
200
269
  /**
201
270
  * Run TUI comment review session
202
- * @param {string} text
203
- * @param {object} options
204
- * @returns {Promise<{text: string, resolved: number, replied: number, skipped: number}>}
205
271
  */
206
- export async function tuiCommentReview(text, options = {}) {
207
- const { getComments, setCommentStatus } = await import('./annotations.js');
272
+ export async function tuiCommentReview(text: string, options: TuiReviewOptions = {}): Promise<TuiReviewResult> {
273
+ const { getComments } = await import('./annotations.js');
208
274
  const { createDocumentSession } = await import('./undo.js');
209
275
  const { author = 'Author', addReply, setStatus } = options;
210
276
 
@@ -216,16 +282,15 @@ export async function tuiCommentReview(text, options = {}) {
216
282
  }
217
283
 
218
284
  // Create session with undo support
219
- const session = createDocumentSession(text);
285
+ const session: DocumentSession = createDocumentSession(text);
220
286
 
221
287
  let currentIndex = 0;
222
288
  let resolved = 0;
223
289
  let replied = 0;
224
290
  let skipped = 0;
225
- let message = ''; // Status message to display
291
+ let message = '';
226
292
 
227
- // Helper to render current state
228
- const render = () => {
293
+ const render = (): void => {
229
294
  clearScreen();
230
295
 
231
296
  const { cols } = getTerminalSize();
@@ -257,7 +322,7 @@ export async function tuiCommentReview(text, options = {}) {
257
322
  }
258
323
 
259
324
  // Action menu with undo
260
- const menuItems = [
325
+ const menuItems: [string, string][] = [
261
326
  ['r', 'eply'],
262
327
  ['m', 'ark resolved'],
263
328
  ['s', 'kip'],
@@ -272,60 +337,9 @@ export async function tuiCommentReview(text, options = {}) {
272
337
  menuItems.push(['A', 'll resolve'], ['q', 'uit']);
273
338
 
274
339
  console.log(' ' + actionMenu(menuItems));
275
-
276
340
  console.log();
277
341
  };
278
342
 
279
- // Prompt for keypress
280
- const promptKey = (validKeys) => {
281
- return new Promise((resolve) => {
282
- const rl = readline.createInterface({
283
- input: process.stdin,
284
- output: process.stdout,
285
- });
286
-
287
- if (process.stdin.isTTY) {
288
- process.stdin.setRawMode(true);
289
- }
290
- process.stdin.resume();
291
-
292
- process.stdin.once('data', (key) => {
293
- const char = key.toString();
294
-
295
- if (process.stdin.isTTY) {
296
- process.stdin.setRawMode(false);
297
- }
298
- rl.close();
299
-
300
- if (char === '\u0003') {
301
- // Ctrl+C
302
- clearScreen();
303
- process.exit(0);
304
- }
305
-
306
- if (validKeys.includes(char.toLowerCase()) || validKeys.includes(char)) {
307
- resolve(char);
308
- } else {
309
- resolve(promptKey(validKeys));
310
- }
311
- });
312
- });
313
- };
314
-
315
- // Prompt for text input
316
- const promptText = (prompt) => {
317
- return new Promise((resolve) => {
318
- const rl = readline.createInterface({
319
- input: process.stdin,
320
- output: process.stdout,
321
- });
322
- rl.question(prompt, (answer) => {
323
- rl.close();
324
- resolve(answer);
325
- });
326
- });
327
- };
328
-
329
343
  // Main loop
330
344
  while (currentIndex < comments.length) {
331
345
  render();
@@ -344,12 +358,10 @@ export async function tuiCommentReview(text, options = {}) {
344
358
  return { text: session.getText(), resolved, replied, skipped: comments.length - currentIndex };
345
359
 
346
360
  case 'u':
347
- // Undo last change
348
361
  if (session.canUndo()) {
349
362
  const undone = session.undo();
350
363
  if (undone) {
351
364
  message = chalk.yellow(`Undone: ${undone.description}`);
352
- // Adjust counters (approximate)
353
365
  if (undone.description.includes('Resolved')) resolved = Math.max(0, resolved - 1);
354
366
  if (undone.description.includes('Reply')) replied = Math.max(0, replied - 1);
355
367
  if (currentIndex > 0) currentIndex--;
@@ -357,8 +369,7 @@ export async function tuiCommentReview(text, options = {}) {
357
369
  }
358
370
  break;
359
371
 
360
- case 'A':
361
- // Resolve all remaining
372
+ case 'A': {
362
373
  let newText = session.getText();
363
374
  for (let j = currentIndex; j < comments.length; j++) {
364
375
  if (setStatus) {
@@ -369,6 +380,7 @@ export async function tuiCommentReview(text, options = {}) {
369
380
  resolved += comments.length - currentIndex;
370
381
  currentIndex = comments.length;
371
382
  break;
383
+ }
372
384
 
373
385
  case 'm':
374
386
  if (setStatus) {
@@ -379,7 +391,7 @@ export async function tuiCommentReview(text, options = {}) {
379
391
  currentIndex++;
380
392
  break;
381
393
 
382
- case 'r':
394
+ case 'r': {
383
395
  console.log();
384
396
  const replyText = await promptText(chalk.cyan(' Reply: '));
385
397
  if (replyText.trim() && addReply) {
@@ -389,6 +401,7 @@ export async function tuiCommentReview(text, options = {}) {
389
401
  }
390
402
  currentIndex++;
391
403
  break;
404
+ }
392
405
 
393
406
  case 's':
394
407
  skipped++;
package/lib/undo.ts CHANGED
@@ -16,7 +16,7 @@ interface HistoryEntry {
16
16
  index: number;
17
17
  }
18
18
 
19
- interface StackInfo {
19
+ export interface StackInfo {
20
20
  position: number;
21
21
  size: number;
22
22
  undoSteps: number;
@@ -32,16 +32,15 @@ interface UndoStack {
32
32
  canRedo(): boolean;
33
33
  info(): StackInfo;
34
34
  history(limit?: number): HistoryEntry[];
35
- clear(): void;
36
35
  getStack(): StackEntry[];
37
36
  }
38
37
 
39
- interface DocumentChange {
38
+ export interface DocumentChange {
40
39
  text: string;
41
40
  description: string;
42
41
  }
43
42
 
44
- interface DocumentSession {
43
+ export interface DocumentSession {
45
44
  getText(): string;
46
45
  applyChange(newText: string, description: string): void;
47
46
  undo(): DocumentChange | null;
@@ -158,14 +157,6 @@ export function createUndoStack(maxSize: number = 50): UndoStack {
158
157
  }));
159
158
  },
160
159
 
161
- /**
162
- * Clear the stack
163
- */
164
- clear(): void {
165
- stack.length = 0;
166
- position = -1;
167
- },
168
-
169
160
  /**
170
161
  * Get the full stack (for debugging)
171
162
  */
package/lib/utils.ts CHANGED
@@ -39,3 +39,31 @@ export function normalizeWhitespace(text: string): string {
39
39
  .replace(/ +/g, ' ')
40
40
  .trim();
41
41
  }
42
+
43
+ /**
44
+ * Escape XML special characters
45
+ * @param str - Input string
46
+ * @returns XML-safe string
47
+ */
48
+ export function escapeXml(str: string): string {
49
+ return str
50
+ .replace(/&/g, '&amp;')
51
+ .replace(/</g, '&lt;')
52
+ .replace(/>/g, '&gt;')
53
+ .replace(/"/g, '&quot;')
54
+ .replace(/'/g, '&apos;');
55
+ }
56
+
57
+ /**
58
+ * Escape LaTeX special characters
59
+ * @param text - Text to escape
60
+ * @returns Escaped text
61
+ */
62
+ export function escapeLatex(text: string): string {
63
+ return text
64
+ .replace(/\\/g, '\\textbackslash{}')
65
+ .replace(/([#$%&_{}])/g, '\\$1')
66
+ .replace(/\^/g, '\\textasciicircum{}')
67
+ .replace(/~/g, '\\textasciitilde{}')
68
+ .replace(/\n/g, ' ');
69
+ }