@kimdayoun/hwpx-mcp 0.3.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.
@@ -0,0 +1,1607 @@
1
+ import JSZip from 'jszip';
2
+ import { HwpxContent, HwpxParagraph, TextRun, CharacterStyle, ParagraphStyle, HwpxTable, TableCell, PageSettings, Footnote, Endnote, Memo, ColumnDef, CharShape, ParaShape } from './types';
3
+ type DocumentFormat = 'hwpx' | 'hwp';
4
+ export interface ImagePositionOptions {
5
+ /** Position type: 'inline' (flows with text like a character) or 'floating' (positioned relative to anchor) */
6
+ positionType?: 'inline' | 'floating';
7
+ /** Vertical reference point: 'para' (paragraph), 'paper' (page) */
8
+ vertRelTo?: 'para' | 'paper';
9
+ /** Horizontal reference point: 'column', 'para' (paragraph), 'paper' (page) */
10
+ horzRelTo?: 'column' | 'para' | 'paper';
11
+ /** Vertical alignment: 'top', 'center', 'bottom' */
12
+ vertAlign?: 'top' | 'center' | 'bottom';
13
+ /** Horizontal alignment: 'left', 'center', 'right' */
14
+ horzAlign?: 'left' | 'center' | 'right';
15
+ /** Vertical offset from anchor in points */
16
+ vertOffset?: number;
17
+ /** Horizontal offset from anchor in points */
18
+ horzOffset?: number;
19
+ /** Text wrap mode */
20
+ textWrap?: 'top_and_bottom' | 'square' | 'tight' | 'behind_text' | 'in_front_of_text' | 'none';
21
+ }
22
+ export interface DocumentChunk {
23
+ id: string;
24
+ text: string;
25
+ startOffset: number;
26
+ endOffset: number;
27
+ sectionIndex: number;
28
+ elementType: 'paragraph' | 'table' | 'mixed';
29
+ elementIndex?: number;
30
+ tableIndex?: number;
31
+ cellPosition?: {
32
+ row: number;
33
+ col: number;
34
+ };
35
+ metadata: {
36
+ charCount: number;
37
+ wordCount: number;
38
+ hasTable: boolean;
39
+ headingLevel?: number;
40
+ };
41
+ }
42
+ export interface PositionIndexEntry {
43
+ id: string;
44
+ type: 'heading' | 'paragraph' | 'table' | 'image';
45
+ text: string;
46
+ sectionIndex: number;
47
+ elementIndex: number;
48
+ offset: number;
49
+ level?: number;
50
+ tableInfo?: {
51
+ tableIndex: number;
52
+ rows: number;
53
+ cols: number;
54
+ };
55
+ }
56
+ export declare class HwpxDocument {
57
+ private _id;
58
+ private _path;
59
+ private _zip;
60
+ private _content;
61
+ private _isDirty;
62
+ private _format;
63
+ private _undoStack;
64
+ private _redoStack;
65
+ private _pendingTextReplacements;
66
+ private _pendingDirectTextUpdates;
67
+ private _pendingTableCellUpdates;
68
+ private _pendingNestedTableInserts;
69
+ private _pendingImageInserts;
70
+ private _pendingCellImageInserts;
71
+ private _pendingTableInserts;
72
+ private _tableInsertCounter;
73
+ private _pendingImageDeletes;
74
+ private _pendingTableDeletes;
75
+ private _pendingParagraphDeletes;
76
+ private _pendingCellMerges;
77
+ private _pendingCellSplits;
78
+ private _pendingHangingIndents;
79
+ private _pendingTableCellHangingIndents;
80
+ private _pendingParagraphInserts;
81
+ private _pendingParagraphStyles;
82
+ private _pendingCharacterStyles;
83
+ private _pendingTableRowInserts;
84
+ private _pendingTableRowDeletes;
85
+ private _pendingTableColumnInserts;
86
+ private _pendingTableColumnDeletes;
87
+ private _pendingParagraphCopies;
88
+ private _pendingParagraphMoves;
89
+ private _pendingHeaderUpdates;
90
+ private _pendingFooterUpdates;
91
+ private _charPrCache;
92
+ private _originalCharPrCount?;
93
+ private constructor();
94
+ private static readonly NESTED_CHECK_LOOKBACK;
95
+ private static readonly SEARCH_SKIP_OFFSET;
96
+ /**
97
+ * Find the closing tag position using balanced bracket matching.
98
+ * Handles nested elements of the same type correctly.
99
+ * @param xml The XML string to search in
100
+ * @param startPos Position right after the opening tag
101
+ * @param openTag Opening tag pattern (e.g., '<hp:tbl')
102
+ * @param closeTag Closing tag (e.g., '</hp:tbl>')
103
+ * @returns Position after the closing tag, or -1 if not found
104
+ */
105
+ private static findClosingTagPosition;
106
+ static createFromBuffer(id: string, path: string, data: Buffer): Promise<HwpxDocument>;
107
+ static createNew(id: string, title?: string, creator?: string): HwpxDocument;
108
+ get id(): string;
109
+ get path(): string;
110
+ get format(): DocumentFormat;
111
+ get isDirty(): boolean;
112
+ get zip(): JSZip | null;
113
+ get content(): HwpxContent;
114
+ private saveState;
115
+ private serializeContent;
116
+ private deserializeContent;
117
+ canUndo(): boolean;
118
+ canRedo(): boolean;
119
+ undo(): boolean;
120
+ redo(): boolean;
121
+ /**
122
+ * Clear all pending operation arrays.
123
+ * Called by undo/redo to prevent memory/XML desync.
124
+ */
125
+ private clearAllPendingArrays;
126
+ /**
127
+ * Mark document as modified and invalidate agentic reading cache.
128
+ * Call this after any modification that changes document structure or content.
129
+ */
130
+ private markModified;
131
+ getSerializableContent(): object;
132
+ getAllText(): string;
133
+ getStructure(): object;
134
+ private findParagraphByPath;
135
+ getParagraphs(sectionIndex?: number): Array<{
136
+ section: number;
137
+ index: number;
138
+ text: string;
139
+ style?: ParagraphStyle;
140
+ }>;
141
+ getParagraph(sectionIndex: number, paragraphIndex: number): {
142
+ text: string;
143
+ runs: TextRun[];
144
+ style?: ParagraphStyle;
145
+ } | null;
146
+ updateParagraphText(sectionIndex: number, elementIndex: number, runIndex: number, text: string): void;
147
+ updateParagraphRuns(sectionIndex: number, elementIndex: number, runs: TextRun[]): void;
148
+ /**
149
+ * Update paragraph text while preserving the style structure of existing runs.
150
+ *
151
+ * Strategy:
152
+ * - If new text is shorter/equal: distribute text across existing runs proportionally
153
+ * - If new text is longer: extend the last run
154
+ * - Preserves charPrIDRef of each run
155
+ *
156
+ * @param sectionIndex Section index
157
+ * @param elementIndex Paragraph element index
158
+ * @param newText New text content
159
+ * @returns true if successful, false otherwise
160
+ */
161
+ updateParagraphTextPreserveStyles(sectionIndex: number, elementIndex: number, newText: string): boolean;
162
+ insertParagraph(sectionIndex: number, afterElementIndex: number, text?: string): number;
163
+ deleteParagraph(sectionIndex: number, elementIndex: number): boolean;
164
+ appendTextToParagraph(sectionIndex: number, elementIndex: number, text: string): void;
165
+ applyCharacterStyle(sectionIndex: number, elementIndex: number, runIndex: number, style: Partial<CharacterStyle>): void;
166
+ getCharacterStyle(sectionIndex: number, elementIndex: number, runIndex?: number): CharacterStyle | CharacterStyle[] | null;
167
+ applyParagraphStyle(sectionIndex: number, elementIndex: number, style: Partial<ParagraphStyle>): void;
168
+ getParagraphStyle(sectionIndex: number, elementIndex: number): ParagraphStyle | null;
169
+ /**
170
+ * Set hanging indent on a paragraph.
171
+ * In HWPML, hanging indent uses:
172
+ * - intent: negative value (pulls first line left)
173
+ * - left: positive value (base left margin for other lines)
174
+ * @param sectionIndex Section index
175
+ * @param elementIndex Paragraph element index
176
+ * @param indentPt Indent amount in points (positive value)
177
+ * @returns true if successful, false otherwise
178
+ */
179
+ setHangingIndent(sectionIndex: number, elementIndex: number, indentPt: number): boolean;
180
+ /**
181
+ * Get hanging indent value for a paragraph.
182
+ * @param sectionIndex Section index
183
+ * @param elementIndex Paragraph element index
184
+ * @returns Indent value in points, 0 if no hanging indent, null if invalid indices
185
+ */
186
+ getHangingIndent(sectionIndex: number, elementIndex: number): number | null;
187
+ /**
188
+ * Remove hanging indent from a paragraph.
189
+ * @param sectionIndex Section index
190
+ * @param elementIndex Paragraph element index
191
+ * @returns true if successful, false otherwise
192
+ */
193
+ removeHangingIndent(sectionIndex: number, elementIndex: number): boolean;
194
+ /**
195
+ * Load character property cache from header.xml.
196
+ * Maps charPr id to font size in pt.
197
+ */
198
+ private loadCharPrCache;
199
+ /**
200
+ * Get font size from charPr id.
201
+ * @param charPrId Character property ID
202
+ * @returns Font size in pt, or undefined if not found
203
+ */
204
+ getFontSizeFromCharPrId(charPrId: number): Promise<number | undefined>;
205
+ /**
206
+ * Get font size of a paragraph from XML.
207
+ * Reads charPrIDRef from the first run in the paragraph.
208
+ * @param sectionIndex Section index
209
+ * @param elementIndex Paragraph element index
210
+ * @returns Font size in pt, or undefined if not found
211
+ */
212
+ getParagraphFontSize(sectionIndex: number, elementIndex: number): Promise<number | undefined>;
213
+ /**
214
+ * Get font size of a paragraph in a table cell from XML.
215
+ * @param sectionIndex Section index
216
+ * @param tableIndex Table index within section
217
+ * @param row Row index (0-based)
218
+ * @param col Column index (0-based)
219
+ * @param paragraphIndex Paragraph index within cell (0-based)
220
+ * @returns Font size in pt, or undefined if not found
221
+ */
222
+ getTableCellParagraphFontSize(sectionIndex: number, tableIndex: number, row: number, col: number, paragraphIndex: number): Promise<number | undefined>;
223
+ /**
224
+ * Automatically set hanging indent based on detected marker in paragraph text.
225
+ * Uses HangingIndentCalculator to detect markers like "○ ", "1. ", "가. " etc.
226
+ * @param sectionIndex Section index
227
+ * @param elementIndex Paragraph element index
228
+ * @param fontSize Font size in pt (if not provided, reads from document)
229
+ * @returns Calculated indent value in pt, or 0 if no marker detected
230
+ */
231
+ setAutoHangingIndent(sectionIndex: number, elementIndex: number, fontSize?: number): number;
232
+ /**
233
+ * Automatically set hanging indent with dynamic font size from document.
234
+ * Async version that reads actual font size from the document.
235
+ * @param sectionIndex Section index
236
+ * @param elementIndex Paragraph element index
237
+ * @param fallbackFontSize Fallback font size in pt if document font size not found (default: 10)
238
+ * @returns Calculated indent value in pt, or 0 if no marker detected
239
+ */
240
+ setAutoHangingIndentAsync(sectionIndex: number, elementIndex: number, fallbackFontSize?: number): Promise<number>;
241
+ /**
242
+ * Set hanging indent on a paragraph inside a table cell.
243
+ * @param sectionIndex Section index
244
+ * @param tableIndex Table index within section
245
+ * @param row Row index (0-based)
246
+ * @param col Column index (0-based)
247
+ * @param paragraphIndex Paragraph index within cell (0-based)
248
+ * @param indentPt Indent value in points (positive)
249
+ * @returns true if successful, false otherwise
250
+ */
251
+ setTableCellHangingIndent(sectionIndex: number, tableIndex: number, row: number, col: number, paragraphIndex: number, indentPt: number): boolean;
252
+ /**
253
+ * Get hanging indent value for a paragraph inside a table cell.
254
+ * @param sectionIndex Section index
255
+ * @param tableIndex Table index within section
256
+ * @param row Row index (0-based)
257
+ * @param col Column index (0-based)
258
+ * @param paragraphIndex Paragraph index within cell (0-based)
259
+ * @returns Indent value in points, 0 if no hanging indent, null if invalid indices
260
+ */
261
+ getTableCellHangingIndent(sectionIndex: number, tableIndex: number, row: number, col: number, paragraphIndex: number): number | null;
262
+ /**
263
+ * Remove hanging indent from a paragraph inside a table cell.
264
+ * @param sectionIndex Section index
265
+ * @param tableIndex Table index within section
266
+ * @param row Row index (0-based)
267
+ * @param col Column index (0-based)
268
+ * @param paragraphIndex Paragraph index within cell (0-based)
269
+ * @returns true if successful, false otherwise
270
+ */
271
+ removeTableCellHangingIndent(sectionIndex: number, tableIndex: number, row: number, col: number, paragraphIndex: number): boolean;
272
+ /**
273
+ * Automatically set hanging indent on a paragraph inside a table cell
274
+ * based on detected marker in the text.
275
+ * @param sectionIndex Section index
276
+ * @param tableIndex Table index within section
277
+ * @param row Row index (0-based)
278
+ * @param col Column index (0-based)
279
+ * @param paragraphIndex Paragraph index within cell (0-based)
280
+ * @param fontSize Font size in pt (default: 10)
281
+ * @returns Calculated indent value in pt, or 0 if no marker detected
282
+ */
283
+ setTableCellAutoHangingIndent(sectionIndex: number, tableIndex: number, row: number, col: number, paragraphIndex: number, fontSize?: number): number;
284
+ /**
285
+ * Automatically set hanging indent on a paragraph inside a table cell
286
+ * with dynamic font size from document.
287
+ * Async version that reads actual font size from the document.
288
+ * @param sectionIndex Section index
289
+ * @param tableIndex Table index within section
290
+ * @param row Row index (0-based)
291
+ * @param col Column index (0-based)
292
+ * @param paragraphIndex Paragraph index within cell (0-based)
293
+ * @param fallbackFontSize Fallback font size in pt if document font size not found (default: 10)
294
+ * @returns Calculated indent value in pt, or 0 if no marker detected
295
+ */
296
+ setTableCellAutoHangingIndentAsync(sectionIndex: number, tableIndex: number, row: number, col: number, paragraphIndex: number, fallbackFontSize?: number): Promise<number>;
297
+ private findTable;
298
+ getTables(): Array<{
299
+ section: number;
300
+ index: number;
301
+ rows: number;
302
+ cols: number;
303
+ }>;
304
+ /**
305
+ * Get table map with headers - maps table indices to their header paragraphs
306
+ * Returns array of table info including the header text from the preceding paragraph
307
+ */
308
+ getTableMap(): Array<{
309
+ table_index: number;
310
+ section_index: number;
311
+ header: string;
312
+ rows: number;
313
+ cols: number;
314
+ is_empty: boolean;
315
+ first_row_preview: string[];
316
+ }>;
317
+ /**
318
+ * Check if a table is empty or contains only placeholder text
319
+ */
320
+ private isTableEmpty;
321
+ /**
322
+ * Find tables that are empty or contain only placeholders
323
+ */
324
+ findEmptyTables(): Array<{
325
+ table_index: number;
326
+ section_index: number;
327
+ header: string;
328
+ rows: number;
329
+ cols: number;
330
+ }>;
331
+ /**
332
+ * Get tables within a specific section
333
+ */
334
+ getTablesBySection(sectionIndex: number): Array<{
335
+ table_index: number;
336
+ local_index: number;
337
+ header: string;
338
+ rows: number;
339
+ cols: number;
340
+ is_empty: boolean;
341
+ }>;
342
+ /**
343
+ * Find tables by header text (partial match, case-insensitive)
344
+ */
345
+ findTableByHeader(searchText: string): Array<{
346
+ table_index: number;
347
+ section_index: number;
348
+ header: string;
349
+ rows: number;
350
+ cols: number;
351
+ is_empty: boolean;
352
+ first_row_preview: string[];
353
+ }>;
354
+ /**
355
+ * Get summary of multiple tables by index range
356
+ */
357
+ getTablesSummary(startIndex?: number, endIndex?: number): Array<{
358
+ table_index: number;
359
+ section_index: number;
360
+ header: string;
361
+ size: string;
362
+ is_empty: boolean;
363
+ content_preview: string;
364
+ }>;
365
+ /**
366
+ * Find cells by label text and return the adjacent cell position.
367
+ * Useful for form-like documents where labels identify input fields.
368
+ * @param labelText - The label text to search for (case-insensitive, partial match)
369
+ * @param direction - Direction to find target cell: 'right' (default) or 'down'
370
+ * @returns Array of found positions with label and target cell info
371
+ */
372
+ findCellByLabel(labelText: string, direction?: 'right' | 'down'): Array<{
373
+ tableIndex: number;
374
+ sectionIndex: number;
375
+ labelRow: number;
376
+ labelCol: number;
377
+ targetRow: number;
378
+ targetCol: number;
379
+ targetCellText: string;
380
+ }>;
381
+ /**
382
+ * Fill table cells using path-based mappings (jkf87 style).
383
+ * Path format: "labelText > direction" or chained "labelText > dir > dir"
384
+ * @param mappings - Object mapping paths to values, e.g., { "이름: > right": "홍길동", "합계 > down > down": "1000" }
385
+ * @returns Object with success count, failed paths, and details
386
+ */
387
+ fillByPath(mappings: Record<string, string>): {
388
+ success: number;
389
+ failed: string[];
390
+ details: Array<{
391
+ path: string;
392
+ tableIndex: number;
393
+ row: number;
394
+ col: number;
395
+ previousValue: string;
396
+ newValue: string;
397
+ }>;
398
+ };
399
+ /**
400
+ * Resolve a path string to a cell position.
401
+ * Path format: "labelText > direction" or chained "labelText > dir > dir"
402
+ * Directions: right, left, up, down
403
+ * @param path - Path string like "이름: > right" or "합계 > down > down"
404
+ * @returns Cell position or null if not found
405
+ */
406
+ private resolvePathToPosition;
407
+ /**
408
+ * Get context around a specific cell (neighboring cells' content).
409
+ * Useful for understanding a cell's position and meaning in a table.
410
+ * @param tableIndex - Global table index
411
+ * @param row - Row index (0-based)
412
+ * @param col - Column index (0-based)
413
+ * @param depth - How many cells in each direction to include (default: 1)
414
+ * @returns Object with center cell and neighboring cells' content
415
+ */
416
+ getCellContext(tableIndex: number, row: number, col: number, depth?: number): {
417
+ center: string;
418
+ [key: string]: string | undefined;
419
+ } | null;
420
+ /**
421
+ * Batch fill a table with 2D array data.
422
+ * Useful for filling multiple cells at once from structured data.
423
+ * @param tableIndex - Global table index
424
+ * @param data - 2D array of strings to fill (row-major order)
425
+ * @param startRow - Starting row index (default: 0)
426
+ * @param startCol - Starting column index (default: 0)
427
+ * @returns Object with success count and any out-of-bounds cells
428
+ */
429
+ batchFillTable(tableIndex: number, data: string[][], startRow?: number, startCol?: number): {
430
+ success: number;
431
+ outOfBounds: Array<{
432
+ row: number;
433
+ col: number;
434
+ value: string;
435
+ }>;
436
+ updated: Array<{
437
+ row: number;
438
+ col: number;
439
+ previousValue: string;
440
+ newValue: string;
441
+ }>;
442
+ };
443
+ /**
444
+ * Convert global table index to section and local index
445
+ * @param globalTableIndex - Global table index (0-based across all sections)
446
+ * @returns Object with section_index and local_index, or null if not found
447
+ */
448
+ convertGlobalToLocalTableIndex(globalTableIndex: number): {
449
+ section_index: number;
450
+ local_index: number;
451
+ } | null;
452
+ /**
453
+ * Get document outline - hierarchical structure showing headers and their associated tables
454
+ */
455
+ getDocumentOutline(): Array<{
456
+ type: 'section' | 'heading' | 'table' | 'paragraph';
457
+ level: number;
458
+ text: string;
459
+ section_index: number;
460
+ table_index?: number;
461
+ element_index: number;
462
+ }>;
463
+ /**
464
+ * Convert a global table index to element index in its section
465
+ * @param tableIndex - Global table index (0-based across all sections)
466
+ * @returns Object with section_index and element_index, or null if not found
467
+ */
468
+ getElementIndexForTable(tableIndex: number): {
469
+ section_index: number;
470
+ element_index: number;
471
+ table_info: {
472
+ rows: number;
473
+ cols: number;
474
+ header: string;
475
+ };
476
+ } | null;
477
+ /**
478
+ * Find element index of a paragraph containing specific text
479
+ * @param searchText - Text to search for (partial match, case-insensitive)
480
+ * @param sectionIndex - Optional: limit search to specific section
481
+ * @returns Array of matching positions with context
482
+ */
483
+ findParagraphByText(searchText: string, sectionIndex?: number): Array<{
484
+ section_index: number;
485
+ element_index: number;
486
+ text: string;
487
+ context_before: string;
488
+ context_after: string;
489
+ }>;
490
+ /**
491
+ * Get context around an element index (useful for verifying insertion point)
492
+ * @param sectionIndex - Section index
493
+ * @param elementIndex - Element index
494
+ * @param contextRange - Number of elements before/after to include (default: 2)
495
+ */
496
+ getInsertContext(sectionIndex: number, elementIndex: number, contextRange?: number): {
497
+ target_element: {
498
+ type: string;
499
+ text: string;
500
+ };
501
+ elements_before: Array<{
502
+ type: string;
503
+ text: string;
504
+ element_index: number;
505
+ }>;
506
+ elements_after: Array<{
507
+ type: string;
508
+ text: string;
509
+ element_index: number;
510
+ }>;
511
+ recommended_insert_after: number;
512
+ } | null;
513
+ /**
514
+ * Find insertion position by searching for a header/title text
515
+ * Returns position right after the found paragraph (good for inserting content under a header)
516
+ * @param headerText - Text to search for in paragraph headers
517
+ */
518
+ /**
519
+ * Find insertion position after header/text.
520
+ * @param headerText - Text to search for
521
+ * @param searchIn - Where to search: 'paragraphs', 'table_cells', or 'all' (default)
522
+ */
523
+ findInsertPositionAfterHeader(headerText: string, searchIn?: 'paragraphs' | 'table_cells' | 'all'): {
524
+ section_index: number;
525
+ element_index: number;
526
+ insert_after: number;
527
+ header_found: string;
528
+ found_in: 'paragraph' | 'table_cell';
529
+ table_info?: {
530
+ table_index: number;
531
+ row: number;
532
+ col: number;
533
+ };
534
+ next_element: {
535
+ type: string;
536
+ text: string;
537
+ } | null;
538
+ } | null;
539
+ /**
540
+ * Find text in table cells
541
+ * @param searchText - Text to search for (partial match)
542
+ */
543
+ private findTextInTableCells;
544
+ /**
545
+ * Find insertion position right after a specific table
546
+ * @param tableIndex - Global table index
547
+ */
548
+ findInsertPositionAfterTable(tableIndex: number): {
549
+ section_index: number;
550
+ element_index: number;
551
+ insert_after: number;
552
+ table_info: {
553
+ rows: number;
554
+ cols: number;
555
+ header: string;
556
+ };
557
+ next_element: {
558
+ type: string;
559
+ text: string;
560
+ } | null;
561
+ } | null;
562
+ getTable(sectionIndex: number, tableIndex: number): {
563
+ rows: number;
564
+ cols: number;
565
+ data: any[][];
566
+ } | null;
567
+ getTableCell(sectionIndex: number, tableIndex: number, row: number, col: number): {
568
+ text: string;
569
+ cell: TableCell;
570
+ } | null;
571
+ updateTableCell(sectionIndex: number, tableIndex: number, row: number, col: number, text: string, charShapeId?: number): boolean;
572
+ setCellProperties(sectionIndex: number, tableIndex: number, row: number, col: number, props: Partial<TableCell>): boolean;
573
+ insertTableRow(sectionIndex: number, tableIndex: number, afterRowIndex: number, cellTexts?: string[]): boolean;
574
+ deleteTableRow(sectionIndex: number, tableIndex: number, rowIndex: number): boolean;
575
+ /**
576
+ * Delete an entire table from the document
577
+ */
578
+ deleteTable(sectionIndex: number, tableIndex: number): boolean;
579
+ insertTableColumn(sectionIndex: number, tableIndex: number, afterColIndex: number): boolean;
580
+ deleteTableColumn(sectionIndex: number, tableIndex: number, colIndex: number): boolean;
581
+ getTableAsCsv(sectionIndex: number, tableIndex: number, delimiter?: string): string | null;
582
+ searchText(query: string, options?: {
583
+ caseSensitive?: boolean;
584
+ regex?: boolean;
585
+ includeTables?: boolean;
586
+ }): Array<{
587
+ section: number;
588
+ element: number;
589
+ text: string;
590
+ matches: string[];
591
+ count: number;
592
+ location?: {
593
+ type: 'paragraph' | 'table';
594
+ tableIndex?: number;
595
+ row?: number;
596
+ col?: number;
597
+ };
598
+ }>;
599
+ replaceText(oldText: string, newText: string, options?: {
600
+ caseSensitive?: boolean;
601
+ regex?: boolean;
602
+ replaceAll?: boolean;
603
+ }): number;
604
+ /**
605
+ * Replace text within a specific table cell.
606
+ * This is more targeted than replaceText and works directly on cell content.
607
+ */
608
+ replaceTextInCell(sectionIndex: number, tableIndex: number, row: number, col: number, oldText: string, newText: string, options?: {
609
+ caseSensitive?: boolean;
610
+ regex?: boolean;
611
+ replaceAll?: boolean;
612
+ }): {
613
+ success: boolean;
614
+ count: number;
615
+ error?: string;
616
+ };
617
+ getMetadata(): HwpxContent['metadata'];
618
+ setMetadata(metadata: Partial<HwpxContent['metadata']>): void;
619
+ getPageSettings(sectionIndex?: number): PageSettings | null;
620
+ setPageSettings(sectionIndex: number, settings: Partial<PageSettings>): boolean;
621
+ getWordCount(): {
622
+ characters: number;
623
+ charactersNoSpaces: number;
624
+ words: number;
625
+ paragraphs: number;
626
+ };
627
+ copyParagraph(sourceSection: number, sourceParagraph: number, targetSection: number, targetAfter: number): boolean;
628
+ moveParagraph(sourceSection: number, sourceParagraph: number, targetSection: number, targetAfter: number): boolean;
629
+ /**
630
+ * Move a table from one location to another within the document.
631
+ * Uses XML-based approach for accurate preservation of table structure.
632
+ */
633
+ moveTable(sectionIndex: number, tableIndex: number, targetSectionIndex: number, targetAfterIndex: number): {
634
+ success: boolean;
635
+ error?: string;
636
+ };
637
+ /**
638
+ * Copy a table to another location (preserving original).
639
+ * Generates new IDs for the copied table.
640
+ */
641
+ copyTable(sectionIndex: number, tableIndex: number, targetSectionIndex: number, targetAfterIndex: number): {
642
+ success: boolean;
643
+ error?: string;
644
+ };
645
+ /**
646
+ * Validate XML tag balance for specified tags.
647
+ * Returns balanced status and any mismatches found.
648
+ */
649
+ validateTagBalance(xml: string): {
650
+ balanced: boolean;
651
+ mismatches: Array<{
652
+ tag: string;
653
+ opens: number;
654
+ closes: number;
655
+ }>;
656
+ };
657
+ /**
658
+ * Validate XML text content is properly escaped.
659
+ */
660
+ validateXmlEscaping(xml: string): {
661
+ valid: boolean;
662
+ issues?: string[];
663
+ };
664
+ private _pendingTableMoves;
665
+ getImages(): Array<{
666
+ id: string;
667
+ width: number;
668
+ height: number;
669
+ }>;
670
+ insertTable(sectionIndex: number, afterElementIndex: number, rows: number, cols: number, options?: {
671
+ width?: number;
672
+ cellWidth?: number;
673
+ }): {
674
+ tableIndex: number;
675
+ } | null;
676
+ /**
677
+ * Insert a nested table inside a table cell.
678
+ * @param sectionIndex Section index
679
+ * @param parentTableIndex Parent table index
680
+ * @param row Row index in parent table
681
+ * @param col Column index in parent table
682
+ * @param nestedRows Number of rows in nested table
683
+ * @param nestedCols Number of columns in nested table
684
+ * @param options Optional data for cells
685
+ */
686
+ insertNestedTable(sectionIndex: number, parentTableIndex: number, row: number, col: number, nestedRows: number, nestedCols: number, options?: {
687
+ data?: string[][];
688
+ }): {
689
+ success: boolean;
690
+ error?: string;
691
+ };
692
+ /**
693
+ * Merge multiple cells in a table into a single cell.
694
+ * The top-left cell becomes the master cell, and other cells in the range are removed.
695
+ *
696
+ * @param sectionIndex Section index
697
+ * @param tableIndex Table index within the section
698
+ * @param startRow Starting row index (0-based)
699
+ * @param startCol Starting column index (0-based)
700
+ * @param endRow Ending row index (0-based, inclusive)
701
+ * @param endCol Ending column index (0-based, inclusive)
702
+ * @returns true if merge was successful, false otherwise
703
+ */
704
+ mergeCells(sectionIndex: number, tableIndex: number, startRow: number, startCol: number, endRow: number, endCol: number): boolean;
705
+ /**
706
+ * Split a merged cell back into individual cells.
707
+ * Only works on cells with colSpan > 1 or rowSpan > 1.
708
+ *
709
+ * @param sectionIndex Section index
710
+ * @param tableIndex Table index within the section
711
+ * @param row Row index of the merged cell (0-based)
712
+ * @param col Column index of the merged cell (0-based)
713
+ * @returns true if split was successful, false otherwise
714
+ */
715
+ splitCell(sectionIndex: number, tableIndex: number, row: number, col: number): boolean;
716
+ getHeader(sectionIndex: number): {
717
+ paragraphs: any[];
718
+ } | null;
719
+ setHeader(sectionIndex: number, text: string): boolean;
720
+ getFooter(sectionIndex: number): {
721
+ paragraphs: any[];
722
+ } | null;
723
+ setFooter(sectionIndex: number, text: string): boolean;
724
+ getFootnotes(): Footnote[];
725
+ insertFootnote(sectionIndex: number, paragraphIndex: number, text: string): {
726
+ id: string;
727
+ } | null;
728
+ getEndnotes(): Endnote[];
729
+ insertEndnote(sectionIndex: number, paragraphIndex: number, text: string): {
730
+ id: string;
731
+ } | null;
732
+ getBookmarks(): {
733
+ name: string;
734
+ section: number;
735
+ paragraph: number;
736
+ }[];
737
+ insertBookmark(sectionIndex: number, paragraphIndex: number, name: string): boolean;
738
+ getHyperlinks(): {
739
+ url: string;
740
+ text: string;
741
+ section: number;
742
+ paragraph: number;
743
+ }[];
744
+ insertHyperlink(sectionIndex: number, paragraphIndex: number, url: string, text: string): boolean;
745
+ /**
746
+ * Insert an image into the document.
747
+ *
748
+ * @param sectionIndex Section to insert into
749
+ * @param afterElementIndex Insert after this element index (-1 for beginning)
750
+ * @param imageData Image data including base64 data and MIME type
751
+ * - width: Target width in points (optional if preserveAspectRatio is true)
752
+ * - height: Target height in points (optional if preserveAspectRatio is true)
753
+ * - preserveAspectRatio: If true, maintains original image aspect ratio.
754
+ * When only width is specified, height is auto-calculated.
755
+ * When only height is specified, width is auto-calculated.
756
+ * When neither is specified, uses original dimensions (scaled to fit if too large).
757
+ * - position: Positioning options for the image (inline/floating, alignment, offset, text wrap)
758
+ * @returns Object with image ID or null on failure
759
+ */
760
+ insertImage(sectionIndex: number, afterElementIndex: number, imageData: {
761
+ data: string;
762
+ mimeType: string;
763
+ width?: number;
764
+ height?: number;
765
+ preserveAspectRatio?: boolean;
766
+ position?: ImagePositionOptions;
767
+ headerText?: string;
768
+ }): {
769
+ id: string;
770
+ actualWidth: number;
771
+ actualHeight: number;
772
+ } | null;
773
+ /**
774
+ * Insert an image inside a table cell
775
+ * @param sectionIndex - Section containing the table
776
+ * @param tableIndex - Table index (local to section)
777
+ * @param row - Row index (0-based)
778
+ * @param col - Column index (0-based)
779
+ * @param imageData - Image data including base64, mimeType, and optional dimensions
780
+ * @returns Object with image ID and actual dimensions, or null on failure
781
+ */
782
+ insertImageInCell(sectionIndex: number, tableIndex: number, row: number, col: number, imageData: {
783
+ data: string;
784
+ mimeType: string;
785
+ width?: number;
786
+ height?: number;
787
+ preserveAspectRatio?: boolean;
788
+ afterText?: string;
789
+ }): {
790
+ id: string;
791
+ actualWidth: number;
792
+ actualHeight: number;
793
+ } | null;
794
+ /**
795
+ * Get existing image IDs from ZIP file
796
+ */
797
+ private getExistingImageIds;
798
+ updateImageSize(imageId: string, width: number, height: number): boolean;
799
+ deleteImage(imageId: string): boolean;
800
+ insertLine(sectionIndex: number, x1: number, y1: number, x2: number, y2: number, options?: {
801
+ color?: string;
802
+ width?: number;
803
+ }): {
804
+ id: string;
805
+ } | null;
806
+ insertRect(sectionIndex: number, x: number, y: number, width: number, height: number, options?: {
807
+ fillColor?: string;
808
+ strokeColor?: string;
809
+ }): {
810
+ id: string;
811
+ } | null;
812
+ insertEllipse(sectionIndex: number, cx: number, cy: number, rx: number, ry: number, options?: {
813
+ fillColor?: string;
814
+ strokeColor?: string;
815
+ }): {
816
+ id: string;
817
+ } | null;
818
+ insertEquation(sectionIndex: number, afterElementIndex: number, script: string): {
819
+ id: string;
820
+ } | null;
821
+ getEquations(): {
822
+ id: string;
823
+ script: string;
824
+ }[];
825
+ getMemos(): Memo[];
826
+ insertMemo(sectionIndex: number, paragraphIndex: number, content: string, author?: string): {
827
+ id: string;
828
+ } | null;
829
+ deleteMemo(memoId: string): boolean;
830
+ getSections(): {
831
+ index: number;
832
+ pageSettings: PageSettings;
833
+ }[];
834
+ insertSection(afterSectionIndex: number): number;
835
+ deleteSection(sectionIndex: number): boolean;
836
+ getStyles(): {
837
+ id: number;
838
+ name: string;
839
+ type: string;
840
+ }[];
841
+ getCharShapes(): CharShape[];
842
+ getParaShapes(): ParaShape[];
843
+ applyStyle(sectionIndex: number, paragraphIndex: number, styleId: number): boolean;
844
+ getColumnDef(sectionIndex: number): ColumnDef | null;
845
+ setColumnDef(sectionIndex: number, columns: number, gap?: number): boolean;
846
+ save(): Promise<Buffer>;
847
+ private syncContentToZip;
848
+ /**
849
+ * Invalidate cached XML positions for all paragraphs.
850
+ * Called after save() because XML modifications may shift byte positions.
851
+ * The positions will be re-populated on next document reload.
852
+ */
853
+ private invalidateXmlPositions;
854
+ /**
855
+ * Get cached XML position for a paragraph at the given section and element index.
856
+ * Returns undefined if no cached position is available.
857
+ * The cached positions are populated during parsing in HwpxParser.parseSection().
858
+ */
859
+ private getCachedXmlPosition;
860
+ /**
861
+ * Remove Fasoo DRM tracking information from content.hpf.
862
+ * Fasoo DRM adds tracking IDs to the description metadata which causes
863
+ * "document corrupted or tampered" warnings when the file is modified externally.
864
+ */
865
+ private removeFasooDrmTracking;
866
+ /**
867
+ * Apply pending image deletions to the ZIP.
868
+ * Removes <hp:pic> elements from section XML and deletes BinData files.
869
+ */
870
+ private applyImageDeletesToZip;
871
+ /**
872
+ * Apply table deletes to XML.
873
+ * Removes tables from the section XML.
874
+ * Uses findAllTables for proper nested table handling.
875
+ */
876
+ private applyTableDeletesToXml;
877
+ /**
878
+ * Apply paragraph/element deletes to XML.
879
+ * Removes paragraphs or tables from the section XML.
880
+ */
881
+ private applyParagraphDeletesToXml;
882
+ /**
883
+ * Apply table inserts to XML.
884
+ * Inserts new tables into the section XML.
885
+ */
886
+ private applyTableInsertsToXml;
887
+ /**
888
+ * Apply table move/copy operations to XML.
889
+ * Extracts table XML from source and inserts at target position.
890
+ */
891
+ private applyTableMovesToXml;
892
+ /**
893
+ * Regenerate all IDs in XML to avoid duplicates.
894
+ */
895
+ private regenerateIdsInXml;
896
+ /**
897
+ * Find the position to insert an element after a given element index.
898
+ * Returns the position after the closing tag of the element.
899
+ */
900
+ private findInsertPositionForElement;
901
+ /**
902
+ * Apply paragraph inserts to XML.
903
+ * Inserts new paragraphs at the specified positions.
904
+ */
905
+ private applyParagraphInsertsToXml;
906
+ /**
907
+ * Apply nested table inserts to XML.
908
+ * Inserts a new table inside a cell of an existing table.
909
+ */
910
+ private applyNestedTableInsertsToXml;
911
+ /**
912
+ * Generate XML for a nested table.
913
+ */
914
+ private generateNestedTableXml;
915
+ /**
916
+ * Insert a nested table XML into a cell XML.
917
+ * Finds the last <hp:p> in the cell and inserts the table inside a run.
918
+ */
919
+ private insertNestedTableIntoCell;
920
+ /**
921
+ * Apply cell merges to XML.
922
+ * Updates colSpan/rowSpan attributes on master cell and removes merged cells.
923
+ * Groups merges by table to handle multiple merges in the same table correctly.
924
+ */
925
+ private applyCellMergesToXml;
926
+ /**
927
+ * Apply merge to a single table XML.
928
+ * @returns Updated table XML or null if merge failed
929
+ */
930
+ private applyMergeToTable;
931
+ /**
932
+ * Apply cell splits to XML.
933
+ * Resets colSpan/rowSpan to 1 and creates new cells to fill the split area.
934
+ */
935
+ private applyCellSplitsToXml;
936
+ /**
937
+ * Apply split to a single table XML.
938
+ * @returns Updated table XML or null if split failed
939
+ */
940
+ private applySplitToTable;
941
+ /**
942
+ * Generate an empty cell XML for split operations.
943
+ */
944
+ private generateEmptyCell;
945
+ /**
946
+ * Apply table cell updates to XML while preserving original structure.
947
+ * This function modifies only the text content of specific cells,
948
+ * keeping all other XML elements, attributes, and structure intact.
949
+ *
950
+ * Safety features:
951
+ * - Backs up original XML before modification
952
+ * - Validates XML structure after changes
953
+ * - Reverts to original if validation fails
954
+ */
955
+ private applyTableCellUpdatesToXml;
956
+ /**
957
+ * Basic XML structure validation.
958
+ * Checks for common corruption indicators.
959
+ * Note: This is intentionally lenient to avoid false positives.
960
+ */
961
+ private validateXmlStructure;
962
+ /**
963
+ * Check tag balance for a specific element name.
964
+ * Returns the difference (open - close). 0 means balanced.
965
+ */
966
+ private checkTagBalance;
967
+ /**
968
+ * Validate table structure integrity.
969
+ * Checks:
970
+ * - Row count consistency (declared vs actual)
971
+ * - Cell count per row matches colCnt when accounting for colSpan
972
+ * - colAddr/rowAddr continuity
973
+ * - No orphaned cells (rowSpan consistency)
974
+ * @returns Error message if validation fails, null if valid
975
+ */
976
+ private validateTableStructure;
977
+ /**
978
+ * Extract all nested tables from XML content.
979
+ * Uses balanced bracket matching to find complete table elements.
980
+ */
981
+ private extractNestedTables;
982
+ /**
983
+ * Find a table by its ID in XML.
984
+ */
985
+ private findTableById;
986
+ /**
987
+ * Extract complete table XML from a regex match.
988
+ */
989
+ private extractTableFromMatch;
990
+ /**
991
+ * Find all tables in XML and return their positions and content.
992
+ */
993
+ /**
994
+ * Find top-level paragraph and table elements (direct children of section).
995
+ * Uses depth tracking to skip elements nested inside <hp:tbl>, <hp:tc>, <hp:secPr>, etc.
996
+ * Only counts <hp:p> and <hp:tbl> at depth 0 (relative to section root).
997
+ */
998
+ private findTopLevelElements;
999
+ private findAllTables;
1000
+ /**
1001
+ * Find all elements of a given type using depth tracking.
1002
+ * This correctly handles nested elements (e.g., nested tables).
1003
+ * @param xml The XML string to search in
1004
+ * @param elementName The element name without namespace prefix (e.g., 'tr', 'tc')
1005
+ * @returns Array of elements with their positions
1006
+ */
1007
+ private findAllElementsWithDepth;
1008
+ /**
1009
+ * Update specific cells in a table XML string.
1010
+ * Groups updates by row to avoid index corruption when multiple cells in the same row are updated.
1011
+ */
1012
+ private updateTableCellsInXml;
1013
+ /**
1014
+ * Update multiple cells in a single row XML string.
1015
+ * Processes cells from right to left (descending col order) to avoid index shifting.
1016
+ */
1017
+ private updateMultipleCellsInRow;
1018
+ /**
1019
+ * Update a specific cell in a row XML string.
1020
+ * @deprecated Use updateMultipleCellsInRow for better index handling
1021
+ */
1022
+ private updateCellInRow;
1023
+ /**
1024
+ * Reset lineseg values to default so Hancom Word recalculates line layout.
1025
+ * When text content changes, the old lineseg values (horzsize, textpos, etc.)
1026
+ * no longer match the new text, causing rendering issues like overlapping text.
1027
+ * By resetting to default values, Hancom Word will recalculate proper line breaks.
1028
+ */
1029
+ private resetLinesegInXml;
1030
+ /**
1031
+ * Update text content in a cell XML string.
1032
+ * Handles both existing text replacement and empty cell population.
1033
+ * If charShapeId is provided, overrides the charPrIDRef attribute.
1034
+ */
1035
+ private updateTextInCell;
1036
+ /**
1037
+ * Update text content in a cell with chunked runs (for long text without newlines).
1038
+ * Splits long text into multiple <hp:run> elements within a single paragraph.
1039
+ */
1040
+ private updateTextInCellChunked;
1041
+ /**
1042
+ * Update text content in a cell with multiple paragraphs (for text with newlines).
1043
+ * Each line becomes a separate <hp:p> element, allowing independent styling.
1044
+ */
1045
+ private updateTextInCellMultiline;
1046
+ /**
1047
+ * Apply cell image inserts to XML
1048
+ * Inserts an image inside a table cell
1049
+ */
1050
+ private applyCellImageInsertsToXml;
1051
+ /**
1052
+ * Apply direct text updates (exact match replacement)
1053
+ * Groups updates by paragraph to handle multi-run updates correctly
1054
+ *
1055
+ * BUGFIX (2026-01-25): Pre-compute paragraph mappings before any modifications
1056
+ * to prevent text merging when multiple paragraphs have the same oldText pattern.
1057
+ * Updates are applied in reverse order (bottom-to-top) to avoid position shifts.
1058
+ */
1059
+ private applyDirectTextUpdatesToXml;
1060
+ /**
1061
+ * Replace multiple runs in a paragraph element at once
1062
+ * This is needed when updating run 0 also clears runs 1-N
1063
+ */
1064
+ private replaceMultipleRunsInElement;
1065
+ /**
1066
+ * Calculate the occurrence index for a paragraph with given ID.
1067
+ * Returns how many paragraphs with the same ID appear before this one.
1068
+ */
1069
+ private getParagraphOccurrence;
1070
+ /**
1071
+ * Find paragraph by its ID attribute and occurrence index.
1072
+ * Uses balanced tag matching to handle nested paragraphs.
1073
+ * @param xml - The XML content
1074
+ * @param paragraphId - The paragraph ID to find
1075
+ * @param occurrence - Which occurrence of this ID (0-indexed)
1076
+ */
1077
+ private findParagraphById;
1078
+ /**
1079
+ * Calculate Levenshtein distance between two strings.
1080
+ * Used for fuzzy paragraph matching when exact match fails.
1081
+ */
1082
+ private levenshteinDistance;
1083
+ /**
1084
+ * Find paragraph using fuzzy text matching with Levenshtein distance.
1085
+ * Fallback method when ID and index-based lookups fail.
1086
+ */
1087
+ private findParagraphByFuzzyMatch;
1088
+ /**
1089
+ * Find the target paragraph for an update operation.
1090
+ * Extracts paragraph-finding logic from replaceMultipleRunsInElement for reuse.
1091
+ * Returns the paragraph's start, end, and XML content in the original document.
1092
+ */
1093
+ private findTargetParagraphForUpdate;
1094
+ /**
1095
+ * Replace multiple runs in a paragraph element at once.
1096
+ * Finds hp:run elements and updates their hp:t content.
1097
+ */
1098
+ private replaceRunsInParagraphDirect;
1099
+ /**
1100
+ * Replace text in a single run directly using pre-computed target location.
1101
+ * Simpler version for single-run updates.
1102
+ */
1103
+ private replaceTextInElementDirect;
1104
+ /**
1105
+ * Replace text in a paragraph identified by both ID and text content.
1106
+ * This is more reliable because:
1107
+ * - Uses ID to narrow down candidates (even if not unique)
1108
+ * - Uses oldText to find the exact paragraph among candidates
1109
+ */
1110
+ private replaceTextInParagraphByIdAndText;
1111
+ /**
1112
+ * Replace text only within a specific element (paragraph) identified by elementIndex.
1113
+ * This ensures that identical text in other parts of the document is not affected.
1114
+ *
1115
+ * IMPORTANT: This follows the same element indexing as HwpxParser.parseSection:
1116
+ * - All top-level elements are counted: paragraphs, tables, images, shapes (23+ types)
1117
+ * - Paragraphs INSIDE tables are NOT counted (they're part of the table)
1118
+ * - Text replacement only applies to paragraph elements (type 'p')
1119
+ * - Other elements (images, shapes, etc.) are counted for indexing but not modified
1120
+ */
1121
+ private replaceTextInElementByIndex;
1122
+ private escapeRegex;
1123
+ /**
1124
+ * Replace text in a paragraph identified by its ID.
1125
+ * This is more reliable than index-based lookup because:
1126
+ * - Paragraph IDs are stable across document modifications
1127
+ * - Not affected by the presence of images, shapes, or other elements
1128
+ */
1129
+ private replaceTextInParagraphById;
1130
+ /**
1131
+ * Apply text replacements directly to XML files.
1132
+ * This is the safest approach as it preserves the original XML structure.
1133
+ */
1134
+ private applyTextReplacementsToXml;
1135
+ /**
1136
+ * Sync structural changes (paragraph text, table cells, etc.)
1137
+ * Regenerates section XML from _content to handle new elements.
1138
+ */
1139
+ private syncStructuralChangesToZip;
1140
+ /**
1141
+ * Generate complete section XML from HwpxSection content.
1142
+ */
1143
+ private generateSectionXml;
1144
+ /**
1145
+ * Generate paragraph XML from HwpxParagraph.
1146
+ */
1147
+ private generateParagraphXml;
1148
+ /**
1149
+ * Generate table XML from HwpxTable.
1150
+ */
1151
+ private generateTableXml;
1152
+ /**
1153
+ * Update section XML with current content.
1154
+ * Handles paragraphs and table cells.
1155
+ */
1156
+ private updateSectionXml;
1157
+ /**
1158
+ * Update paragraph XML with new text content.
1159
+ */
1160
+ private updateParagraphXml;
1161
+ /**
1162
+ * Serialize a CharShape object to XML string.
1163
+ * This preserves all character style properties including spacing (자간).
1164
+ */
1165
+ private serializeCharShape;
1166
+ /**
1167
+ * Sync charShapes from memory to header.xml.
1168
+ * This ensures character styles (including spacing) are preserved after save.
1169
+ */
1170
+ private syncCharShapesToZip;
1171
+ /**
1172
+ * Sync metadata to header.xml
1173
+ */
1174
+ private syncMetadataToZip;
1175
+ private escapeXml;
1176
+ /**
1177
+ * Default chunk size for splitting long text (in characters).
1178
+ * Texts longer than this will be split into multiple <hp:run> elements.
1179
+ */
1180
+ private static readonly TEXT_CHUNK_SIZE;
1181
+ /**
1182
+ * Split long text into chunks for safer XML processing.
1183
+ * Attempts to split at word boundaries (spaces, punctuation) when possible.
1184
+ * @param text The text to split
1185
+ * @param maxChunkSize Maximum characters per chunk (default: TEXT_CHUNK_SIZE)
1186
+ * @returns Array of text chunks
1187
+ */
1188
+ private splitTextIntoChunks;
1189
+ /**
1190
+ * Generate multiple <hp:run> elements for chunked text.
1191
+ * Used when text is too long to be in a single run.
1192
+ */
1193
+ private generateChunkedRuns;
1194
+ /**
1195
+ * Get image dimensions from base64 encoded data
1196
+ * Returns width and height in pixels
1197
+ */
1198
+ private getImageDimensions;
1199
+ /**
1200
+ * Get raw XML content of a section.
1201
+ * Useful for AI-based document manipulation.
1202
+ */
1203
+ getSectionXml(sectionIndex: number): Promise<string | null>;
1204
+ /**
1205
+ * Set (replace) raw XML content of a section.
1206
+ * WARNING: This completely replaces the section XML. Use with caution.
1207
+ * The XML must be valid HWPML format.
1208
+ *
1209
+ * @param sectionIndex The section index to replace
1210
+ * @param xml The new XML content (must be valid HWPML)
1211
+ * @param validate If true, performs basic XML validation before replacing
1212
+ * @returns Object with success status and any validation errors
1213
+ */
1214
+ setSectionXml(sectionIndex: number, xml: string, validate?: boolean): Promise<{
1215
+ success: boolean;
1216
+ error?: string;
1217
+ }>;
1218
+ /**
1219
+ * Validate section XML structure.
1220
+ */
1221
+ private validateSectionXml;
1222
+ /**
1223
+ * Render Mermaid diagram and insert as image into the document.
1224
+ * Uses mermaid.ink API for rendering.
1225
+ *
1226
+ * @param mermaidCode The Mermaid diagram code
1227
+ * @param sectionIndex Section to insert into
1228
+ * @param afterElementIndex Insert after this element index (-1 for beginning)
1229
+ * @param options Optional rendering options
1230
+ * - width: Target width in points (optional)
1231
+ * - height: Target height in points (optional)
1232
+ * - preserveAspectRatio: If true, maintains original image aspect ratio (default: true)
1233
+ * When only width is specified, height is auto-calculated.
1234
+ * When only height is specified, width is auto-calculated.
1235
+ * - position: Positioning options for the rendered diagram
1236
+ * @returns Object with image ID and actual dimensions, or error
1237
+ */
1238
+ renderMermaidToImage(mermaidCode: string, sectionIndex: number, afterElementIndex: number, options?: {
1239
+ width?: number;
1240
+ height?: number;
1241
+ theme?: 'default' | 'dark' | 'forest' | 'neutral';
1242
+ backgroundColor?: string;
1243
+ preserveAspectRatio?: boolean;
1244
+ position?: ImagePositionOptions;
1245
+ headerText?: string;
1246
+ }): Promise<{
1247
+ success: boolean;
1248
+ imageId?: string;
1249
+ actualWidth?: number;
1250
+ actualHeight?: number;
1251
+ error?: string;
1252
+ }>;
1253
+ /**
1254
+ * Get list of available sections.
1255
+ */
1256
+ getAvailableSections(): Promise<number[]>;
1257
+ /**
1258
+ * Apply pending image inserts to ZIP file.
1259
+ * 1. Add image file to BinData/ folder
1260
+ * 2. Update content.hpf manifest
1261
+ * 3. Add hp:pic tag to section XML
1262
+ */
1263
+ private applyImageInsertsToZip;
1264
+ /**
1265
+ * Get file extension from MIME type
1266
+ */
1267
+ private getExtensionFromMimeType;
1268
+ /**
1269
+ * Extract original image dimensions from base64 encoded image data.
1270
+ * Supports PNG and JPEG formats.
1271
+ * @param base64Data Base64 encoded image data
1272
+ * @param mimeType MIME type of the image
1273
+ * @returns { width, height } or null if unable to parse
1274
+ */
1275
+ private getImageDimensionsFromBase64;
1276
+ /**
1277
+ * Add image entry to content.hpf manifest
1278
+ */
1279
+ private addImageToContentHpf;
1280
+ /**
1281
+ * Add hp:pic tag to section XML
1282
+ */
1283
+ private addImageToSectionXml;
1284
+ /**
1285
+ * Find insertion position in XML by searching for text content.
1286
+ * Returns the position right after the paragraph containing the text, or null if not found.
1287
+ */
1288
+ private findInsertPositionByTextInXml;
1289
+ /**
1290
+ * Extract text content from paragraph XML
1291
+ */
1292
+ private extractTextFromParagraphXml;
1293
+ /**
1294
+ * Extract text content from cell XML (handles subList and nested paragraphs)
1295
+ */
1296
+ private extractTextFromCellXml;
1297
+ /**
1298
+ * Find all paragraphs in a table cell XML
1299
+ * Returns array of { start, end, xml } for each paragraph
1300
+ */
1301
+ private findAllParagraphsInCell;
1302
+ /**
1303
+ * Generate hp:pic XML tag for image with positioning options
1304
+ */
1305
+ private generateImagePicXml;
1306
+ /**
1307
+ * Analyze XML for issues like tag imbalance, malformed elements, etc.
1308
+ * @param sectionIndex Section to analyze (optional, all sections if not specified)
1309
+ * @returns Detailed analysis report
1310
+ */
1311
+ analyzeXml(sectionIndex?: number): Promise<{
1312
+ hasIssues: boolean;
1313
+ sections: Array<{
1314
+ sectionIndex: number;
1315
+ issues: Array<{
1316
+ type: 'tag_imbalance' | 'malformed_tag' | 'unclosed_tag' | 'orphan_close_tag' | 'nesting_error';
1317
+ severity: 'error' | 'warning';
1318
+ message: string;
1319
+ position?: number;
1320
+ context?: string;
1321
+ suggestedFix?: string;
1322
+ }>;
1323
+ tagCounts: Record<string, {
1324
+ open: number;
1325
+ close: number;
1326
+ balance: number;
1327
+ }>;
1328
+ }>;
1329
+ summary: string;
1330
+ }>;
1331
+ /**
1332
+ * Analyze XML content for issues
1333
+ */
1334
+ private analyzeXmlContent;
1335
+ /**
1336
+ * Find specific issues with tbl (table) tags
1337
+ */
1338
+ private findTblTagIssues;
1339
+ /**
1340
+ * Check for common nesting errors
1341
+ */
1342
+ private checkNestingErrors;
1343
+ /**
1344
+ * Attempt to repair XML issues in a section
1345
+ * @param sectionIndex Section to repair
1346
+ * @param options Repair options
1347
+ * @returns Repair result
1348
+ */
1349
+ repairXml(sectionIndex: number, options?: {
1350
+ removeOrphanCloseTags?: boolean;
1351
+ addMissingCloseTags?: boolean;
1352
+ fixTableStructure?: boolean;
1353
+ backup?: boolean;
1354
+ }): Promise<{
1355
+ success: boolean;
1356
+ message: string;
1357
+ repairsApplied: string[];
1358
+ originalXml?: string;
1359
+ }>;
1360
+ /**
1361
+ * Remove orphan closing tags (tbl, tr, tc, p, subList)
1362
+ */
1363
+ private removeOrphanCloseTags;
1364
+ /**
1365
+ * Fix tag imbalance globally (not just within tables)
1366
+ */
1367
+ private fixTagImbalanceGlobal;
1368
+ /**
1369
+ * Fix table structure issues
1370
+ */
1371
+ private fixTableStructure;
1372
+ /**
1373
+ * Fix tag imbalance for a specific element type
1374
+ */
1375
+ private fixTagImbalance;
1376
+ /**
1377
+ * Get raw XML of a section for manual inspection/editing
1378
+ */
1379
+ getRawSectionXml(sectionIndex: number): Promise<string | null>;
1380
+ /**
1381
+ * Set raw XML of a section (use with caution)
1382
+ */
1383
+ setRawSectionXml(sectionIndex: number, xml: string, validate?: boolean): Promise<{
1384
+ success: boolean;
1385
+ message: string;
1386
+ issues?: Array<{
1387
+ type: string;
1388
+ message: string;
1389
+ }>;
1390
+ }>;
1391
+ /**
1392
+ * 위치 찾기 통합 도구
1393
+ * @param type 찾을 대상 유형: 'table' | 'paragraph' | 'insert_point'
1394
+ * @param query 검색할 텍스트
1395
+ * @returns 찾은 위치 정보 또는 null
1396
+ */
1397
+ findPosition(type: 'table' | 'paragraph' | 'insert_point', query: string): {
1398
+ type: string;
1399
+ sectionIndex: number;
1400
+ elementIndex?: number;
1401
+ tableIndex?: number;
1402
+ paragraphIndex?: number;
1403
+ foundIn?: 'paragraph' | 'table_cell';
1404
+ tableInfo?: {
1405
+ tableIndex: number;
1406
+ row: number;
1407
+ col: number;
1408
+ };
1409
+ } | null;
1410
+ /**
1411
+ * 테이블 조회 통합 도구
1412
+ * @param options 조회 옵션
1413
+ * @returns 테이블 정보
1414
+ */
1415
+ queryTable(options: {
1416
+ mode: 'list' | 'full' | 'cell' | 'map' | 'summary';
1417
+ tableIndex?: number;
1418
+ row?: number;
1419
+ col?: number;
1420
+ sectionIndex?: number;
1421
+ }): {
1422
+ tables?: Array<{
1423
+ index: number;
1424
+ rowCount: number;
1425
+ colCount: number;
1426
+ sectionIndex: number;
1427
+ }>;
1428
+ table?: HwpxTable | null;
1429
+ cell?: {
1430
+ text: string;
1431
+ paragraphs: HwpxParagraph[];
1432
+ } | null;
1433
+ map?: Array<{
1434
+ index: number;
1435
+ header: string;
1436
+ sectionIndex: number;
1437
+ elementIndex: number;
1438
+ firstRowPreview: string[];
1439
+ }>;
1440
+ };
1441
+ /**
1442
+ * 내용 수정 통합 도구
1443
+ * @param options 수정 옵션
1444
+ * @returns 성공 여부
1445
+ */
1446
+ modifyContent(options: {
1447
+ type: 'cell' | 'replace' | 'paragraph';
1448
+ tableIndex?: number;
1449
+ row?: number;
1450
+ col?: number;
1451
+ sectionIndex?: number;
1452
+ paragraphIndex?: number;
1453
+ runIndex?: number;
1454
+ text?: string;
1455
+ oldText?: string;
1456
+ newText?: string;
1457
+ replaceAll?: boolean;
1458
+ caseSensitive?: boolean;
1459
+ }): boolean;
1460
+ /**
1461
+ * 스타일 적용 통합 도구
1462
+ * 기존 applyStyle(sectionIndex, paragraphIndex, styleId)과 구분하기 위해 이름 변경
1463
+ * @param options 스타일 옵션
1464
+ * @returns 성공 여부
1465
+ */
1466
+ applyConsolidatedStyle(options: {
1467
+ target: 'paragraph' | 'table_cell' | 'text';
1468
+ sectionIndex?: number;
1469
+ paragraphIndex?: number;
1470
+ tableIndex?: number;
1471
+ row?: number;
1472
+ col?: number;
1473
+ runIndex?: number;
1474
+ style: {
1475
+ hangingIndent?: number;
1476
+ align?: 'left' | 'center' | 'right' | 'justify';
1477
+ lineSpacing?: number;
1478
+ bold?: boolean;
1479
+ italic?: boolean;
1480
+ fontSize?: number;
1481
+ fontColor?: string;
1482
+ };
1483
+ }): boolean;
1484
+ private _positionIndex;
1485
+ private _documentChunks;
1486
+ private _lastChunkTime;
1487
+ /**
1488
+ * Chunk document into overlapping segments for agentic reading
1489
+ * @param chunkSize Target chunk size in characters (default 500)
1490
+ * @param overlap Overlap between chunks in characters (default 100)
1491
+ * @returns Array of document chunks with position information
1492
+ */
1493
+ chunkDocument(chunkSize?: number, overlap?: number): DocumentChunk[];
1494
+ /**
1495
+ * Search chunks using keyword-based similarity scoring
1496
+ * Returns chunks ranked by relevance to the query
1497
+ * @param query Search query
1498
+ * @param topK Number of top results to return (default 5)
1499
+ * @param minScore Minimum similarity score threshold (default 0.1)
1500
+ */
1501
+ searchChunks(query: string, topK?: number, minScore?: number): Array<{
1502
+ chunk: DocumentChunk;
1503
+ score: number;
1504
+ matchedTerms: string[];
1505
+ snippet: string;
1506
+ }>;
1507
+ /**
1508
+ * Simple tokenizer for Korean and English text
1509
+ */
1510
+ private tokenize;
1511
+ /**
1512
+ * Extract table of contents based on formatting rules
1513
+ * Identifies headings by:
1514
+ * - Numbered patterns (1., 가., (1), ①)
1515
+ * - Short paragraphs followed by longer content
1516
+ * - Bold or larger font (if style info available)
1517
+ */
1518
+ extractToc(): Array<{
1519
+ level: number;
1520
+ title: string;
1521
+ sectionIndex: number;
1522
+ elementIndex: number;
1523
+ offset: number;
1524
+ children?: Array<{
1525
+ level: number;
1526
+ title: string;
1527
+ sectionIndex: number;
1528
+ elementIndex: number;
1529
+ offset: number;
1530
+ }>;
1531
+ }>;
1532
+ /**
1533
+ * Build hierarchical TOC structure from flat list
1534
+ */
1535
+ private buildTocHierarchy;
1536
+ /**
1537
+ * Build and store position index for quick lookup
1538
+ * Call this after document modifications to keep index updated
1539
+ */
1540
+ buildPositionIndex(): PositionIndexEntry[];
1541
+ /**
1542
+ * Get cached position index or build if needed
1543
+ */
1544
+ getPositionIndex(): PositionIndexEntry[];
1545
+ /**
1546
+ * Search position index by text query
1547
+ */
1548
+ searchPositionIndex(query: string, type?: 'heading' | 'paragraph' | 'table'): PositionIndexEntry[];
1549
+ /**
1550
+ * Get chunk at specific offset
1551
+ */
1552
+ getChunkAtOffset(offset: number): DocumentChunk | null;
1553
+ /**
1554
+ * Get surrounding chunks (context window)
1555
+ * @param chunkId ID of the center chunk
1556
+ * @param before Number of chunks before
1557
+ * @param after Number of chunks after
1558
+ */
1559
+ getChunkContext(chunkId: string, before?: number, after?: number): {
1560
+ chunks: DocumentChunk[];
1561
+ centerIndex: number;
1562
+ };
1563
+ /**
1564
+ * Clear cached chunks and position index
1565
+ * Call this after document modifications
1566
+ */
1567
+ invalidateReadingCache(): void;
1568
+ /**
1569
+ * Apply paragraph style changes (alignment, etc.) to XML files.
1570
+ * Creates new paraPr elements in header.xml and updates paragraph references in section XML.
1571
+ */
1572
+ private applyParagraphStylesToXml;
1573
+ /**
1574
+ * Apply character style changes (font, size, bold, italic) to XML files.
1575
+ * Creates new charPr elements in header.xml and updates run references in section XML.
1576
+ */
1577
+ private applyCharacterStylesToXml;
1578
+ /**
1579
+ * Apply hanging indent changes to XML files.
1580
+ * This adds new paraPr elements to header.xml and updates paragraph references in section XML.
1581
+ */
1582
+ private applyHangingIndentsToXml;
1583
+ /**
1584
+ * Apply table cell hanging indent changes to XML files.
1585
+ * This adds new paraPr elements to header.xml and updates paragraph references in table cells.
1586
+ */
1587
+ private applyTableCellHangingIndentsToXml;
1588
+ /**
1589
+ * Find a specific cell in table XML by row and column index.
1590
+ * Uses balanced bracket matching to correctly handle nested tables.
1591
+ * @returns Cell XML content and its position, or null if not found
1592
+ */
1593
+ private findTableCellInXml;
1594
+ private applyTableRowInsertsToXml;
1595
+ private applyTableRowDeletesToXml;
1596
+ private applyTableColumnInsertsToXml;
1597
+ private applyTableColumnDeletesToXml;
1598
+ /**
1599
+ * Find top-level paragraph/table XML elements in section XML with full content.
1600
+ * Returns elements with their complete XML (including closing tags).
1601
+ */
1602
+ private findTopLevelFullElements;
1603
+ private applyParagraphCopiesToXml;
1604
+ private applyParagraphMovesToXml;
1605
+ private applyHeaderFooterUpdatesToXml;
1606
+ }
1607
+ export {};