@adeu/core 1.21.0 → 1.23.0
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 +1150 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +156 -12
- package/dist/index.d.ts +156 -12
- package/dist/index.js +1142 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/diff.ts +500 -1
- package/src/docx/bridge.ts +13 -0
- package/src/domain.ts +68 -15
- package/src/engine.ts +391 -28
- package/src/index.ts +6 -5
- package/src/ingest.ts +77 -5
- package/src/mapper.ts +101 -12
- package/src/markup.test.ts +2 -1
- package/src/markup.ts +214 -20
- package/src/repro_qa_report_2026_07_18.test.ts +1244 -0
- package/src/repro_qa_report_v5.test.ts +399 -0
- package/src/sanitize/core.ts +3 -0
- package/src/sanitize/transforms.ts +66 -2
- package/src/utils/docx.ts +257 -16
- package/src/utils/safe-regex.ts +90 -0
package/dist/index.d.cts
CHANGED
|
@@ -65,6 +65,8 @@ interface TextSpan {
|
|
|
65
65
|
del_id?: string | null;
|
|
66
66
|
hyperlink_id?: string | null;
|
|
67
67
|
comment_ids?: string[];
|
|
68
|
+
part_index: number;
|
|
69
|
+
is_image_marker?: boolean;
|
|
68
70
|
}
|
|
69
71
|
declare class DocumentMapper {
|
|
70
72
|
doc: DocumentObject;
|
|
@@ -74,10 +76,26 @@ declare class DocumentMapper {
|
|
|
74
76
|
full_text: string;
|
|
75
77
|
spans: TextSpan[];
|
|
76
78
|
appendix_start_index: number;
|
|
79
|
+
part_ranges: [number, number, string][];
|
|
80
|
+
private _current_part_index;
|
|
77
81
|
private _text_chunks;
|
|
78
82
|
private _plain_projection;
|
|
79
83
|
constructor(doc: DocumentObject, clean_view?: boolean, original_view?: boolean);
|
|
80
84
|
private _build_map;
|
|
85
|
+
/** [part_index, start, end, kind] for parts that projected any text. */
|
|
86
|
+
private _nonempty_part_ranges;
|
|
87
|
+
part_kind_of(part_index: number): string | null;
|
|
88
|
+
/** Kind of the part whose projected range contains `index`, or null. */
|
|
89
|
+
part_kind_at(index: number): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* When `index` falls strictly AFTER one part's text and at-or-before the
|
|
92
|
+
* start of the next part's text (i.e. inside the "\n\n" separator or
|
|
93
|
+
* exactly at the next part's first character), returns
|
|
94
|
+
* [previous_part_index, next_part_index]. Returns null everywhere else —
|
|
95
|
+
* including index == previous part's end, which is an ordinary
|
|
96
|
+
* end-of-part text position, not a boundary gap.
|
|
97
|
+
*/
|
|
98
|
+
part_boundary_at(index: number): [number, number] | null;
|
|
81
99
|
private _map_blocks;
|
|
82
100
|
private _map_table;
|
|
83
101
|
private _strip_markdown_formatting;
|
|
@@ -197,6 +215,8 @@ declare class BatchValidationError extends Error {
|
|
|
197
215
|
errors: string[];
|
|
198
216
|
constructor(errors: string[]);
|
|
199
217
|
}
|
|
218
|
+
declare function describe_illegal_control_chars(text: string): string | null;
|
|
219
|
+
declare function validate_edit_strings(edits: any[], index_offset?: number): string[];
|
|
200
220
|
declare class RedlineEngine {
|
|
201
221
|
doc: DocumentObject;
|
|
202
222
|
author: string;
|
|
@@ -243,8 +263,8 @@ declare class RedlineEngine {
|
|
|
243
263
|
* Snapshots the document text around a resolved edit BEFORE anything is
|
|
244
264
|
* applied. Previews rendered after the batch mutates the DOM cannot slice
|
|
245
265
|
* full_text at the stored offsets: applied edits shift offsets and inject
|
|
246
|
-
* tracked-change markup,
|
|
247
|
-
*
|
|
266
|
+
* tracked-change markup, garbling previews with unrelated edits and
|
|
267
|
+
* internal scaffolding (QA H1).
|
|
248
268
|
*/
|
|
249
269
|
private _capture_preview_context;
|
|
250
270
|
/**
|
|
@@ -263,8 +283,8 @@ declare class RedlineEngine {
|
|
|
263
283
|
private _build_full_match_preview;
|
|
264
284
|
/**
|
|
265
285
|
* The "new text" a batch report should show for an edit. InsertTableRow has
|
|
266
|
-
* no new_text field — surface its cell contents
|
|
267
|
-
* empty string
|
|
286
|
+
* no new_text field — surface its cell contents rather than a misleading
|
|
287
|
+
* empty string (QA M4).
|
|
268
288
|
*/
|
|
269
289
|
private static _report_new_text;
|
|
270
290
|
private _build_edit_context_previews;
|
|
@@ -303,6 +323,19 @@ declare class RedlineEngine {
|
|
|
303
323
|
private _getNextId;
|
|
304
324
|
private _create_track_change_tag;
|
|
305
325
|
private _set_text_content;
|
|
326
|
+
/**
|
|
327
|
+
* Walks `element` to its XML root element. Word (and LibreOffice, which
|
|
328
|
+
* refuses to LOAD such files) only supports comment ranges in the main
|
|
329
|
+
* document story ("w:document") — never in headers, footers, footnotes or
|
|
330
|
+
* endnotes (QA 2026-07-18 H4/C1).
|
|
331
|
+
*/
|
|
332
|
+
private _comment_anchor_in_main_story;
|
|
333
|
+
/**
|
|
334
|
+
* When the anchor lives outside the main document story, records a
|
|
335
|
+
* user-visible warning and returns true (caller must skip the comment).
|
|
336
|
+
* The tracked change itself still applies — only the bubble is dropped.
|
|
337
|
+
*/
|
|
338
|
+
private _skip_comment_outside_main_story;
|
|
306
339
|
/**
|
|
307
340
|
* Attaches a comment that wraps a contiguous range within a single paragraph.
|
|
308
341
|
* start_element and end_element must both be direct children of parent_element
|
|
@@ -393,6 +426,13 @@ declare class RedlineEngine {
|
|
|
393
426
|
* [start, start+length) in `mapper`, or null if that text is not inside a
|
|
394
427
|
* table row.
|
|
395
428
|
*/
|
|
429
|
+
/**
|
|
430
|
+
* True when a replacement anchored in a table would ADD line-separated
|
|
431
|
+
* pipe-delimited content — the text shape of a table row. Writing that
|
|
432
|
+
* into a cell renders a fake row inside one cell while the real grid
|
|
433
|
+
* stays unchanged (QA 2026-07-18 C2); such edits must use insert_row.
|
|
434
|
+
*/
|
|
435
|
+
private static _introduces_table_row_text;
|
|
396
436
|
private static _column_count_at;
|
|
397
437
|
validate_review_actions(actions: any[]): string[];
|
|
398
438
|
process_batch(changes: DocumentChange[], dry_run?: boolean): any;
|
|
@@ -413,12 +453,86 @@ declare class RedlineEngine {
|
|
|
413
453
|
private _apply_single_edit_indexed;
|
|
414
454
|
}
|
|
415
455
|
|
|
456
|
+
/** Text-space extent of one projected table row plus its cell texts. */
|
|
457
|
+
interface RowGeometry {
|
|
458
|
+
start: number;
|
|
459
|
+
end: number;
|
|
460
|
+
cells: string[];
|
|
461
|
+
}
|
|
462
|
+
/** Text-space extent of one projected top-level table. */
|
|
463
|
+
interface TableGeometry {
|
|
464
|
+
start: number;
|
|
465
|
+
end: number;
|
|
466
|
+
rows: RowGeometry[];
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Structural map of a projection: which offset ranges belong to which OPC
|
|
470
|
+
* part, and where top-level table rows live. Produced in the SAME pass as
|
|
471
|
+
* the text, so offsets always agree with it. Consumed by the diff pipeline
|
|
472
|
+
* to keep generated edits from crossing part boundaries (QA 2026-07-18 C1)
|
|
473
|
+
* and to emit structured row operations for table changes (QA C2).
|
|
474
|
+
*/
|
|
475
|
+
interface ExtractStructure {
|
|
476
|
+
part_ranges: [number, number, string][];
|
|
477
|
+
tables: TableGeometry[];
|
|
478
|
+
}
|
|
479
|
+
declare function extractTextFromBuffer(buffer: Buffer, cleanView?: boolean, includeAppendix?: boolean): Promise<string>;
|
|
480
|
+
declare function _extractTextFromDoc(doc: DocumentObject, cleanView?: boolean, includeAppendix?: boolean, return_paragraph_offsets?: boolean, return_structure?: boolean): string | {
|
|
481
|
+
text: string;
|
|
482
|
+
paragraph_offsets: Map<any, [number, number]>;
|
|
483
|
+
} | {
|
|
484
|
+
text: string;
|
|
485
|
+
structure: ExtractStructure;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
type DiffEdit = ModifyText | InsertTableRow | DeleteTableRow;
|
|
416
489
|
declare function trim_common_context(target: string, new_val: string): [number, number];
|
|
417
490
|
declare function generate_edits_from_text(original_text: string, modified_text: string): ModifyText[];
|
|
491
|
+
/**
|
|
492
|
+
* DOCX-to-DOCX diff over projections extracted with return_structure=true.
|
|
493
|
+
*
|
|
494
|
+
* Improvements over a flat generate_edits_from_text pass:
|
|
495
|
+
* - each OPC part (header/body/footer/notes) diffs against its
|
|
496
|
+
* counterpart, so no edit can span a part boundary and content that
|
|
497
|
+
* moved between parts is reported instead of hidden (QA 2026-07-18 C1);
|
|
498
|
+
* - table row insertions/deletions become structured insert_row /
|
|
499
|
+
* delete_row operations instead of pipe-text edits (QA C2).
|
|
500
|
+
*
|
|
501
|
+
* Every ModifyText keeps its _match_start_index pinned into text_orig, which
|
|
502
|
+
* the engine consumes positionally (the Node engine has no
|
|
503
|
+
* make_edits_self_contained JSON round trip like the Python CLI).
|
|
504
|
+
*
|
|
505
|
+
* Returns { edits, warnings }. Warnings describe fallbacks the caller should
|
|
506
|
+
* surface (differing part layouts, unanchorable rows, …).
|
|
507
|
+
*/
|
|
508
|
+
declare function generate_structured_edits(text_orig: string, struct_orig: ExtractStructure, text_mod: string, struct_mod: ExtractStructure): {
|
|
509
|
+
edits: DiffEdit[];
|
|
510
|
+
warnings: string[];
|
|
511
|
+
};
|
|
418
512
|
declare function create_unified_diff(original_text: string, modified_text: string, context_lines?: number): string;
|
|
419
513
|
declare function create_word_patch_diff(original_text: string, modified_text: string, original_path?: string, modified_path?: string): string;
|
|
420
514
|
|
|
421
|
-
|
|
515
|
+
/** One entry per input edit in apply_edits_to_markdown's edit_reports. */
|
|
516
|
+
interface MarkupEditReport {
|
|
517
|
+
index: number;
|
|
518
|
+
status: "applied" | "failed";
|
|
519
|
+
error: string | null;
|
|
520
|
+
occurrences: number;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Applies edits to Markdown text and returns CriticMarkup-annotated output.
|
|
524
|
+
*
|
|
525
|
+
* Edit resolution follows the SAME semantics as `apply` (QA 2026-07-18 M1):
|
|
526
|
+
* - `regex: true` targets match as regular expressions
|
|
527
|
+
* - `match_mode` is honored: "strict" (default) refuses ambiguous targets,
|
|
528
|
+
* "first" marks the first occurrence, "all" marks every occurrence
|
|
529
|
+
* - a missing target is a per-edit failure, never a silent skip
|
|
530
|
+
*
|
|
531
|
+
* When `edit_reports` is provided, one entry per input edit is appended:
|
|
532
|
+
* { index: 0-based input position, status: "applied"|"failed",
|
|
533
|
+
* error: string|null, occurrences: number }.
|
|
534
|
+
*/
|
|
535
|
+
declare function apply_edits_to_markdown(markdown_text: string, edits: ModifyText[], include_index?: boolean, highlight_only?: boolean, edit_reports?: MarkupEditReport[]): string;
|
|
422
536
|
|
|
423
537
|
/**
|
|
424
538
|
* Stateless paginator for projected DOCX Markdown.
|
|
@@ -454,12 +568,6 @@ interface OutlineNode {
|
|
|
454
568
|
}
|
|
455
569
|
declare function extract_outline(doc: DocumentObject, projected_body: string, body_pages: string[], body_page_offsets: number[], paragraph_offsets?: Record<string, [number, number]> | Map<any, [number, number]> | null): OutlineNode[];
|
|
456
570
|
|
|
457
|
-
declare function extractTextFromBuffer(buffer: Buffer, cleanView?: boolean): Promise<string>;
|
|
458
|
-
declare function _extractTextFromDoc(doc: DocumentObject, cleanView?: boolean, includeAppendix?: boolean, return_paragraph_offsets?: boolean): string | {
|
|
459
|
-
text: string;
|
|
460
|
-
paragraph_offsets: Map<any, [number, number]>;
|
|
461
|
-
};
|
|
462
|
-
|
|
463
571
|
interface FinalizeOptions {
|
|
464
572
|
filename: string;
|
|
465
573
|
sanitize_mode?: 'full' | 'keep-markup' | 'baseline';
|
|
@@ -475,6 +583,42 @@ interface FinalizeResult {
|
|
|
475
583
|
}
|
|
476
584
|
declare function finalize_document(doc: DocumentObject, options: FinalizeOptions): Promise<FinalizeResult>;
|
|
477
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Time-budgeted execution of USER/LLM-supplied regular expressions.
|
|
588
|
+
*
|
|
589
|
+
* `regex: true` on a ModifyText edit and `search_regex` on read_docx hand an
|
|
590
|
+
* LLM-controlled pattern to V8's backtracking engine. A pathological pattern
|
|
591
|
+
* like `(a|a)*$` against a run of repeated characters hangs the event loop
|
|
592
|
+
* indefinitely (QA 2026-07-17 F5 — ReDoS; mirrors the Python fix).
|
|
593
|
+
*
|
|
594
|
+
* JS regexes cannot be interrupted in-thread, but V8 CAN interrupt regex
|
|
595
|
+
* execution running inside a `vm` context with a `timeout` — that is the
|
|
596
|
+
* mechanism used here. `node:vm` is a builtin, preserving the zero-dependency
|
|
597
|
+
* bundle constraint.
|
|
598
|
+
*
|
|
599
|
+
* Only USER-SUPPLIED patterns belong here. The engine's own generated
|
|
600
|
+
* patterns (fuzzy matchers etc.) are built to be linear-time.
|
|
601
|
+
*/
|
|
602
|
+
declare const USER_PATTERN_TIMEOUT_MS = 2000;
|
|
603
|
+
declare class RegexTimeoutError extends Error {
|
|
604
|
+
pattern: string;
|
|
605
|
+
constructor(pattern: string);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* All non-overlapping matches of a user pattern, under the wall-clock budget.
|
|
609
|
+
* Invalid patterns throw SyntaxError exactly like `new RegExp(...)` would.
|
|
610
|
+
* The budget covers the ENTIRE scan, not just the first match.
|
|
611
|
+
*/
|
|
612
|
+
declare function userFindAllMatches(pattern: string, text: string, flags?: string): Array<{
|
|
613
|
+
start: number;
|
|
614
|
+
end: number;
|
|
615
|
+
}>;
|
|
616
|
+
/** First match of a user pattern under the wall-clock budget, or null. */
|
|
617
|
+
declare function userSearch(pattern: string, text: string, flags?: string): {
|
|
618
|
+
start: number;
|
|
619
|
+
end: number;
|
|
620
|
+
} | null;
|
|
621
|
+
|
|
478
622
|
declare function identifyEngine(): string;
|
|
479
623
|
|
|
480
|
-
export { BatchValidationError, DocumentMapper, DocumentObject, type FinalizeOptions, type FinalizeResult, type OutlineNode, type PageInfo, type PaginationResult, RedlineEngine, type TextSpan, _extractTextFromDoc, apply_edits_to_markdown, create_unified_diff, create_word_patch_diff, extractTextFromBuffer, extract_outline, finalize_document, generate_edits_from_text, identifyEngine, paginate, split_structural_appendix, trim_common_context };
|
|
624
|
+
export { BatchValidationError, type DiffEdit, DocumentMapper, DocumentObject, type ExtractStructure, type FinalizeOptions, type FinalizeResult, type MarkupEditReport, type OutlineNode, type PageInfo, type PaginationResult, RedlineEngine, RegexTimeoutError, type RowGeometry, type TableGeometry, type TextSpan, USER_PATTERN_TIMEOUT_MS, _extractTextFromDoc, apply_edits_to_markdown, create_unified_diff, create_word_patch_diff, describe_illegal_control_chars, extractTextFromBuffer, extract_outline, finalize_document, generate_edits_from_text, generate_structured_edits, identifyEngine, paginate, split_structural_appendix, trim_common_context, userFindAllMatches, userSearch, validate_edit_strings };
|
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ interface TextSpan {
|
|
|
65
65
|
del_id?: string | null;
|
|
66
66
|
hyperlink_id?: string | null;
|
|
67
67
|
comment_ids?: string[];
|
|
68
|
+
part_index: number;
|
|
69
|
+
is_image_marker?: boolean;
|
|
68
70
|
}
|
|
69
71
|
declare class DocumentMapper {
|
|
70
72
|
doc: DocumentObject;
|
|
@@ -74,10 +76,26 @@ declare class DocumentMapper {
|
|
|
74
76
|
full_text: string;
|
|
75
77
|
spans: TextSpan[];
|
|
76
78
|
appendix_start_index: number;
|
|
79
|
+
part_ranges: [number, number, string][];
|
|
80
|
+
private _current_part_index;
|
|
77
81
|
private _text_chunks;
|
|
78
82
|
private _plain_projection;
|
|
79
83
|
constructor(doc: DocumentObject, clean_view?: boolean, original_view?: boolean);
|
|
80
84
|
private _build_map;
|
|
85
|
+
/** [part_index, start, end, kind] for parts that projected any text. */
|
|
86
|
+
private _nonempty_part_ranges;
|
|
87
|
+
part_kind_of(part_index: number): string | null;
|
|
88
|
+
/** Kind of the part whose projected range contains `index`, or null. */
|
|
89
|
+
part_kind_at(index: number): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* When `index` falls strictly AFTER one part's text and at-or-before the
|
|
92
|
+
* start of the next part's text (i.e. inside the "\n\n" separator or
|
|
93
|
+
* exactly at the next part's first character), returns
|
|
94
|
+
* [previous_part_index, next_part_index]. Returns null everywhere else —
|
|
95
|
+
* including index == previous part's end, which is an ordinary
|
|
96
|
+
* end-of-part text position, not a boundary gap.
|
|
97
|
+
*/
|
|
98
|
+
part_boundary_at(index: number): [number, number] | null;
|
|
81
99
|
private _map_blocks;
|
|
82
100
|
private _map_table;
|
|
83
101
|
private _strip_markdown_formatting;
|
|
@@ -197,6 +215,8 @@ declare class BatchValidationError extends Error {
|
|
|
197
215
|
errors: string[];
|
|
198
216
|
constructor(errors: string[]);
|
|
199
217
|
}
|
|
218
|
+
declare function describe_illegal_control_chars(text: string): string | null;
|
|
219
|
+
declare function validate_edit_strings(edits: any[], index_offset?: number): string[];
|
|
200
220
|
declare class RedlineEngine {
|
|
201
221
|
doc: DocumentObject;
|
|
202
222
|
author: string;
|
|
@@ -243,8 +263,8 @@ declare class RedlineEngine {
|
|
|
243
263
|
* Snapshots the document text around a resolved edit BEFORE anything is
|
|
244
264
|
* applied. Previews rendered after the batch mutates the DOM cannot slice
|
|
245
265
|
* full_text at the stored offsets: applied edits shift offsets and inject
|
|
246
|
-
* tracked-change markup,
|
|
247
|
-
*
|
|
266
|
+
* tracked-change markup, garbling previews with unrelated edits and
|
|
267
|
+
* internal scaffolding (QA H1).
|
|
248
268
|
*/
|
|
249
269
|
private _capture_preview_context;
|
|
250
270
|
/**
|
|
@@ -263,8 +283,8 @@ declare class RedlineEngine {
|
|
|
263
283
|
private _build_full_match_preview;
|
|
264
284
|
/**
|
|
265
285
|
* The "new text" a batch report should show for an edit. InsertTableRow has
|
|
266
|
-
* no new_text field — surface its cell contents
|
|
267
|
-
* empty string
|
|
286
|
+
* no new_text field — surface its cell contents rather than a misleading
|
|
287
|
+
* empty string (QA M4).
|
|
268
288
|
*/
|
|
269
289
|
private static _report_new_text;
|
|
270
290
|
private _build_edit_context_previews;
|
|
@@ -303,6 +323,19 @@ declare class RedlineEngine {
|
|
|
303
323
|
private _getNextId;
|
|
304
324
|
private _create_track_change_tag;
|
|
305
325
|
private _set_text_content;
|
|
326
|
+
/**
|
|
327
|
+
* Walks `element` to its XML root element. Word (and LibreOffice, which
|
|
328
|
+
* refuses to LOAD such files) only supports comment ranges in the main
|
|
329
|
+
* document story ("w:document") — never in headers, footers, footnotes or
|
|
330
|
+
* endnotes (QA 2026-07-18 H4/C1).
|
|
331
|
+
*/
|
|
332
|
+
private _comment_anchor_in_main_story;
|
|
333
|
+
/**
|
|
334
|
+
* When the anchor lives outside the main document story, records a
|
|
335
|
+
* user-visible warning and returns true (caller must skip the comment).
|
|
336
|
+
* The tracked change itself still applies — only the bubble is dropped.
|
|
337
|
+
*/
|
|
338
|
+
private _skip_comment_outside_main_story;
|
|
306
339
|
/**
|
|
307
340
|
* Attaches a comment that wraps a contiguous range within a single paragraph.
|
|
308
341
|
* start_element and end_element must both be direct children of parent_element
|
|
@@ -393,6 +426,13 @@ declare class RedlineEngine {
|
|
|
393
426
|
* [start, start+length) in `mapper`, or null if that text is not inside a
|
|
394
427
|
* table row.
|
|
395
428
|
*/
|
|
429
|
+
/**
|
|
430
|
+
* True when a replacement anchored in a table would ADD line-separated
|
|
431
|
+
* pipe-delimited content — the text shape of a table row. Writing that
|
|
432
|
+
* into a cell renders a fake row inside one cell while the real grid
|
|
433
|
+
* stays unchanged (QA 2026-07-18 C2); such edits must use insert_row.
|
|
434
|
+
*/
|
|
435
|
+
private static _introduces_table_row_text;
|
|
396
436
|
private static _column_count_at;
|
|
397
437
|
validate_review_actions(actions: any[]): string[];
|
|
398
438
|
process_batch(changes: DocumentChange[], dry_run?: boolean): any;
|
|
@@ -413,12 +453,86 @@ declare class RedlineEngine {
|
|
|
413
453
|
private _apply_single_edit_indexed;
|
|
414
454
|
}
|
|
415
455
|
|
|
456
|
+
/** Text-space extent of one projected table row plus its cell texts. */
|
|
457
|
+
interface RowGeometry {
|
|
458
|
+
start: number;
|
|
459
|
+
end: number;
|
|
460
|
+
cells: string[];
|
|
461
|
+
}
|
|
462
|
+
/** Text-space extent of one projected top-level table. */
|
|
463
|
+
interface TableGeometry {
|
|
464
|
+
start: number;
|
|
465
|
+
end: number;
|
|
466
|
+
rows: RowGeometry[];
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Structural map of a projection: which offset ranges belong to which OPC
|
|
470
|
+
* part, and where top-level table rows live. Produced in the SAME pass as
|
|
471
|
+
* the text, so offsets always agree with it. Consumed by the diff pipeline
|
|
472
|
+
* to keep generated edits from crossing part boundaries (QA 2026-07-18 C1)
|
|
473
|
+
* and to emit structured row operations for table changes (QA C2).
|
|
474
|
+
*/
|
|
475
|
+
interface ExtractStructure {
|
|
476
|
+
part_ranges: [number, number, string][];
|
|
477
|
+
tables: TableGeometry[];
|
|
478
|
+
}
|
|
479
|
+
declare function extractTextFromBuffer(buffer: Buffer, cleanView?: boolean, includeAppendix?: boolean): Promise<string>;
|
|
480
|
+
declare function _extractTextFromDoc(doc: DocumentObject, cleanView?: boolean, includeAppendix?: boolean, return_paragraph_offsets?: boolean, return_structure?: boolean): string | {
|
|
481
|
+
text: string;
|
|
482
|
+
paragraph_offsets: Map<any, [number, number]>;
|
|
483
|
+
} | {
|
|
484
|
+
text: string;
|
|
485
|
+
structure: ExtractStructure;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
type DiffEdit = ModifyText | InsertTableRow | DeleteTableRow;
|
|
416
489
|
declare function trim_common_context(target: string, new_val: string): [number, number];
|
|
417
490
|
declare function generate_edits_from_text(original_text: string, modified_text: string): ModifyText[];
|
|
491
|
+
/**
|
|
492
|
+
* DOCX-to-DOCX diff over projections extracted with return_structure=true.
|
|
493
|
+
*
|
|
494
|
+
* Improvements over a flat generate_edits_from_text pass:
|
|
495
|
+
* - each OPC part (header/body/footer/notes) diffs against its
|
|
496
|
+
* counterpart, so no edit can span a part boundary and content that
|
|
497
|
+
* moved between parts is reported instead of hidden (QA 2026-07-18 C1);
|
|
498
|
+
* - table row insertions/deletions become structured insert_row /
|
|
499
|
+
* delete_row operations instead of pipe-text edits (QA C2).
|
|
500
|
+
*
|
|
501
|
+
* Every ModifyText keeps its _match_start_index pinned into text_orig, which
|
|
502
|
+
* the engine consumes positionally (the Node engine has no
|
|
503
|
+
* make_edits_self_contained JSON round trip like the Python CLI).
|
|
504
|
+
*
|
|
505
|
+
* Returns { edits, warnings }. Warnings describe fallbacks the caller should
|
|
506
|
+
* surface (differing part layouts, unanchorable rows, …).
|
|
507
|
+
*/
|
|
508
|
+
declare function generate_structured_edits(text_orig: string, struct_orig: ExtractStructure, text_mod: string, struct_mod: ExtractStructure): {
|
|
509
|
+
edits: DiffEdit[];
|
|
510
|
+
warnings: string[];
|
|
511
|
+
};
|
|
418
512
|
declare function create_unified_diff(original_text: string, modified_text: string, context_lines?: number): string;
|
|
419
513
|
declare function create_word_patch_diff(original_text: string, modified_text: string, original_path?: string, modified_path?: string): string;
|
|
420
514
|
|
|
421
|
-
|
|
515
|
+
/** One entry per input edit in apply_edits_to_markdown's edit_reports. */
|
|
516
|
+
interface MarkupEditReport {
|
|
517
|
+
index: number;
|
|
518
|
+
status: "applied" | "failed";
|
|
519
|
+
error: string | null;
|
|
520
|
+
occurrences: number;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Applies edits to Markdown text and returns CriticMarkup-annotated output.
|
|
524
|
+
*
|
|
525
|
+
* Edit resolution follows the SAME semantics as `apply` (QA 2026-07-18 M1):
|
|
526
|
+
* - `regex: true` targets match as regular expressions
|
|
527
|
+
* - `match_mode` is honored: "strict" (default) refuses ambiguous targets,
|
|
528
|
+
* "first" marks the first occurrence, "all" marks every occurrence
|
|
529
|
+
* - a missing target is a per-edit failure, never a silent skip
|
|
530
|
+
*
|
|
531
|
+
* When `edit_reports` is provided, one entry per input edit is appended:
|
|
532
|
+
* { index: 0-based input position, status: "applied"|"failed",
|
|
533
|
+
* error: string|null, occurrences: number }.
|
|
534
|
+
*/
|
|
535
|
+
declare function apply_edits_to_markdown(markdown_text: string, edits: ModifyText[], include_index?: boolean, highlight_only?: boolean, edit_reports?: MarkupEditReport[]): string;
|
|
422
536
|
|
|
423
537
|
/**
|
|
424
538
|
* Stateless paginator for projected DOCX Markdown.
|
|
@@ -454,12 +568,6 @@ interface OutlineNode {
|
|
|
454
568
|
}
|
|
455
569
|
declare function extract_outline(doc: DocumentObject, projected_body: string, body_pages: string[], body_page_offsets: number[], paragraph_offsets?: Record<string, [number, number]> | Map<any, [number, number]> | null): OutlineNode[];
|
|
456
570
|
|
|
457
|
-
declare function extractTextFromBuffer(buffer: Buffer, cleanView?: boolean): Promise<string>;
|
|
458
|
-
declare function _extractTextFromDoc(doc: DocumentObject, cleanView?: boolean, includeAppendix?: boolean, return_paragraph_offsets?: boolean): string | {
|
|
459
|
-
text: string;
|
|
460
|
-
paragraph_offsets: Map<any, [number, number]>;
|
|
461
|
-
};
|
|
462
|
-
|
|
463
571
|
interface FinalizeOptions {
|
|
464
572
|
filename: string;
|
|
465
573
|
sanitize_mode?: 'full' | 'keep-markup' | 'baseline';
|
|
@@ -475,6 +583,42 @@ interface FinalizeResult {
|
|
|
475
583
|
}
|
|
476
584
|
declare function finalize_document(doc: DocumentObject, options: FinalizeOptions): Promise<FinalizeResult>;
|
|
477
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Time-budgeted execution of USER/LLM-supplied regular expressions.
|
|
588
|
+
*
|
|
589
|
+
* `regex: true` on a ModifyText edit and `search_regex` on read_docx hand an
|
|
590
|
+
* LLM-controlled pattern to V8's backtracking engine. A pathological pattern
|
|
591
|
+
* like `(a|a)*$` against a run of repeated characters hangs the event loop
|
|
592
|
+
* indefinitely (QA 2026-07-17 F5 — ReDoS; mirrors the Python fix).
|
|
593
|
+
*
|
|
594
|
+
* JS regexes cannot be interrupted in-thread, but V8 CAN interrupt regex
|
|
595
|
+
* execution running inside a `vm` context with a `timeout` — that is the
|
|
596
|
+
* mechanism used here. `node:vm` is a builtin, preserving the zero-dependency
|
|
597
|
+
* bundle constraint.
|
|
598
|
+
*
|
|
599
|
+
* Only USER-SUPPLIED patterns belong here. The engine's own generated
|
|
600
|
+
* patterns (fuzzy matchers etc.) are built to be linear-time.
|
|
601
|
+
*/
|
|
602
|
+
declare const USER_PATTERN_TIMEOUT_MS = 2000;
|
|
603
|
+
declare class RegexTimeoutError extends Error {
|
|
604
|
+
pattern: string;
|
|
605
|
+
constructor(pattern: string);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* All non-overlapping matches of a user pattern, under the wall-clock budget.
|
|
609
|
+
* Invalid patterns throw SyntaxError exactly like `new RegExp(...)` would.
|
|
610
|
+
* The budget covers the ENTIRE scan, not just the first match.
|
|
611
|
+
*/
|
|
612
|
+
declare function userFindAllMatches(pattern: string, text: string, flags?: string): Array<{
|
|
613
|
+
start: number;
|
|
614
|
+
end: number;
|
|
615
|
+
}>;
|
|
616
|
+
/** First match of a user pattern under the wall-clock budget, or null. */
|
|
617
|
+
declare function userSearch(pattern: string, text: string, flags?: string): {
|
|
618
|
+
start: number;
|
|
619
|
+
end: number;
|
|
620
|
+
} | null;
|
|
621
|
+
|
|
478
622
|
declare function identifyEngine(): string;
|
|
479
623
|
|
|
480
|
-
export { BatchValidationError, DocumentMapper, DocumentObject, type FinalizeOptions, type FinalizeResult, type OutlineNode, type PageInfo, type PaginationResult, RedlineEngine, type TextSpan, _extractTextFromDoc, apply_edits_to_markdown, create_unified_diff, create_word_patch_diff, extractTextFromBuffer, extract_outline, finalize_document, generate_edits_from_text, identifyEngine, paginate, split_structural_appendix, trim_common_context };
|
|
624
|
+
export { BatchValidationError, type DiffEdit, DocumentMapper, DocumentObject, type ExtractStructure, type FinalizeOptions, type FinalizeResult, type MarkupEditReport, type OutlineNode, type PageInfo, type PaginationResult, RedlineEngine, RegexTimeoutError, type RowGeometry, type TableGeometry, type TextSpan, USER_PATTERN_TIMEOUT_MS, _extractTextFromDoc, apply_edits_to_markdown, create_unified_diff, create_word_patch_diff, describe_illegal_control_chars, extractTextFromBuffer, extract_outline, finalize_document, generate_edits_from_text, generate_structured_edits, identifyEngine, paginate, split_structural_appendix, trim_common_context, userFindAllMatches, userSearch, validate_edit_strings };
|