hwp2md 1.0.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 ADDED
@@ -0,0 +1,1200 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let cfb = require("cfb");
29
+ cfb = __toESM(cfb);
30
+ let pako = require("pako");
31
+ let fflate = require("fflate");
32
+ let fast_xml_parser = require("fast-xml-parser");
33
+
34
+ //#region src/utils/compression.ts
35
+ /**
36
+ * Decompress raw deflate data (no zlib header)
37
+ * Equivalent to Python: zlib.decompress(data, -15)
38
+ *
39
+ * HWP files use raw deflate compression without zlib wrapper headers.
40
+ * The windowBits=-15 in Python indicates raw deflate mode.
41
+ *
42
+ * @param data - Compressed data
43
+ * @returns Decompressed data
44
+ */
45
+ function decompressRaw(data) {
46
+ try {
47
+ return (0, pako.inflateRaw)(data);
48
+ } catch (error) {
49
+ throw new Error(`Failed to decompress data: ${error.message}`);
50
+ }
51
+ }
52
+
53
+ //#endregion
54
+ //#region src/parser.ts
55
+ /**
56
+ * HWP 5.0 File Parser
57
+ * Parses OLE Compound File format HWP files
58
+ */
59
+ /**
60
+ * HWP File Parser
61
+ * Reads and parses HWP 5.0 files using OLE Compound File format
62
+ */
63
+ var HWPFile = class HWPFile {
64
+ cfb = null;
65
+ _fileHeader = null;
66
+ _isCompressed = false;
67
+ /**
68
+ * Create HWPFile from raw data
69
+ * @param data - Raw HWP file data
70
+ */
71
+ constructor(data) {
72
+ this.data = data;
73
+ }
74
+ /**
75
+ * Create HWPFile from file path (Node.js only)
76
+ * @param path - Path to HWP file
77
+ */
78
+ static async fromFile(path) {
79
+ const data = await (await import("node:fs/promises")).readFile(path);
80
+ return new HWPFile(new Uint8Array(data));
81
+ }
82
+ /**
83
+ * Create HWPFile from ArrayBuffer
84
+ * @param data - ArrayBuffer data
85
+ */
86
+ static fromArrayBuffer(data) {
87
+ return new HWPFile(new Uint8Array(data));
88
+ }
89
+ /**
90
+ * Create HWPFile from Uint8Array
91
+ * @param data - Uint8Array data
92
+ */
93
+ static fromUint8Array(data) {
94
+ return new HWPFile(data);
95
+ }
96
+ /**
97
+ * Open and parse HWP file
98
+ */
99
+ open() {
100
+ const uint8Array = this.data instanceof Uint8Array ? this.data : new Uint8Array(this.data);
101
+ if (uint8Array.length >= 30) {
102
+ if (new TextDecoder("utf-8").decode(uint8Array.slice(0, 30)).replace(/\0+$/, "").startsWith("HWP Document File V3")) throw new Error("HWP 3.0 format (HWP 97, 2002, etc.) is not supported. Please use HWP 5.0 or later format. You can convert HWP 3.0 files to HWP 5.0 format using Hancom Office.");
103
+ }
104
+ try {
105
+ if (typeof Buffer !== "undefined") {
106
+ const buffer = Buffer.from(uint8Array);
107
+ this.cfb = cfb.read(buffer, { type: "buffer" });
108
+ } else {
109
+ const buffer = Array.from(uint8Array);
110
+ this.cfb = cfb.read(buffer, { type: "array" });
111
+ }
112
+ } catch (error) {
113
+ throw new Error(`Failed to parse HWP file: ${error.message}`);
114
+ }
115
+ this._fileHeader = this.parseFileHeader();
116
+ this._isCompressed = this._fileHeader.isCompressed;
117
+ }
118
+ /**
119
+ * Close HWP file and release resources
120
+ */
121
+ close() {
122
+ this.cfb = null;
123
+ this._fileHeader = null;
124
+ }
125
+ /**
126
+ * Get file header information
127
+ */
128
+ get fileHeader() {
129
+ return this._fileHeader;
130
+ }
131
+ /**
132
+ * Check if file is compressed
133
+ */
134
+ get isCompressed() {
135
+ return this._isCompressed;
136
+ }
137
+ /**
138
+ * Parse FileHeader stream (256 bytes fixed)
139
+ */
140
+ parseFileHeader() {
141
+ if (!this.cfb) throw new Error("HWP file not opened");
142
+ const entry = cfb.find(this.cfb, "FileHeader");
143
+ if (!entry) throw new Error("FileHeader not found in HWP file");
144
+ const data = entry.content;
145
+ if (data.length !== 256) throw new Error(`Invalid FileHeader size: ${data.length} (expected 256)`);
146
+ const signatureBytes = data.slice(0, 32);
147
+ const signature = new TextDecoder("utf-8").decode(signatureBytes).replace(/\0+$/, "");
148
+ if (!signature.startsWith("HWP Document File")) throw new Error(`Invalid HWP signature: ${signature}`);
149
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
150
+ const versionRaw = view.getUint32(32, true);
151
+ const major = versionRaw >> 24 & 255;
152
+ const minor = versionRaw >> 16 & 255;
153
+ const patch = versionRaw >> 8 & 255;
154
+ const rev = versionRaw & 255;
155
+ const properties = view.getUint32(36, true);
156
+ const isCompressed = Boolean(properties & 1);
157
+ const isEncrypted = Boolean(properties & 2);
158
+ return {
159
+ signature,
160
+ version: `${major}.${minor}.${patch}.${rev}`,
161
+ isCompressed,
162
+ isEncrypted,
163
+ rawProperties: properties
164
+ };
165
+ }
166
+ /**
167
+ * Read and decompress stream
168
+ * @param streamPath - Stream path (e.g., 'DocInfo', 'BodyText/Section0')
169
+ * @returns Decompressed data or null if stream doesn't exist
170
+ */
171
+ readStream(streamPath) {
172
+ if (!this.cfb) return null;
173
+ const entry = cfb.find(this.cfb, streamPath);
174
+ if (!entry) return null;
175
+ let data = entry.content;
176
+ if (this._isCompressed) try {
177
+ data = decompressRaw(data);
178
+ } catch (error) {
179
+ throw new Error(`Failed to decompress ${streamPath}: ${error.message}`);
180
+ }
181
+ return data;
182
+ }
183
+ /**
184
+ * List all streams in HWP file
185
+ * @returns Array of stream paths
186
+ */
187
+ listStreams() {
188
+ if (!this.cfb) return [];
189
+ const streams = [];
190
+ for (const entry of this.cfb.FileIndex) if (entry.type === 2) {
191
+ const path = entry.name.split("/").filter((p) => p);
192
+ if (path.length > 0) streams.push(path);
193
+ }
194
+ return streams;
195
+ }
196
+ /**
197
+ * Get file information
198
+ */
199
+ getFileInfo() {
200
+ if (!this._fileHeader) return {};
201
+ return {
202
+ signature: this._fileHeader.signature,
203
+ version: this._fileHeader.version,
204
+ compressed: this._isCompressed,
205
+ encrypted: this._fileHeader.isEncrypted,
206
+ streams: this.listStreams()
207
+ };
208
+ }
209
+ /**
210
+ * Get number of sections in BodyText
211
+ */
212
+ getSectionCount() {
213
+ if (!this.cfb) return 0;
214
+ let count = 0;
215
+ while (cfb.find(this.cfb, `BodyText/Section${count}`) || cfb.find(this.cfb, `Section${count}`)) count++;
216
+ return count;
217
+ }
218
+ /**
219
+ * Read section data
220
+ * @param sectionIndex - Section index (0-based)
221
+ * @returns Decompressed section data
222
+ */
223
+ readSection(sectionIndex) {
224
+ let data = this.readStream(`BodyText/Section${sectionIndex}`);
225
+ if (!data) data = this.readStream(`Section${sectionIndex}`);
226
+ return data;
227
+ }
228
+ };
229
+
230
+ //#endregion
231
+ //#region src/record.ts
232
+ const HWPTAG_BEGIN = 16;
233
+ const HWPTAG_DOCUMENT_PROPERTIES = 16;
234
+ const HWPTAG_ID_MAPPINGS = 17;
235
+ const HWPTAG_BIN_DATA = 18;
236
+ const HWPTAG_FACE_NAME = 19;
237
+ const HWPTAG_BORDER_FILL = 20;
238
+ const HWPTAG_CHAR_SHAPE = 21;
239
+ const HWPTAG_TAB_DEF = 22;
240
+ const HWPTAG_NUMBERING = 23;
241
+ const HWPTAG_BULLET = 24;
242
+ const HWPTAG_PARA_SHAPE = 25;
243
+ const HWPTAG_STYLE = 26;
244
+ const HWPTAG_PARA_HEADER = HWPTAG_BEGIN + 50;
245
+ const HWPTAG_PARA_TEXT = HWPTAG_BEGIN + 51;
246
+ const HWPTAG_PARA_CHAR_SHAPE = HWPTAG_BEGIN + 52;
247
+ const HWPTAG_PARA_LINE_SEG = HWPTAG_BEGIN + 53;
248
+ const HWPTAG_PARA_RANGE_TAG = HWPTAG_BEGIN + 54;
249
+ const HWPTAG_CTRL_HEADER = HWPTAG_BEGIN + 55;
250
+ const HWPTAG_LIST_HEADER = HWPTAG_BEGIN + 56;
251
+ const HWPTAG_PAGE_DEF = HWPTAG_BEGIN + 57;
252
+ const HWPTAG_FOOTNOTE_SHAPE = HWPTAG_BEGIN + 58;
253
+ const HWPTAG_PAGE_BORDER_FILL = HWPTAG_BEGIN + 59;
254
+ const HWPTAG_SHAPE_COMPONENT = HWPTAG_BEGIN + 60;
255
+ const HWPTAG_TABLE = HWPTAG_BEGIN + 61;
256
+ const HWPTAG_SHAPE_COMPONENT_LINE = HWPTAG_BEGIN + 62;
257
+ const HWPTAG_CTRL_DATA = HWPTAG_BEGIN + 71;
258
+ /**
259
+ * HWP Record Reader
260
+ * Reads binary records from HWP stream data
261
+ */
262
+ var RecordReader = class {
263
+ data;
264
+ offset = 0;
265
+ constructor(data) {
266
+ this.data = data;
267
+ }
268
+ /**
269
+ * Check if there are more records to read
270
+ */
271
+ hasMore() {
272
+ return this.offset < this.data.length;
273
+ }
274
+ /**
275
+ * Read next record
276
+ * @returns Next record, or null if no more records
277
+ */
278
+ readRecord() {
279
+ if (this.offset + 4 > this.data.length) return null;
280
+ const header = new DataView(this.data.buffer, this.data.byteOffset + this.offset, 4).getUint32(0, true);
281
+ const tagId = header & 1023;
282
+ const level = header >> 10 & 1023;
283
+ let size = header >> 20 & 4095;
284
+ let dataOffset = this.offset + 4;
285
+ if (size === 4095) {
286
+ if (dataOffset + 4 > this.data.length) return null;
287
+ size = new DataView(this.data.buffer, this.data.byteOffset + dataOffset, 4).getUint32(0, true);
288
+ dataOffset += 4;
289
+ }
290
+ if (dataOffset + size > this.data.length) return null;
291
+ const recordData = this.data.slice(dataOffset, dataOffset + size);
292
+ this.offset = dataOffset + size;
293
+ return {
294
+ tagId,
295
+ level,
296
+ data: recordData,
297
+ size
298
+ };
299
+ }
300
+ /**
301
+ * Peek at next record header without consuming it
302
+ * @returns Header info with tagId, level, size
303
+ */
304
+ peekRecordHeader() {
305
+ if (this.offset + 4 > this.data.length) return null;
306
+ const header = new DataView(this.data.buffer, this.data.byteOffset + this.offset, 4).getUint32(0, true);
307
+ const tagId = header & 1023;
308
+ const level = header >> 10 & 1023;
309
+ let size = header >> 20 & 4095;
310
+ let dataOffset = this.offset + 4;
311
+ if (size === 4095) {
312
+ if (dataOffset + 4 > this.data.length) return null;
313
+ size = new DataView(this.data.buffer, this.data.byteOffset + dataOffset, 4).getUint32(0, true);
314
+ }
315
+ return {
316
+ tagId,
317
+ level,
318
+ size
319
+ };
320
+ }
321
+ /**
322
+ * Read all records (considering hierarchy)
323
+ * @param parentLevel - Stop when reaching this level or below
324
+ * @returns All records at current level
325
+ */
326
+ readAllRecords(parentLevel) {
327
+ const records = [];
328
+ while (this.hasMore()) {
329
+ const header = this.peekRecordHeader();
330
+ if (!header) break;
331
+ if (parentLevel !== void 0 && header.level <= parentLevel) break;
332
+ const record = this.readRecord();
333
+ if (record) records.push(record);
334
+ }
335
+ return records;
336
+ }
337
+ /**
338
+ * Get current position
339
+ */
340
+ get position() {
341
+ return this.offset;
342
+ }
343
+ /**
344
+ * Get remaining bytes
345
+ */
346
+ get remaining() {
347
+ return this.data.length - this.offset;
348
+ }
349
+ };
350
+
351
+ //#endregion
352
+ //#region src/table.ts
353
+ /**
354
+ * HWP Table Parser
355
+ * Parses table records and converts to Markdown
356
+ */
357
+ /**
358
+ * Parse table properties from TABLE record data
359
+ * @param data - TABLE record data
360
+ * @returns Table properties with rows, cols
361
+ */
362
+ function parseTableProperties(data) {
363
+ if (data.length < 8) throw new Error(`Invalid TABLE record size: ${data.length}`);
364
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
365
+ return {
366
+ properties: view.getUint32(0, true),
367
+ rows: view.getUint16(4, true),
368
+ cols: view.getUint16(6, true)
369
+ };
370
+ }
371
+ /**
372
+ * Parse cell properties
373
+ * @param data - Cell property data
374
+ * @returns Cell properties
375
+ */
376
+ function parseCellProperties(data) {
377
+ if (data.length < 8) return {
378
+ col: 0,
379
+ row: 0,
380
+ colspan: 1,
381
+ rowspan: 1
382
+ };
383
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
384
+ return {
385
+ col: view.getUint16(0, true),
386
+ row: view.getUint16(2, true),
387
+ colspan: view.getUint16(4, true),
388
+ rowspan: view.getUint16(6, true)
389
+ };
390
+ }
391
+ /**
392
+ * Parse table from TABLE record and subsequent records
393
+ * @param tableRecordData - TABLE record data
394
+ * @param reader - Record reader for reading cell data
395
+ * @param lineBreakStyle - How to handle line breaks in cells
396
+ * @returns Parsed table
397
+ */
398
+ function parseTable(tableRecordData, reader, lineBreakStyle = "space") {
399
+ const props = parseTableProperties(tableRecordData);
400
+ const rows = props.rows;
401
+ const cols = props.cols;
402
+ const cells = [];
403
+ let cellIndex = 0;
404
+ while (reader.hasMore() && cellIndex < rows * cols) {
405
+ const header = reader.peekRecordHeader();
406
+ if (!header) break;
407
+ if (header.level < 2) break;
408
+ if (header.tagId === HWPTAG_LIST_HEADER) {
409
+ const listRecord = reader.readRecord();
410
+ if (!listRecord) break;
411
+ let row, col, colspan, rowspan;
412
+ if (listRecord.data.length >= 16) {
413
+ const view = new DataView(listRecord.data.buffer, listRecord.data.byteOffset, listRecord.data.byteLength);
414
+ col = view.getUint16(8, true);
415
+ row = view.getUint16(10, true);
416
+ colspan = view.getUint16(12, true);
417
+ rowspan = view.getUint16(14, true);
418
+ if (colspan === 0) colspan = 1;
419
+ if (rowspan === 0) rowspan = 1;
420
+ } else {
421
+ row = Math.floor(cellIndex / cols);
422
+ col = cellIndex % cols;
423
+ colspan = 1;
424
+ rowspan = 1;
425
+ }
426
+ const textParts = [];
427
+ while (reader.hasMore()) {
428
+ const paraHeader = reader.peekRecordHeader();
429
+ if (!paraHeader) break;
430
+ if (paraHeader.tagId === HWPTAG_LIST_HEADER) break;
431
+ if (paraHeader.level < 2) break;
432
+ if (paraHeader.tagId === HWPTAG_PARA_HEADER) {
433
+ const paraRec = reader.readRecord();
434
+ if (!paraRec) break;
435
+ if (paraRec.data.length >= 4) {
436
+ const nchars = new DataView(paraRec.data.buffer, paraRec.data.byteOffset, paraRec.data.byteLength).getUint32(0, true) & 2147483647;
437
+ if (nchars > 0 && reader.hasMore()) {
438
+ const nextH = reader.peekRecordHeader();
439
+ if (nextH && nextH.tagId === HWPTAG_PARA_TEXT) {
440
+ const textRec = reader.readRecord();
441
+ if (textRec) {
442
+ const text = processControlChars(parseParaText(textRec.data, nchars)).text;
443
+ if (text.trim()) textParts.push(text.trim());
444
+ }
445
+ }
446
+ }
447
+ }
448
+ } else reader.readRecord();
449
+ }
450
+ let cellText;
451
+ if (lineBreakStyle === "br") {
452
+ cellText = textParts.join("<br>");
453
+ cellText = cellText.replace(/\n/g, "<br>");
454
+ } else {
455
+ cellText = textParts.join(" ");
456
+ cellText = cellText.replace(/\n/g, " ");
457
+ }
458
+ const cell = {
459
+ row,
460
+ col,
461
+ rowspan,
462
+ colspan,
463
+ text: cellText
464
+ };
465
+ cells.push(cell);
466
+ cellIndex++;
467
+ } else reader.readRecord();
468
+ }
469
+ return {
470
+ rows,
471
+ cols,
472
+ cells
473
+ };
474
+ }
475
+ /**
476
+ * Convert table to Markdown
477
+ * @param table - Table object
478
+ * @param mergeStrategy - 'repeat' (default) or 'blank'
479
+ * @returns Markdown table
480
+ */
481
+ function tableToMarkdown(table, mergeStrategy = "repeat") {
482
+ const { rows, cols, cells } = table;
483
+ const matrix = Array.from({ length: rows }, () => Array(cols).fill(""));
484
+ for (const cell of cells) if (mergeStrategy === "repeat") {
485
+ for (let r = cell.row; r < cell.row + cell.rowspan; r++) for (let c = cell.col; c < cell.col + cell.colspan; c++) if (r < rows && c < cols) matrix[r][c] = cell.text;
486
+ } else if (cell.row < rows && cell.col < cols) matrix[cell.row][cell.col] = cell.text;
487
+ const lines = [];
488
+ if (rows === 0 || cols === 0) return "[Empty Table]";
489
+ lines.push("| " + Array(cols).fill("").join(" | ") + " |");
490
+ lines.push("| " + Array(cols).fill("---").join(" | ") + " |");
491
+ for (const row of matrix) lines.push("| " + row.join(" | ") + " |");
492
+ return lines.join("\n");
493
+ }
494
+
495
+ //#endregion
496
+ //#region src/paragraph.ts
497
+ /**
498
+ * HWP Paragraph Parser
499
+ * Parses paragraph records and extracts text
500
+ */
501
+ /**
502
+ * Parse paragraph header
503
+ * @param data - PARA_HEADER record data
504
+ * @returns Paragraph header information
505
+ */
506
+ function parseParaHeader(data) {
507
+ if (data.length < 22) throw new Error(`Invalid PARA_HEADER size: ${data.length}`);
508
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
509
+ return {
510
+ textCount: view.getUint32(0, true) & 2147483647,
511
+ controlMask: view.getUint32(4, true),
512
+ paraShapeId: view.getUint16(8, true),
513
+ styleId: view.getUint8(10),
514
+ columnType: view.getUint8(11),
515
+ charShapeCount: view.getUint16(12, true)
516
+ };
517
+ }
518
+ /**
519
+ * Parse paragraph text with control info table
520
+ *
521
+ * PARA_TEXT structure:
522
+ * [Control info table: 16 bytes per control] + [Actual text]
523
+ *
524
+ * Each control info block (16 bytes):
525
+ * - 2 bytes: control code
526
+ * - 4 bytes: control ID
527
+ * - 8 bytes: control data
528
+ * - 2 bytes: control code (repeated)
529
+ *
530
+ * @param data - PARA_TEXT record data
531
+ * @param nchars - Number of WCHAR characters (control table + text)
532
+ * @returns Decoded text
533
+ */
534
+ function parseParaText(data, _nchars) {
535
+ const CHAR_CONTROLS = new Set([
536
+ 0,
537
+ 10,
538
+ 13,
539
+ 30,
540
+ 31
541
+ ]);
542
+ let offset = 0;
543
+ while (offset + 2 <= data.length) {
544
+ const charCode = new DataView(data.buffer, data.byteOffset + offset, 2).getUint16(0, true);
545
+ if (charCode < 32 && !CHAR_CONTROLS.has(charCode)) {
546
+ offset += 16;
547
+ if (offset > data.length) break;
548
+ } else break;
549
+ }
550
+ const textData = data.slice(offset);
551
+ if (textData.length === 0) return "";
552
+ try {
553
+ return new TextDecoder("utf-16le").decode(textData);
554
+ } catch (error) {
555
+ return "";
556
+ }
557
+ }
558
+ /**
559
+ * Process control characters in text
560
+ * @param text - Raw text with control characters
561
+ * @returns Processed text and has_table flag
562
+ */
563
+ function processControlChars(text) {
564
+ const result = [];
565
+ let hasTable = false;
566
+ for (const char of text) {
567
+ const code = char.codePointAt(0) ?? 0;
568
+ if (code < 32) {
569
+ if (code === 10) result.push("\n");
570
+ else if (code === 11) hasTable = true;
571
+ else if (code === 13) result.push("\n");
572
+ } else result.push(char);
573
+ }
574
+ return {
575
+ text: result.join(""),
576
+ hasTable
577
+ };
578
+ }
579
+ /**
580
+ * Paragraph Parser
581
+ * Parses paragraphs from HWP record stream
582
+ */
583
+ var ParagraphParser = class {
584
+ constructor(reader, options = {}) {
585
+ this.reader = reader;
586
+ this.options = options;
587
+ if (!this.options.tableLineBreakStyle) this.options.tableLineBreakStyle = "space";
588
+ }
589
+ /**
590
+ * Parse next paragraph
591
+ * @returns Parsed paragraph, or null if no more paragraphs
592
+ */
593
+ parseParagraph() {
594
+ if (!this.reader.hasMore()) return null;
595
+ let record = null;
596
+ while (this.reader.hasMore()) {
597
+ record = this.reader.readRecord();
598
+ if (record && record.tagId === HWPTAG_PARA_HEADER) break;
599
+ record = null;
600
+ }
601
+ if (!record) return null;
602
+ const header = parseParaHeader(record.data);
603
+ let text = "";
604
+ if (header.textCount > 0) {
605
+ const nextHeader = this.reader.peekRecordHeader();
606
+ if (nextHeader && nextHeader.tagId === HWPTAG_PARA_TEXT) {
607
+ record = this.reader.readRecord();
608
+ if (record) text = processControlChars(parseParaText(record.data, header.textCount)).text;
609
+ }
610
+ }
611
+ const tables = [];
612
+ while (this.reader.hasMore()) {
613
+ const nextHeader = this.reader.peekRecordHeader();
614
+ if (!nextHeader) break;
615
+ if (nextHeader.tagId === HWPTAG_PARA_HEADER) break;
616
+ if (nextHeader.level === 0) break;
617
+ if (nextHeader.tagId === HWPTAG_CTRL_HEADER) {
618
+ this.reader.readRecord();
619
+ const tableHeader = this.reader.peekRecordHeader();
620
+ if (tableHeader && tableHeader.tagId === HWPTAG_TABLE) {
621
+ const tableRecord = this.reader.readRecord();
622
+ if (tableRecord) try {
623
+ const tableMd = tableToMarkdown(parseTable(tableRecord.data, this.reader, this.options.tableLineBreakStyle));
624
+ tables.push(tableMd);
625
+ } catch (error) {
626
+ console.warn("Warning: Failed to parse table:", error);
627
+ tables.push("[TABLE - Parse Error]");
628
+ }
629
+ }
630
+ } else this.reader.readRecord();
631
+ }
632
+ if (tables.length > 0) {
633
+ text = text.trimEnd();
634
+ if (text.length > 0 && text.length < 5 && [...text].every((c) => {
635
+ return (c.codePointAt(0) ?? 0) > 127 || /\s/.test(c);
636
+ })) text = tables.join("\n\n");
637
+ else {
638
+ if (text) text += "\n\n";
639
+ text += tables.join("\n\n");
640
+ }
641
+ }
642
+ return {
643
+ text,
644
+ header
645
+ };
646
+ }
647
+ /**
648
+ * Parse all paragraphs in section
649
+ * @returns All paragraphs
650
+ */
651
+ parseAllParagraphs() {
652
+ const paragraphs = [];
653
+ while (this.reader.hasMore()) {
654
+ const para = this.parseParagraph();
655
+ if (para) paragraphs.push(para);
656
+ else break;
657
+ }
658
+ return paragraphs;
659
+ }
660
+ };
661
+
662
+ //#endregion
663
+ //#region src/hwpx_parser.ts
664
+ /**
665
+ * HWPX (ZIP+XML) File Parser
666
+ * Parses HWPX files (ZIP archives containing XML) into Paragraphs and Tables
667
+ */
668
+ const HWPX_MIMETYPE = "application/hwp+zip";
669
+ /** Options for the XML parser */
670
+ const xmlParserOptions = {
671
+ ignoreAttributes: false,
672
+ attributeNamePrefix: "@_",
673
+ removeNSPrefix: false,
674
+ isArray: (name) => {
675
+ return [
676
+ "hp:p",
677
+ "hp:run",
678
+ "hp:t",
679
+ "hp:tr",
680
+ "hp:tc",
681
+ "hp:tbl",
682
+ "opf:item",
683
+ "opf:itemref"
684
+ ].includes(name);
685
+ },
686
+ textNodeName: "#text"
687
+ };
688
+ /**
689
+ * HWPX file parser
690
+ */
691
+ var HWPXFile = class HWPXFile {
692
+ data;
693
+ entries = null;
694
+ sectionPaths = [];
695
+ version = "";
696
+ xmlParser;
697
+ constructor(data) {
698
+ this.data = data;
699
+ this.xmlParser = new fast_xml_parser.XMLParser(xmlParserOptions);
700
+ }
701
+ static async fromFile(path) {
702
+ const { readFileSync } = await import("node:fs");
703
+ const data = readFileSync(path);
704
+ return new HWPXFile(new Uint8Array(data));
705
+ }
706
+ static fromArrayBuffer(data) {
707
+ return new HWPXFile(new Uint8Array(data));
708
+ }
709
+ static fromUint8Array(data) {
710
+ return new HWPXFile(data);
711
+ }
712
+ open() {
713
+ if (this.data.length < 4 || this.data[0] !== 80 || this.data[1] !== 75 || this.data[2] !== 3 || this.data[3] !== 4) throw new Error("Not a valid HWPX file (not a ZIP archive)");
714
+ try {
715
+ this.entries = (0, fflate.unzipSync)(this.data);
716
+ } catch (e) {
717
+ throw new Error(`Failed to parse HWPX file: ${e instanceof Error ? e.message : e}`);
718
+ }
719
+ const mimetypeEntry = this.entries["mimetype"];
720
+ if (!mimetypeEntry) throw new Error("Not a valid HWPX file: missing 'mimetype' entry");
721
+ const mimetype = new TextDecoder().decode(mimetypeEntry).trim();
722
+ if (mimetype !== HWPX_MIMETYPE) throw new Error(`Invalid HWPX mimetype: '${mimetype}' (expected '${HWPX_MIMETYPE}')`);
723
+ this.parseVersion();
724
+ this.discoverSections();
725
+ }
726
+ close() {
727
+ this.entries = null;
728
+ this.sectionPaths = [];
729
+ }
730
+ parseVersion() {
731
+ const versionEntry = this.entries?.["version.xml"];
732
+ if (!versionEntry) {
733
+ this.version = "unknown";
734
+ return;
735
+ }
736
+ try {
737
+ const xml = new TextDecoder().decode(versionEntry);
738
+ const parsed = this.xmlParser.parse(xml);
739
+ const root = parsed["hv:HCFVersion"] ?? parsed["HCFVersion"] ?? parsed;
740
+ const major = root["@_major"] ?? "";
741
+ const minor = root["@_minor"] ?? "";
742
+ const micro = root["@_micro"] ?? "";
743
+ if (major) this.version = `${major}.${minor}.${micro}`;
744
+ else this.version = root["@_Version"] ?? "unknown";
745
+ } catch {
746
+ this.version = "unknown";
747
+ }
748
+ }
749
+ discoverSections() {
750
+ this.sectionPaths = [];
751
+ const hpfEntry = this.entries?.["Contents/content.hpf"];
752
+ if (!hpfEntry) {
753
+ for (const name of Object.keys(this.entries ?? {})) if (name.startsWith("Contents/section") && name.endsWith(".xml")) this.sectionPaths.push(name);
754
+ this.sectionPaths.sort();
755
+ return;
756
+ }
757
+ try {
758
+ const xml = new TextDecoder().decode(hpfEntry);
759
+ const parsed = this.xmlParser.parse(xml);
760
+ const pkg = parsed["opf:package"] ?? parsed["package"] ?? {};
761
+ const manifest = pkg["opf:manifest"] ?? pkg["manifest"] ?? {};
762
+ const spine = pkg["opf:spine"] ?? pkg["spine"] ?? {};
763
+ const idToHref = {};
764
+ const items = manifest["opf:item"] ?? manifest["item"] ?? [];
765
+ const itemList = Array.isArray(items) ? items : [items];
766
+ for (const item of itemList) {
767
+ const id = item["@_id"] ?? "";
768
+ const href = item["@_href"] ?? "";
769
+ if (href) idToHref[id] = href;
770
+ }
771
+ const itemrefs = spine["opf:itemref"] ?? spine["itemref"] ?? [];
772
+ const refList = Array.isArray(itemrefs) ? itemrefs : [itemrefs];
773
+ for (const ref of refList) {
774
+ const idref = ref["@_idref"] ?? "";
775
+ if (idref in idToHref && idref.toLowerCase().includes("section")) {
776
+ let href = idToHref[idref];
777
+ if (!href.startsWith("Contents/")) href = "Contents/" + href;
778
+ this.sectionPaths.push(href);
779
+ }
780
+ }
781
+ if (this.sectionPaths.length === 0) {
782
+ for (const [, href] of Object.entries(idToHref).sort()) if (href.toLowerCase().includes("section")) {
783
+ const fullHref = href.startsWith("Contents/") ? href : "Contents/" + href;
784
+ this.sectionPaths.push(fullHref);
785
+ }
786
+ }
787
+ } catch {
788
+ for (const name of Object.keys(this.entries ?? {})) if (name.startsWith("Contents/section") && name.endsWith(".xml")) this.sectionPaths.push(name);
789
+ this.sectionPaths.sort();
790
+ }
791
+ }
792
+ get fileInfo() {
793
+ this.ensureOpen();
794
+ return {
795
+ format: "HWPX",
796
+ version: this.version,
797
+ sectionCount: this.sectionPaths.length,
798
+ contents: Object.keys(this.entries ?? {})
799
+ };
800
+ }
801
+ getSectionCount() {
802
+ this.ensureOpen();
803
+ return this.sectionPaths.length;
804
+ }
805
+ getSectionXml(index) {
806
+ this.ensureOpen();
807
+ if (index < 0 || index >= this.sectionPaths.length) throw new RangeError(`Section index ${index} out of range (0-${this.sectionPaths.length - 1})`);
808
+ const path = this.sectionPaths[index];
809
+ const entry = this.entries?.[path];
810
+ if (!entry) throw new Error(`Section file not found: ${path}`);
811
+ const xml = new TextDecoder().decode(entry);
812
+ return this.xmlParser.parse(xml);
813
+ }
814
+ listContents() {
815
+ this.ensureOpen();
816
+ return Object.keys(this.entries ?? {});
817
+ }
818
+ ensureOpen() {
819
+ if (!this.entries) throw new Error("HWPX file is not open. Call open() first.");
820
+ }
821
+ };
822
+ function ensureArray(val) {
823
+ if (val === void 0 || val === null) return [];
824
+ return Array.isArray(val) ? val : [val];
825
+ }
826
+ /**
827
+ * Extract text from a paragraph object (hp:p)
828
+ */
829
+ function extractParagraphText(pObj) {
830
+ const parts = [];
831
+ for (const run of ensureArray(pObj["hp:run"])) for (const [key, value] of Object.entries(run)) if (key === "hp:t") for (const t of ensureArray(value)) {
832
+ const text = typeof t === "string" ? t : t?.["#text"];
833
+ if (text !== void 0 && text !== null) parts.push(String(text));
834
+ }
835
+ else if (key === "hp:lineBreak") parts.push("\n");
836
+ else if (key === "hp:tab") parts.push(" ");
837
+ return parts.join("");
838
+ }
839
+ /**
840
+ * Extract text from a table cell (hp:tc)
841
+ */
842
+ function extractCellText(tcObj, lineBreakStyle = "space") {
843
+ const textParts = [];
844
+ const subList = tcObj["hp:subList"];
845
+ if (subList) for (const p of ensureArray(subList["hp:p"])) {
846
+ const paraText = extractParagraphText(p);
847
+ if (paraText.trim()) textParts.push(paraText.trim());
848
+ }
849
+ if (lineBreakStyle === "br") return textParts.join("<br>").replace(/\n/g, "<br>");
850
+ return textParts.join(" ").replace(/\n/g, " ");
851
+ }
852
+ /**
853
+ * Parse a table XML object (hp:tbl) into a Table
854
+ */
855
+ function parseHwpxTable(tblObj, lineBreakStyle = "space") {
856
+ const rows = Number(tblObj["@_rowCnt"] ?? 0);
857
+ const cols = Number(tblObj["@_colCnt"] ?? 0);
858
+ const cells = [];
859
+ for (const tr of ensureArray(tblObj["hp:tr"])) for (const tc of ensureArray(tr["hp:tc"])) {
860
+ const addr = tc["hp:cellAddr"];
861
+ const col = Number(addr?.["@_colAddr"] ?? 0);
862
+ const row = Number(addr?.["@_rowAddr"] ?? 0);
863
+ const span = tc["hp:cellSpan"];
864
+ let colspan = Number(span?.["@_colSpan"] ?? 1);
865
+ let rowspan = Number(span?.["@_rowSpan"] ?? 1);
866
+ if (colspan < 1) colspan = 1;
867
+ if (rowspan < 1) rowspan = 1;
868
+ const text = extractCellText(tc, lineBreakStyle);
869
+ cells.push({
870
+ row,
871
+ col,
872
+ rowspan,
873
+ colspan,
874
+ text
875
+ });
876
+ }
877
+ return {
878
+ rows,
879
+ cols,
880
+ cells
881
+ };
882
+ }
883
+ /**
884
+ * Parse a section XML object into paragraphs
885
+ */
886
+ function parseHwpxSection(sectionObj, options = {}) {
887
+ const lineBreakStyle = options.tableLineBreakStyle ?? "space";
888
+ const paragraphs = [];
889
+ let sec = sectionObj;
890
+ if (sec["hs:sec"]) sec = sec["hs:sec"];
891
+ for (const p of ensureArray(sec["hp:p"])) {
892
+ let text = extractParagraphText(p);
893
+ const tableMds = [];
894
+ for (const run of ensureArray(p["hp:run"])) for (const tbl of ensureArray(run["hp:tbl"])) try {
895
+ const table = parseHwpxTable(tbl, lineBreakStyle);
896
+ tableMds.push(tableToMarkdown(table));
897
+ } catch {
898
+ tableMds.push("[TABLE - Parse Error]");
899
+ }
900
+ for (const tbl of ensureArray(p["hp:tbl"])) try {
901
+ const table = parseHwpxTable(tbl, lineBreakStyle);
902
+ tableMds.push(tableToMarkdown(table));
903
+ } catch {
904
+ tableMds.push("[TABLE - Parse Error]");
905
+ }
906
+ if (tableMds.length > 0) {
907
+ text = text.trimEnd();
908
+ if (text) text += "\n\n";
909
+ text += tableMds.join("\n\n");
910
+ }
911
+ if (text.trim()) paragraphs.push({
912
+ text,
913
+ header: {
914
+ textCount: 0,
915
+ controlMask: 0,
916
+ paraShapeId: 0,
917
+ styleId: 0,
918
+ columnType: 0,
919
+ charShapeCount: 0
920
+ }
921
+ });
922
+ }
923
+ for (const tbl of ensureArray(sec["hp:tbl"])) try {
924
+ const md = tableToMarkdown(parseHwpxTable(tbl, lineBreakStyle));
925
+ if (md.trim()) paragraphs.push({
926
+ text: md,
927
+ header: {
928
+ textCount: 0,
929
+ controlMask: 0,
930
+ paraShapeId: 0,
931
+ styleId: 0,
932
+ columnType: 0,
933
+ charShapeCount: 0
934
+ }
935
+ });
936
+ } catch {}
937
+ return paragraphs;
938
+ }
939
+ /**
940
+ * Check if binary data is a HWPX file (ZIP magic bytes)
941
+ */
942
+ function isHwpxData(data) {
943
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
944
+ return bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
945
+ }
946
+ /**
947
+ * Check if a file path has HWPX extension
948
+ */
949
+ function isHwpxPath(path) {
950
+ return path.toLowerCase().endsWith(".hwpx");
951
+ }
952
+
953
+ //#endregion
954
+ //#region src/converter.ts
955
+ /**
956
+ * HWP/HWPX to Markdown Converter
957
+ * Orchestrates conversion from HWP/HWPX to Markdown
958
+ */
959
+ /**
960
+ * Convert paragraphs to Markdown
961
+ * @param paragraphs - List of paragraphs
962
+ * @returns Markdown text
963
+ */
964
+ function paragraphsToMarkdown(paragraphs) {
965
+ const lines = [];
966
+ for (const para of paragraphs) {
967
+ const text = para.text.trim();
968
+ if (!text) continue;
969
+ if (text.length < 5 && !text.startsWith("|") && [...text].every((c) => (c.codePointAt(0) ?? 0) > 127)) continue;
970
+ lines.push(text);
971
+ }
972
+ return lines.join("\n\n");
973
+ }
974
+ /**
975
+ * Convert HWP file to Markdown
976
+ * @param hwp - Opened HWP file
977
+ * @param options - Conversion options
978
+ * @returns Markdown content
979
+ */
980
+ function convertHwpToMarkdown(hwp, options = {}) {
981
+ const allParagraphs = [];
982
+ if (!options.tableLineBreakStyle) options.tableLineBreakStyle = "space";
983
+ const sectionCount = hwp.getSectionCount();
984
+ for (let i = 0; i < sectionCount; i++) {
985
+ const sectionData = hwp.readSection(i);
986
+ if (!sectionData) continue;
987
+ const paragraphs = new ParagraphParser(new RecordReader(sectionData), options).parseAllParagraphs();
988
+ allParagraphs.push(...paragraphs);
989
+ }
990
+ return paragraphsToMarkdown(allParagraphs);
991
+ }
992
+ /**
993
+ * Convert HWPX file to Markdown
994
+ * @param hwpx - Opened HWPX file
995
+ * @param options - Conversion options
996
+ * @returns Markdown content
997
+ */
998
+ function convertHwpxToMarkdown(hwpx, options = {}) {
999
+ const allParagraphs = [];
1000
+ const sectionCount = hwpx.getSectionCount();
1001
+ for (let i = 0; i < sectionCount; i++) {
1002
+ const paragraphs = parseHwpxSection(hwpx.getSectionXml(i), options);
1003
+ allParagraphs.push(...paragraphs);
1004
+ }
1005
+ return paragraphsToMarkdown(allParagraphs);
1006
+ }
1007
+ /**
1008
+ * High-level API: Convert HWP/HWPX file to Markdown
1009
+ * Auto-detects format based on file extension or magic bytes.
1010
+ * @param input - File path (Node.js), ArrayBuffer, or Uint8Array
1011
+ * @param options - Conversion options
1012
+ * @returns Markdown content
1013
+ */
1014
+ async function convert(input, options) {
1015
+ if (typeof input === "string" && isHwpxPath(input)) {
1016
+ const hwpx = await HWPXFile.fromFile(input);
1017
+ try {
1018
+ hwpx.open();
1019
+ return convertHwpxToMarkdown(hwpx, options);
1020
+ } finally {
1021
+ hwpx.close();
1022
+ }
1023
+ }
1024
+ if ((input instanceof Uint8Array || input instanceof ArrayBuffer) && isHwpxData(input instanceof ArrayBuffer ? new Uint8Array(input) : input)) {
1025
+ const hwpx = input instanceof Uint8Array ? HWPXFile.fromUint8Array(input) : HWPXFile.fromArrayBuffer(input);
1026
+ try {
1027
+ hwpx.open();
1028
+ return convertHwpxToMarkdown(hwpx, options);
1029
+ } finally {
1030
+ hwpx.close();
1031
+ }
1032
+ }
1033
+ let hwp;
1034
+ if (typeof input === "string") hwp = await HWPFile.fromFile(input);
1035
+ else if (input instanceof Uint8Array) hwp = HWPFile.fromUint8Array(input);
1036
+ else hwp = HWPFile.fromArrayBuffer(input);
1037
+ try {
1038
+ hwp.open();
1039
+ return convertHwpToMarkdown(hwp, options);
1040
+ } finally {
1041
+ hwp.close();
1042
+ }
1043
+ }
1044
+
1045
+ //#endregion
1046
+ //#region src/utils/binary.ts
1047
+ /**
1048
+ * Binary Reader Utility
1049
+ * Replaces Python's struct.unpack functionality
1050
+ * All multi-byte values are little-endian
1051
+ */
1052
+ var BinaryReader = class BinaryReader {
1053
+ view;
1054
+ offset = 0;
1055
+ constructor(data) {
1056
+ const buffer = data instanceof Uint8Array ? data.buffer : data;
1057
+ const byteOffset = data instanceof Uint8Array ? data.byteOffset : 0;
1058
+ const byteLength = data instanceof Uint8Array ? data.byteLength : data.byteLength;
1059
+ this.view = new DataView(buffer, byteOffset, byteLength);
1060
+ }
1061
+ /**
1062
+ * Read unsigned 8-bit integer (BYTE)
1063
+ */
1064
+ readUint8() {
1065
+ const value = this.view.getUint8(this.offset);
1066
+ this.offset += 1;
1067
+ return value;
1068
+ }
1069
+ /**
1070
+ * Read unsigned 16-bit integer (WORD) - little-endian
1071
+ */
1072
+ readUint16LE() {
1073
+ const value = this.view.getUint16(this.offset, true);
1074
+ this.offset += 2;
1075
+ return value;
1076
+ }
1077
+ /**
1078
+ * Read unsigned 32-bit integer (DWORD) - little-endian
1079
+ */
1080
+ readUint32LE() {
1081
+ const value = this.view.getUint32(this.offset, true);
1082
+ this.offset += 4;
1083
+ return value;
1084
+ }
1085
+ /**
1086
+ * Read signed 32-bit integer - little-endian
1087
+ */
1088
+ readInt32LE() {
1089
+ const value = this.view.getInt32(this.offset, true);
1090
+ this.offset += 4;
1091
+ return value;
1092
+ }
1093
+ /**
1094
+ * Read bytes without advancing offset (peek)
1095
+ */
1096
+ peekBytes(length) {
1097
+ return new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, length);
1098
+ }
1099
+ /**
1100
+ * Read bytes and advance offset
1101
+ */
1102
+ readBytes(length) {
1103
+ const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.offset, length);
1104
+ this.offset += length;
1105
+ return bytes;
1106
+ }
1107
+ /**
1108
+ * Skip bytes
1109
+ */
1110
+ skip(length) {
1111
+ this.offset += length;
1112
+ }
1113
+ /**
1114
+ * Set absolute position
1115
+ */
1116
+ seek(offset) {
1117
+ this.offset = offset;
1118
+ }
1119
+ /**
1120
+ * Get current position
1121
+ */
1122
+ get position() {
1123
+ return this.offset;
1124
+ }
1125
+ /**
1126
+ * Get remaining bytes
1127
+ */
1128
+ get remaining() {
1129
+ return this.view.byteLength - this.offset;
1130
+ }
1131
+ /**
1132
+ * Check if more data is available
1133
+ */
1134
+ hasMore(minBytes = 1) {
1135
+ return this.remaining >= minBytes;
1136
+ }
1137
+ /**
1138
+ * Get total length
1139
+ */
1140
+ get length() {
1141
+ return this.view.byteLength;
1142
+ }
1143
+ /**
1144
+ * Create a new BinaryReader for a subset of data
1145
+ */
1146
+ slice(start, end) {
1147
+ const actualEnd = end ?? this.view.byteLength;
1148
+ return new BinaryReader(new Uint8Array(this.view.buffer, this.view.byteOffset + start, actualEnd - start));
1149
+ }
1150
+ };
1151
+
1152
+ //#endregion
1153
+ exports.BinaryReader = BinaryReader;
1154
+ exports.HWPFile = HWPFile;
1155
+ exports.HWPTAG_BEGIN = HWPTAG_BEGIN;
1156
+ exports.HWPTAG_BIN_DATA = HWPTAG_BIN_DATA;
1157
+ exports.HWPTAG_BORDER_FILL = HWPTAG_BORDER_FILL;
1158
+ exports.HWPTAG_BULLET = HWPTAG_BULLET;
1159
+ exports.HWPTAG_CHAR_SHAPE = HWPTAG_CHAR_SHAPE;
1160
+ exports.HWPTAG_CTRL_DATA = HWPTAG_CTRL_DATA;
1161
+ exports.HWPTAG_CTRL_HEADER = HWPTAG_CTRL_HEADER;
1162
+ exports.HWPTAG_DOCUMENT_PROPERTIES = HWPTAG_DOCUMENT_PROPERTIES;
1163
+ exports.HWPTAG_FACE_NAME = HWPTAG_FACE_NAME;
1164
+ exports.HWPTAG_FOOTNOTE_SHAPE = HWPTAG_FOOTNOTE_SHAPE;
1165
+ exports.HWPTAG_ID_MAPPINGS = HWPTAG_ID_MAPPINGS;
1166
+ exports.HWPTAG_LIST_HEADER = HWPTAG_LIST_HEADER;
1167
+ exports.HWPTAG_NUMBERING = HWPTAG_NUMBERING;
1168
+ exports.HWPTAG_PAGE_BORDER_FILL = HWPTAG_PAGE_BORDER_FILL;
1169
+ exports.HWPTAG_PAGE_DEF = HWPTAG_PAGE_DEF;
1170
+ exports.HWPTAG_PARA_CHAR_SHAPE = HWPTAG_PARA_CHAR_SHAPE;
1171
+ exports.HWPTAG_PARA_HEADER = HWPTAG_PARA_HEADER;
1172
+ exports.HWPTAG_PARA_LINE_SEG = HWPTAG_PARA_LINE_SEG;
1173
+ exports.HWPTAG_PARA_RANGE_TAG = HWPTAG_PARA_RANGE_TAG;
1174
+ exports.HWPTAG_PARA_SHAPE = HWPTAG_PARA_SHAPE;
1175
+ exports.HWPTAG_PARA_TEXT = HWPTAG_PARA_TEXT;
1176
+ exports.HWPTAG_SHAPE_COMPONENT = HWPTAG_SHAPE_COMPONENT;
1177
+ exports.HWPTAG_SHAPE_COMPONENT_LINE = HWPTAG_SHAPE_COMPONENT_LINE;
1178
+ exports.HWPTAG_STYLE = HWPTAG_STYLE;
1179
+ exports.HWPTAG_TABLE = HWPTAG_TABLE;
1180
+ exports.HWPTAG_TAB_DEF = HWPTAG_TAB_DEF;
1181
+ exports.HWPXFile = HWPXFile;
1182
+ exports.ParagraphParser = ParagraphParser;
1183
+ exports.RecordReader = RecordReader;
1184
+ exports.convert = convert;
1185
+ exports.convertHwpToMarkdown = convertHwpToMarkdown;
1186
+ exports.convertHwpxToMarkdown = convertHwpxToMarkdown;
1187
+ exports.decompressRaw = decompressRaw;
1188
+ exports.isHwpxData = isHwpxData;
1189
+ exports.isHwpxPath = isHwpxPath;
1190
+ exports.paragraphsToMarkdown = paragraphsToMarkdown;
1191
+ exports.parseCellProperties = parseCellProperties;
1192
+ exports.parseHwpxSection = parseHwpxSection;
1193
+ exports.parseHwpxTable = parseHwpxTable;
1194
+ exports.parseParaHeader = parseParaHeader;
1195
+ exports.parseParaText = parseParaText;
1196
+ exports.parseTable = parseTable;
1197
+ exports.parseTableProperties = parseTableProperties;
1198
+ exports.processControlChars = processControlChars;
1199
+ exports.tableToMarkdown = tableToMarkdown;
1200
+ //# sourceMappingURL=index.cjs.map