kordoc 3.7.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/dist/{-7UC4ZWBS.js → -LD4BZDDJ.js} +3 -3
- package/dist/{chunk-IJJMVTU5.js → chunk-IFYJFWD2.js} +2 -2
- package/dist/{chunk-NXRABCWW.js → chunk-KT2BCHXI.js} +803 -703
- package/dist/chunk-KT2BCHXI.js.map +1 -0
- package/dist/{chunk-QZCP3UWU.cjs → chunk-LFCS3UVG.cjs} +2 -2
- package/dist/{chunk-QZCP3UWU.cjs.map → chunk-LFCS3UVG.cjs.map} +1 -1
- package/dist/{chunk-MEVKYW55.js → chunk-PELBIL4K.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/index.cjs +968 -868
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +802 -702
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +3 -3
- package/dist/{parser-DR5CTZ74.js → parser-FFEBMLSH.js} +1830 -1803
- package/dist/parser-FFEBMLSH.js.map +1 -0
- package/dist/{parser-RFLPUZ7P.cjs → parser-IXK5V7YG.cjs} +1830 -1803
- package/dist/parser-IXK5V7YG.cjs.map +1 -0
- package/dist/{parser-ZHJFQR44.js → parser-XEDROIM7.js} +1830 -1803
- package/dist/parser-XEDROIM7.js.map +1 -0
- package/dist/{watch-UIX447QV.js → watch-MAWCDNFI.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-NXRABCWW.js.map +0 -1
- package/dist/parser-DR5CTZ74.js.map +0 -1
- package/dist/parser-RFLPUZ7P.cjs.map +0 -1
- package/dist/parser-ZHJFQR44.js.map +0 -1
- /package/dist/{-7UC4ZWBS.js.map → -LD4BZDDJ.js.map} +0 -0
- /package/dist/{chunk-IJJMVTU5.js.map → chunk-IFYJFWD2.js.map} +0 -0
- /package/dist/{chunk-MEVKYW55.js.map → chunk-PELBIL4K.js.map} +0 -0
- /package/dist/{watch-UIX447QV.js.map → watch-MAWCDNFI.js.map} +0 -0
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
sanitizeHref,
|
|
26
26
|
stripDtd,
|
|
27
27
|
toArrayBuffer
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-IFYJFWD2.js";
|
|
29
29
|
import {
|
|
30
30
|
parsePageRange
|
|
31
31
|
} from "./chunk-MOL7MDBG.js";
|
|
@@ -34,9 +34,7 @@ import {
|
|
|
34
34
|
import { readFile } from "fs/promises";
|
|
35
35
|
|
|
36
36
|
// src/hwpx/parser.ts
|
|
37
|
-
import
|
|
38
|
-
import { inflateRawSync } from "zlib";
|
|
39
|
-
import { DOMParser } from "@xmldom/xmldom";
|
|
37
|
+
import JSZip2 from "jszip";
|
|
40
38
|
|
|
41
39
|
// src/hwpx/com-fallback.ts
|
|
42
40
|
import { execFileSync } from "child_process";
|
|
@@ -145,6 +143,245 @@ function comResultToParseResult(pages, pageCount, warnings) {
|
|
|
145
143
|
};
|
|
146
144
|
}
|
|
147
145
|
|
|
146
|
+
// src/hwpx/parser-shared.ts
|
|
147
|
+
import { DOMParser } from "@xmldom/xmldom";
|
|
148
|
+
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
149
|
+
var MAX_ZIP_ENTRIES = 500;
|
|
150
|
+
function clampSpan(val, max) {
|
|
151
|
+
return Math.max(1, Math.min(val, max));
|
|
152
|
+
}
|
|
153
|
+
var MAX_XML_DEPTH = 200;
|
|
154
|
+
function createSectionShared() {
|
|
155
|
+
return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
|
|
156
|
+
}
|
|
157
|
+
function createXmlParser(warnings) {
|
|
158
|
+
return new DOMParser({
|
|
159
|
+
onError(level, msg2) {
|
|
160
|
+
if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg2}`);
|
|
161
|
+
warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg2}` });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function applyPageText(blocks, shared) {
|
|
166
|
+
const { headers, footers } = shared.pageText;
|
|
167
|
+
if (headers.length > 0) {
|
|
168
|
+
blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
|
|
169
|
+
}
|
|
170
|
+
if (footers.length > 0) {
|
|
171
|
+
blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function findChildByLocalName(parent, name) {
|
|
175
|
+
const children = parent.childNodes;
|
|
176
|
+
if (!children) return null;
|
|
177
|
+
for (let i = 0; i < children.length; i++) {
|
|
178
|
+
const ch = children[i];
|
|
179
|
+
if (ch.nodeType !== 1) continue;
|
|
180
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
181
|
+
if (tag === name) return ch;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function extractTextFromNode(node) {
|
|
186
|
+
let result = "";
|
|
187
|
+
const children = node.childNodes;
|
|
188
|
+
if (!children) return result;
|
|
189
|
+
for (let i = 0; i < children.length; i++) {
|
|
190
|
+
const child = children[i];
|
|
191
|
+
if (child.nodeType === 3) result += child.textContent || "";
|
|
192
|
+
else if (child.nodeType === 1) result += extractTextFromNode(child);
|
|
193
|
+
}
|
|
194
|
+
return result.trim();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/hwpx/styles.ts
|
|
198
|
+
async function extractHwpxStyles(zip, decompressed) {
|
|
199
|
+
const result = {
|
|
200
|
+
charProperties: /* @__PURE__ */ new Map(),
|
|
201
|
+
styles: /* @__PURE__ */ new Map(),
|
|
202
|
+
numberings: /* @__PURE__ */ new Map(),
|
|
203
|
+
bullets: /* @__PURE__ */ new Map(),
|
|
204
|
+
paraHeadings: /* @__PURE__ */ new Map()
|
|
205
|
+
};
|
|
206
|
+
const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
|
|
207
|
+
for (const hp of headerPaths) {
|
|
208
|
+
const hpLower = hp.toLowerCase();
|
|
209
|
+
const file = zip.file(hp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === hpLower) || null;
|
|
210
|
+
if (!file) continue;
|
|
211
|
+
try {
|
|
212
|
+
const xml = await file.async("text");
|
|
213
|
+
if (decompressed) {
|
|
214
|
+
decompressed.total += xml.length * 2;
|
|
215
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
216
|
+
}
|
|
217
|
+
const parser = createXmlParser();
|
|
218
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
219
|
+
if (!doc.documentElement) continue;
|
|
220
|
+
parseCharProperties(doc, result.charProperties);
|
|
221
|
+
parseStyleElements(doc, result.styles);
|
|
222
|
+
const domDoc = doc;
|
|
223
|
+
parseNumberings(domDoc, result.numberings);
|
|
224
|
+
parseBullets(domDoc, result.bullets);
|
|
225
|
+
parseParaHeadings(domDoc, result.paraHeadings);
|
|
226
|
+
break;
|
|
227
|
+
} catch {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function parseCharProperties(doc, map) {
|
|
234
|
+
const tagNames = ["hh:charPr", "charPr", "hp:charPr"];
|
|
235
|
+
for (const tagName of tagNames) {
|
|
236
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
237
|
+
for (let i = 0; i < elements.length; i++) {
|
|
238
|
+
const el = elements[i];
|
|
239
|
+
const id = el.getAttribute("id") || el.getAttribute("IDRef") || "";
|
|
240
|
+
if (!id) continue;
|
|
241
|
+
const prop = {};
|
|
242
|
+
const height = el.getAttribute("height");
|
|
243
|
+
if (height) {
|
|
244
|
+
const parsedHeight = parseInt(height, 10);
|
|
245
|
+
if (!isNaN(parsedHeight) && parsedHeight > 0) {
|
|
246
|
+
prop.fontSize = parsedHeight / 100;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const bold = el.getAttribute("bold");
|
|
250
|
+
if (bold === "true" || bold === "1") prop.bold = true;
|
|
251
|
+
const italic = el.getAttribute("italic");
|
|
252
|
+
if (italic === "true" || italic === "1") prop.italic = true;
|
|
253
|
+
const fontFaces = el.getElementsByTagName("*");
|
|
254
|
+
for (let j = 0; j < fontFaces.length; j++) {
|
|
255
|
+
const ff = fontFaces[j];
|
|
256
|
+
const localTag = (ff.tagName || "").replace(/^[^:]+:/, "");
|
|
257
|
+
if (localTag === "fontface" || localTag === "fontRef") {
|
|
258
|
+
const face = ff.getAttribute("face") || ff.getAttribute("FontFace");
|
|
259
|
+
if (face) {
|
|
260
|
+
prop.fontName = face;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
map.set(id, prop);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function parseStyleElements(doc, map) {
|
|
270
|
+
const tagNames = ["hh:style", "style", "hp:style"];
|
|
271
|
+
for (const tagName of tagNames) {
|
|
272
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
273
|
+
for (let i = 0; i < elements.length; i++) {
|
|
274
|
+
const el = elements[i];
|
|
275
|
+
const id = el.getAttribute("id") || el.getAttribute("IDRef") || String(i);
|
|
276
|
+
const name = el.getAttribute("name") || el.getAttribute("engName") || "";
|
|
277
|
+
const charPrId = el.getAttribute("charPrIDRef") || void 0;
|
|
278
|
+
const paraPrId = el.getAttribute("paraPrIDRef") || void 0;
|
|
279
|
+
map.set(id, { name, charPrId, paraPrId });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function parseNumberings(doc, map) {
|
|
284
|
+
const tagNames = ["hh:numbering", "numbering"];
|
|
285
|
+
for (const tagName of tagNames) {
|
|
286
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
287
|
+
for (let i = 0; i < elements.length; i++) {
|
|
288
|
+
const el = elements[i];
|
|
289
|
+
const id = el.getAttribute("id") || "";
|
|
290
|
+
if (!id) continue;
|
|
291
|
+
const def = { heads: /* @__PURE__ */ new Map() };
|
|
292
|
+
const children = el.childNodes;
|
|
293
|
+
for (let j = 0; j < children.length; j++) {
|
|
294
|
+
const ch = children[j];
|
|
295
|
+
if (ch.nodeType !== 1) continue;
|
|
296
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
297
|
+
if (tag !== "paraHead") continue;
|
|
298
|
+
const level = parseInt(ch.getAttribute("level") || "", 10);
|
|
299
|
+
if (isNaN(level) || level < 1 || level > 10) continue;
|
|
300
|
+
const start = parseInt(ch.getAttribute("start") || "1", 10);
|
|
301
|
+
def.heads.set(level, {
|
|
302
|
+
numFormat: ch.getAttribute("numFormat") || "DIGIT",
|
|
303
|
+
text: ch.textContent || "",
|
|
304
|
+
start: isNaN(start) ? 1 : start
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (def.heads.size > 0) map.set(id, def);
|
|
308
|
+
}
|
|
309
|
+
if (map.size > 0) break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function parseBullets(doc, map) {
|
|
313
|
+
const tagNames = ["hh:bullet", "bullet"];
|
|
314
|
+
for (const tagName of tagNames) {
|
|
315
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
316
|
+
for (let i = 0; i < elements.length; i++) {
|
|
317
|
+
const el = elements[i];
|
|
318
|
+
const id = el.getAttribute("id") || "";
|
|
319
|
+
const char = el.getAttribute("char") || "";
|
|
320
|
+
if (id && char) map.set(id, char);
|
|
321
|
+
}
|
|
322
|
+
if (map.size > 0) break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function parseParaHeadings(doc, map) {
|
|
326
|
+
const tagNames = ["hh:paraPr", "paraPr"];
|
|
327
|
+
for (const tagName of tagNames) {
|
|
328
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
329
|
+
for (let i = 0; i < elements.length; i++) {
|
|
330
|
+
const el = elements[i];
|
|
331
|
+
const id = el.getAttribute("id") || "";
|
|
332
|
+
if (!id) continue;
|
|
333
|
+
const heading = findChildByLocalName(el, "heading");
|
|
334
|
+
if (!heading) continue;
|
|
335
|
+
const type = heading.getAttribute("type") || "NONE";
|
|
336
|
+
if (type !== "NUMBER" && type !== "BULLET" && type !== "OUTLINE") continue;
|
|
337
|
+
const level = parseInt(heading.getAttribute("level") || "0", 10);
|
|
338
|
+
map.set(id, {
|
|
339
|
+
type,
|
|
340
|
+
idRef: heading.getAttribute("idRef") || "0",
|
|
341
|
+
level: isNaN(level) ? 0 : Math.max(0, Math.min(level, 9))
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (map.size > 0) break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function detectHwpxHeadings(blocks, styleMap) {
|
|
348
|
+
if (blocks.some((b) => b.type === "heading")) return;
|
|
349
|
+
let baseFontSize = 0;
|
|
350
|
+
const sizeFreq = /* @__PURE__ */ new Map();
|
|
351
|
+
for (const b of blocks) {
|
|
352
|
+
if (b.style?.fontSize) {
|
|
353
|
+
sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
let maxCount = 0;
|
|
357
|
+
for (const [size, count] of sizeFreq) {
|
|
358
|
+
if (count > maxCount) {
|
|
359
|
+
maxCount = count;
|
|
360
|
+
baseFontSize = size;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const block of blocks) {
|
|
364
|
+
if (block.type !== "paragraph" || !block.text) continue;
|
|
365
|
+
const text = block.text.trim();
|
|
366
|
+
if (text.length === 0 || text.length > 200 || /^\d+$/.test(text)) continue;
|
|
367
|
+
let level = 0;
|
|
368
|
+
if (baseFontSize > 0 && block.style?.fontSize) {
|
|
369
|
+
const ratio = block.style.fontSize / baseFontSize;
|
|
370
|
+
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
371
|
+
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
372
|
+
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
373
|
+
}
|
|
374
|
+
const compactText = text.replace(/\s+/g, "");
|
|
375
|
+
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
376
|
+
if (level === 0) level = 3;
|
|
377
|
+
}
|
|
378
|
+
if (level > 0) {
|
|
379
|
+
block.type = "heading";
|
|
380
|
+
block.level = level;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
148
385
|
// src/hwpx/equation.ts
|
|
149
386
|
var CONVERT_MAP = {
|
|
150
387
|
TIMES: "\\times",
|
|
@@ -584,173 +821,7 @@ function hmlToLatex(hmlEqStr) {
|
|
|
584
821
|
return out;
|
|
585
822
|
}
|
|
586
823
|
|
|
587
|
-
// src/hwpx/
|
|
588
|
-
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
589
|
-
var MAX_ZIP_ENTRIES = 500;
|
|
590
|
-
function clampSpan(val, max) {
|
|
591
|
-
return Math.max(1, Math.min(val, max));
|
|
592
|
-
}
|
|
593
|
-
var MAX_XML_DEPTH = 200;
|
|
594
|
-
function createSectionShared() {
|
|
595
|
-
return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
|
|
596
|
-
}
|
|
597
|
-
function createXmlParser(warnings) {
|
|
598
|
-
return new DOMParser({
|
|
599
|
-
onError(level, msg2) {
|
|
600
|
-
if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg2}`);
|
|
601
|
-
warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg2}` });
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
async function extractHwpxStyles(zip, decompressed) {
|
|
606
|
-
const result = {
|
|
607
|
-
charProperties: /* @__PURE__ */ new Map(),
|
|
608
|
-
styles: /* @__PURE__ */ new Map(),
|
|
609
|
-
numberings: /* @__PURE__ */ new Map(),
|
|
610
|
-
bullets: /* @__PURE__ */ new Map(),
|
|
611
|
-
paraHeadings: /* @__PURE__ */ new Map()
|
|
612
|
-
};
|
|
613
|
-
const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
|
|
614
|
-
for (const hp of headerPaths) {
|
|
615
|
-
const hpLower = hp.toLowerCase();
|
|
616
|
-
const file = zip.file(hp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === hpLower) || null;
|
|
617
|
-
if (!file) continue;
|
|
618
|
-
try {
|
|
619
|
-
const xml = await file.async("text");
|
|
620
|
-
if (decompressed) {
|
|
621
|
-
decompressed.total += xml.length * 2;
|
|
622
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
623
|
-
}
|
|
624
|
-
const parser = createXmlParser();
|
|
625
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
626
|
-
if (!doc.documentElement) continue;
|
|
627
|
-
parseCharProperties(doc, result.charProperties);
|
|
628
|
-
parseStyleElements(doc, result.styles);
|
|
629
|
-
const domDoc = doc;
|
|
630
|
-
parseNumberings(domDoc, result.numberings);
|
|
631
|
-
parseBullets(domDoc, result.bullets);
|
|
632
|
-
parseParaHeadings(domDoc, result.paraHeadings);
|
|
633
|
-
break;
|
|
634
|
-
} catch {
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return result;
|
|
639
|
-
}
|
|
640
|
-
function parseCharProperties(doc, map) {
|
|
641
|
-
const tagNames = ["hh:charPr", "charPr", "hp:charPr"];
|
|
642
|
-
for (const tagName of tagNames) {
|
|
643
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
644
|
-
for (let i = 0; i < elements.length; i++) {
|
|
645
|
-
const el = elements[i];
|
|
646
|
-
const id = el.getAttribute("id") || el.getAttribute("IDRef") || "";
|
|
647
|
-
if (!id) continue;
|
|
648
|
-
const prop = {};
|
|
649
|
-
const height = el.getAttribute("height");
|
|
650
|
-
if (height) {
|
|
651
|
-
const parsedHeight = parseInt(height, 10);
|
|
652
|
-
if (!isNaN(parsedHeight) && parsedHeight > 0) {
|
|
653
|
-
prop.fontSize = parsedHeight / 100;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
const bold = el.getAttribute("bold");
|
|
657
|
-
if (bold === "true" || bold === "1") prop.bold = true;
|
|
658
|
-
const italic = el.getAttribute("italic");
|
|
659
|
-
if (italic === "true" || italic === "1") prop.italic = true;
|
|
660
|
-
const fontFaces = el.getElementsByTagName("*");
|
|
661
|
-
for (let j = 0; j < fontFaces.length; j++) {
|
|
662
|
-
const ff = fontFaces[j];
|
|
663
|
-
const localTag = (ff.tagName || "").replace(/^[^:]+:/, "");
|
|
664
|
-
if (localTag === "fontface" || localTag === "fontRef") {
|
|
665
|
-
const face = ff.getAttribute("face") || ff.getAttribute("FontFace");
|
|
666
|
-
if (face) {
|
|
667
|
-
prop.fontName = face;
|
|
668
|
-
break;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
map.set(id, prop);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
function parseStyleElements(doc, map) {
|
|
677
|
-
const tagNames = ["hh:style", "style", "hp:style"];
|
|
678
|
-
for (const tagName of tagNames) {
|
|
679
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
680
|
-
for (let i = 0; i < elements.length; i++) {
|
|
681
|
-
const el = elements[i];
|
|
682
|
-
const id = el.getAttribute("id") || el.getAttribute("IDRef") || String(i);
|
|
683
|
-
const name = el.getAttribute("name") || el.getAttribute("engName") || "";
|
|
684
|
-
const charPrId = el.getAttribute("charPrIDRef") || void 0;
|
|
685
|
-
const paraPrId = el.getAttribute("paraPrIDRef") || void 0;
|
|
686
|
-
map.set(id, { name, charPrId, paraPrId });
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
function parseNumberings(doc, map) {
|
|
691
|
-
const tagNames = ["hh:numbering", "numbering"];
|
|
692
|
-
for (const tagName of tagNames) {
|
|
693
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
694
|
-
for (let i = 0; i < elements.length; i++) {
|
|
695
|
-
const el = elements[i];
|
|
696
|
-
const id = el.getAttribute("id") || "";
|
|
697
|
-
if (!id) continue;
|
|
698
|
-
const def = { heads: /* @__PURE__ */ new Map() };
|
|
699
|
-
const children = el.childNodes;
|
|
700
|
-
for (let j = 0; j < children.length; j++) {
|
|
701
|
-
const ch = children[j];
|
|
702
|
-
if (ch.nodeType !== 1) continue;
|
|
703
|
-
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
704
|
-
if (tag !== "paraHead") continue;
|
|
705
|
-
const level = parseInt(ch.getAttribute("level") || "", 10);
|
|
706
|
-
if (isNaN(level) || level < 1 || level > 10) continue;
|
|
707
|
-
const start = parseInt(ch.getAttribute("start") || "1", 10);
|
|
708
|
-
def.heads.set(level, {
|
|
709
|
-
numFormat: ch.getAttribute("numFormat") || "DIGIT",
|
|
710
|
-
text: ch.textContent || "",
|
|
711
|
-
start: isNaN(start) ? 1 : start
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
if (def.heads.size > 0) map.set(id, def);
|
|
715
|
-
}
|
|
716
|
-
if (map.size > 0) break;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
function parseBullets(doc, map) {
|
|
720
|
-
const tagNames = ["hh:bullet", "bullet"];
|
|
721
|
-
for (const tagName of tagNames) {
|
|
722
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
723
|
-
for (let i = 0; i < elements.length; i++) {
|
|
724
|
-
const el = elements[i];
|
|
725
|
-
const id = el.getAttribute("id") || "";
|
|
726
|
-
const char = el.getAttribute("char") || "";
|
|
727
|
-
if (id && char) map.set(id, char);
|
|
728
|
-
}
|
|
729
|
-
if (map.size > 0) break;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
function parseParaHeadings(doc, map) {
|
|
733
|
-
const tagNames = ["hh:paraPr", "paraPr"];
|
|
734
|
-
for (const tagName of tagNames) {
|
|
735
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
736
|
-
for (let i = 0; i < elements.length; i++) {
|
|
737
|
-
const el = elements[i];
|
|
738
|
-
const id = el.getAttribute("id") || "";
|
|
739
|
-
if (!id) continue;
|
|
740
|
-
const heading = findChildByLocalName(el, "heading");
|
|
741
|
-
if (!heading) continue;
|
|
742
|
-
const type = heading.getAttribute("type") || "NONE";
|
|
743
|
-
if (type !== "NUMBER" && type !== "BULLET" && type !== "OUTLINE") continue;
|
|
744
|
-
const level = parseInt(heading.getAttribute("level") || "0", 10);
|
|
745
|
-
map.set(id, {
|
|
746
|
-
type,
|
|
747
|
-
idRef: heading.getAttribute("idRef") || "0",
|
|
748
|
-
level: isNaN(level) ? 0 : Math.max(0, Math.min(level, 9))
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
if (map.size > 0) break;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
824
|
+
// src/hwpx/para-heading.ts
|
|
754
825
|
var HANGUL_SYLLABLE_SEQ = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
755
826
|
var HANGUL_JAMO_SEQ = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
756
827
|
function toRoman(n) {
|
|
@@ -773,448 +844,78 @@ function toRoman(n) {
|
|
|
773
844
|
let out = "";
|
|
774
845
|
for (const [v, s] of table) {
|
|
775
846
|
while (n >= v) {
|
|
776
|
-
out += s;
|
|
777
|
-
n -= v;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return out;
|
|
781
|
-
}
|
|
782
|
-
function formatHeadNumber(n, numFormat) {
|
|
783
|
-
if (n <= 0) n = 1;
|
|
784
|
-
switch (numFormat) {
|
|
785
|
-
case "DIGIT":
|
|
786
|
-
return String(n);
|
|
787
|
-
case "CIRCLED_DIGIT":
|
|
788
|
-
return n <= 20 ? String.fromCodePoint(9312 + n - 1) : `(${n})`;
|
|
789
|
-
case "HANGUL_SYLLABLE":
|
|
790
|
-
return HANGUL_SYLLABLE_SEQ[(n - 1) % HANGUL_SYLLABLE_SEQ.length];
|
|
791
|
-
case "CIRCLED_HANGUL_SYLLABLE":
|
|
792
|
-
return n <= 14 ? String.fromCodePoint(12910 + n - 1) : HANGUL_SYLLABLE_SEQ[(n - 1) % 14];
|
|
793
|
-
case "HANGUL_JAMO":
|
|
794
|
-
return HANGUL_JAMO_SEQ[(n - 1) % HANGUL_JAMO_SEQ.length];
|
|
795
|
-
case "CIRCLED_HANGUL_JAMO":
|
|
796
|
-
return n <= 14 ? String.fromCodePoint(12896 + n - 1) : HANGUL_JAMO_SEQ[(n - 1) % 14];
|
|
797
|
-
case "LATIN_CAPITAL":
|
|
798
|
-
return String.fromCharCode(65 + (n - 1) % 26);
|
|
799
|
-
case "LATIN_SMALL":
|
|
800
|
-
return String.fromCharCode(97 + (n - 1) % 26);
|
|
801
|
-
case "CIRCLED_LATIN_CAPITAL":
|
|
802
|
-
return n <= 26 ? String.fromCodePoint(9398 + n - 1) : String.fromCharCode(65 + (n - 1) % 26);
|
|
803
|
-
case "CIRCLED_LATIN_SMALL":
|
|
804
|
-
return n <= 26 ? String.fromCodePoint(9424 + n - 1) : String.fromCharCode(97 + (n - 1) % 26);
|
|
805
|
-
case "ROMAN_CAPITAL":
|
|
806
|
-
return toRoman(n);
|
|
807
|
-
case "ROMAN_SMALL":
|
|
808
|
-
return toRoman(n).toLowerCase();
|
|
809
|
-
default:
|
|
810
|
-
return String(n);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
function resolveParaHeading(paraEl, ctx) {
|
|
814
|
-
const sm = ctx.styleMap;
|
|
815
|
-
if (!sm) return null;
|
|
816
|
-
const prId = paraEl.getAttribute("paraPrIDRef");
|
|
817
|
-
if (!prId) return null;
|
|
818
|
-
const ref = sm.paraHeadings.get(prId);
|
|
819
|
-
if (!ref) return null;
|
|
820
|
-
if (ref.type === "BULLET") {
|
|
821
|
-
const char = sm.bullets.get(ref.idRef);
|
|
822
|
-
return char ? { prefix: char } : null;
|
|
823
|
-
}
|
|
824
|
-
const numId = ref.type === "OUTLINE" ? ctx.outlineNumId || "1" : ref.idRef;
|
|
825
|
-
const level = Math.min(ref.level + 1, 10);
|
|
826
|
-
const headingLevel = ref.type === "OUTLINE" ? Math.min(ref.level + 1, 6) : void 0;
|
|
827
|
-
const numDef = sm.numberings.get(numId);
|
|
828
|
-
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
829
|
-
let counters = ctx.shared.numState.get(numId);
|
|
830
|
-
if (!counters) {
|
|
831
|
-
counters = new Array(11).fill(0);
|
|
832
|
-
ctx.shared.numState.set(numId, counters);
|
|
833
|
-
}
|
|
834
|
-
const head = numDef.heads.get(level);
|
|
835
|
-
counters[level] = counters[level] === 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
836
|
-
for (let l = level + 1; l <= 10; l++) counters[l] = 0;
|
|
837
|
-
const fmtText = head?.text?.trim() || `^${level}.`;
|
|
838
|
-
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
839
|
-
const lv = parseInt(d, 10);
|
|
840
|
-
const refHead = numDef.heads.get(lv);
|
|
841
|
-
const n = counters[lv] || refHead?.start || 1;
|
|
842
|
-
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
843
|
-
});
|
|
844
|
-
return { prefix, headingLevel };
|
|
845
|
-
}
|
|
846
|
-
async function parseHwpxDocument(buffer, options) {
|
|
847
|
-
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
848
|
-
let zip;
|
|
849
|
-
try {
|
|
850
|
-
zip = await JSZip.loadAsync(buffer);
|
|
851
|
-
} catch {
|
|
852
|
-
return extractFromBrokenZip(buffer);
|
|
853
|
-
}
|
|
854
|
-
const actualEntryCount = Object.keys(zip.files).length;
|
|
855
|
-
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
856
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
857
|
-
}
|
|
858
|
-
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
859
|
-
if (manifestFile) {
|
|
860
|
-
const manifestXml = await manifestFile.async("text");
|
|
861
|
-
if (isEncryptedHwpx(manifestXml)) {
|
|
862
|
-
if (isComFallbackAvailable() && options?.filePath) {
|
|
863
|
-
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
864
|
-
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
865
|
-
return comResultToParseResult(pages, pageCount, warnings2);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
throw new KordocError("DRM \uC554\uD638\uD654\uB41C HWPX \uD30C\uC77C\uC785\uB2C8\uB2E4. Windows + \uD55C\uCEF4 \uC624\uD53C\uC2A4 \uC124\uCE58 \uC2DC \uC790\uB3D9 \uCD94\uCD9C\uB429\uB2C8\uB2E4.");
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
const decompressed = { total: 0 };
|
|
872
|
-
const metadata = {};
|
|
873
|
-
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
874
|
-
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
875
|
-
const warnings = [];
|
|
876
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
877
|
-
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
878
|
-
metadata.pageCount = sectionPaths.length;
|
|
879
|
-
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
880
|
-
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
881
|
-
const blocks = [];
|
|
882
|
-
const shared = createSectionShared();
|
|
883
|
-
let parsedSections = 0;
|
|
884
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
885
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
886
|
-
const file = zip.file(sectionPaths[si]);
|
|
887
|
-
if (!file) continue;
|
|
888
|
-
try {
|
|
889
|
-
const xml = await file.async("text");
|
|
890
|
-
decompressed.total += xml.length * 2;
|
|
891
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
892
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
893
|
-
parsedSections++;
|
|
894
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
895
|
-
} catch (secErr) {
|
|
896
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
897
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
applyPageText(blocks, shared);
|
|
901
|
-
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings);
|
|
902
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
903
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
904
|
-
const markdown = blocksToMarkdown(blocks);
|
|
905
|
-
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
906
|
-
}
|
|
907
|
-
function applyPageText(blocks, shared) {
|
|
908
|
-
const { headers, footers } = shared.pageText;
|
|
909
|
-
if (headers.length > 0) {
|
|
910
|
-
blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
|
|
911
|
-
}
|
|
912
|
-
if (footers.length > 0) {
|
|
913
|
-
blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
function imageExtToMime(ext) {
|
|
917
|
-
switch (ext.toLowerCase()) {
|
|
918
|
-
case "jpg":
|
|
919
|
-
case "jpeg":
|
|
920
|
-
return "image/jpeg";
|
|
921
|
-
case "png":
|
|
922
|
-
return "image/png";
|
|
923
|
-
case "gif":
|
|
924
|
-
return "image/gif";
|
|
925
|
-
case "bmp":
|
|
926
|
-
return "image/bmp";
|
|
927
|
-
case "tif":
|
|
928
|
-
case "tiff":
|
|
929
|
-
return "image/tiff";
|
|
930
|
-
case "wmf":
|
|
931
|
-
return "image/wmf";
|
|
932
|
-
case "emf":
|
|
933
|
-
return "image/emf";
|
|
934
|
-
case "svg":
|
|
935
|
-
return "image/svg+xml";
|
|
936
|
-
default:
|
|
937
|
-
return "application/octet-stream";
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
function mimeToExt(mime) {
|
|
941
|
-
if (mime.includes("jpeg")) return "jpg";
|
|
942
|
-
if (mime.includes("png")) return "png";
|
|
943
|
-
if (mime.includes("gif")) return "gif";
|
|
944
|
-
if (mime.includes("bmp")) return "bmp";
|
|
945
|
-
if (mime.includes("tiff")) return "tif";
|
|
946
|
-
if (mime.includes("wmf")) return "wmf";
|
|
947
|
-
if (mime.includes("emf")) return "emf";
|
|
948
|
-
if (mime.includes("svg")) return "svg";
|
|
949
|
-
return "bin";
|
|
950
|
-
}
|
|
951
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
952
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
953
|
-
for (const block of blocks) {
|
|
954
|
-
if (block.type === "image") {
|
|
955
|
-
out.push({ block, ownerCell });
|
|
956
|
-
} else if (block.type === "table" && block.table) {
|
|
957
|
-
for (const row of block.table.cells) {
|
|
958
|
-
for (const cell of row) {
|
|
959
|
-
if (cell.blocks?.length) collectImageBlocks(cell.blocks, out, cell, depth + 1);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
|
|
966
|
-
const images = [];
|
|
967
|
-
let imageIndex = 0;
|
|
968
|
-
const imageBlocks = [];
|
|
969
|
-
collectImageBlocks(blocks, imageBlocks);
|
|
970
|
-
for (const { block, ownerCell } of imageBlocks) {
|
|
971
|
-
if (block.type !== "image" || !block.text) continue;
|
|
972
|
-
const ref = block.text;
|
|
973
|
-
const candidates = [
|
|
974
|
-
`BinData/${ref}`,
|
|
975
|
-
`Contents/BinData/${ref}`,
|
|
976
|
-
ref
|
|
977
|
-
// 절대 경로일 수도 있음
|
|
978
|
-
];
|
|
979
|
-
let resolvedPath = null;
|
|
980
|
-
if (!ref.includes(".")) {
|
|
981
|
-
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
982
|
-
for (const prefix of prefixes) {
|
|
983
|
-
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
984
|
-
if (match.length > 0) {
|
|
985
|
-
resolvedPath = match[0].name;
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
let found = false;
|
|
991
|
-
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
992
|
-
for (const path of allCandidates) {
|
|
993
|
-
if (isPathTraversal(path)) continue;
|
|
994
|
-
const file = zip.file(path);
|
|
995
|
-
if (!file) continue;
|
|
996
|
-
try {
|
|
997
|
-
const data = await file.async("uint8array");
|
|
998
|
-
decompressed.total += data.length;
|
|
999
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1000
|
-
const actualPath = path;
|
|
1001
|
-
const ext = actualPath.includes(".") ? actualPath.split(".").pop() || "png" : "png";
|
|
1002
|
-
const mimeType = imageExtToMime(ext);
|
|
1003
|
-
imageIndex++;
|
|
1004
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1005
|
-
images.push({ filename, data, mimeType });
|
|
1006
|
-
block.text = filename;
|
|
1007
|
-
block.imageData = { data, mimeType, filename: ref };
|
|
1008
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1009
|
-
found = true;
|
|
1010
|
-
break;
|
|
1011
|
-
} catch (err) {
|
|
1012
|
-
if (err instanceof KordocError) throw err;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
if (!found) {
|
|
1016
|
-
warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1017
|
-
block.type = "paragraph";
|
|
1018
|
-
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1019
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
return images;
|
|
1023
|
-
}
|
|
1024
|
-
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
1025
|
-
try {
|
|
1026
|
-
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
1027
|
-
for (const mp of metaPaths) {
|
|
1028
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
1029
|
-
if (!file) continue;
|
|
1030
|
-
const xml = await file.async("text");
|
|
1031
|
-
if (decompressed) {
|
|
1032
|
-
decompressed.total += xml.length * 2;
|
|
1033
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1034
|
-
}
|
|
1035
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
1036
|
-
if (metadata.title || metadata.author) return;
|
|
1037
|
-
}
|
|
1038
|
-
} catch {
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
function parseDublinCoreMetadata(xml, metadata) {
|
|
1042
|
-
const parser = createXmlParser();
|
|
1043
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1044
|
-
if (!doc.documentElement) return;
|
|
1045
|
-
const getText = (tagNames) => {
|
|
1046
|
-
for (const tag of tagNames) {
|
|
1047
|
-
const els = doc.getElementsByTagName(tag);
|
|
1048
|
-
if (els.length > 0) {
|
|
1049
|
-
const text = els[0].textContent?.trim();
|
|
1050
|
-
if (text) return text;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
return void 0;
|
|
1054
|
-
};
|
|
1055
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
1056
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
1057
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
1058
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
1059
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
1060
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
1061
|
-
if (keywords && !metadata.keywords) {
|
|
1062
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
async function extractHwpxMetadataOnly(buffer) {
|
|
1066
|
-
let zip;
|
|
1067
|
-
try {
|
|
1068
|
-
zip = await JSZip.loadAsync(buffer);
|
|
1069
|
-
} catch {
|
|
1070
|
-
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1071
|
-
}
|
|
1072
|
-
const metadata = {};
|
|
1073
|
-
await extractHwpxMetadata(zip, metadata);
|
|
1074
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
1075
|
-
metadata.pageCount = sectionPaths.length;
|
|
1076
|
-
return metadata;
|
|
1077
|
-
}
|
|
1078
|
-
function extractFromBrokenZip(buffer) {
|
|
1079
|
-
const data = new Uint8Array(buffer);
|
|
1080
|
-
const view = new DataView(buffer);
|
|
1081
|
-
let pos = 0;
|
|
1082
|
-
const blocks = [];
|
|
1083
|
-
const warnings = [
|
|
1084
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1085
|
-
];
|
|
1086
|
-
let totalDecompressed = 0;
|
|
1087
|
-
let entryCount = 0;
|
|
1088
|
-
let sectionNum = 0;
|
|
1089
|
-
const shared = createSectionShared();
|
|
1090
|
-
while (pos < data.length - 30) {
|
|
1091
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1092
|
-
pos++;
|
|
1093
|
-
while (pos < data.length - 30) {
|
|
1094
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1095
|
-
pos++;
|
|
1096
|
-
}
|
|
1097
|
-
continue;
|
|
1098
|
-
}
|
|
1099
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1100
|
-
const method = view.getUint16(pos + 8, true);
|
|
1101
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
1102
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
1103
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
1104
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
1105
|
-
pos += 30 + nameLen + extraLen;
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1109
|
-
if (fileStart + compSize > data.length) break;
|
|
1110
|
-
if (compSize === 0 && method !== 0) {
|
|
1111
|
-
pos = fileStart;
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1115
|
-
const name = new TextDecoder().decode(nameBytes);
|
|
1116
|
-
if (isPathTraversal(name)) {
|
|
1117
|
-
pos = fileStart + compSize;
|
|
1118
|
-
continue;
|
|
1119
|
-
}
|
|
1120
|
-
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1121
|
-
pos = fileStart + compSize;
|
|
1122
|
-
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1123
|
-
try {
|
|
1124
|
-
let content;
|
|
1125
|
-
if (method === 0) {
|
|
1126
|
-
content = new TextDecoder().decode(fileData);
|
|
1127
|
-
} else if (method === 8) {
|
|
1128
|
-
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1129
|
-
content = new TextDecoder().decode(decompressed);
|
|
1130
|
-
} else {
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
totalDecompressed += content.length * 2;
|
|
1134
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1135
|
-
sectionNum++;
|
|
1136
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1137
|
-
} catch {
|
|
1138
|
-
continue;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
if (blocks.length === 0) throw new KordocError("\uC190\uC0C1\uB41C HWPX\uC5D0\uC11C \uC139\uC158 \uB370\uC774\uD130\uB97C \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1142
|
-
applyPageText(blocks, shared);
|
|
1143
|
-
const markdown = blocksToMarkdown(blocks);
|
|
1144
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
1145
|
-
}
|
|
1146
|
-
async function resolveSectionPaths(zip) {
|
|
1147
|
-
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1148
|
-
for (const mp of manifestPaths) {
|
|
1149
|
-
const mpLower = mp.toLowerCase();
|
|
1150
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1151
|
-
if (!file) continue;
|
|
1152
|
-
const xml = await file.async("text");
|
|
1153
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
1154
|
-
if (paths.length > 0) return paths;
|
|
1155
|
-
}
|
|
1156
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
1157
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
1158
|
-
}
|
|
1159
|
-
function parseSectionPathsFromManifest(xml) {
|
|
1160
|
-
const parser = createXmlParser();
|
|
1161
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1162
|
-
const items = doc.getElementsByTagName("opf:item");
|
|
1163
|
-
const spine = doc.getElementsByTagName("opf:itemref");
|
|
1164
|
-
const idToHref = /* @__PURE__ */ new Map();
|
|
1165
|
-
for (let i = 0; i < items.length; i++) {
|
|
1166
|
-
const item = items[i];
|
|
1167
|
-
const id = item.getAttribute("id") || "";
|
|
1168
|
-
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
1169
|
-
if (id && href) idToHref.set(id, href);
|
|
1170
|
-
}
|
|
1171
|
-
if (spine.length > 0) {
|
|
1172
|
-
const ordered = [];
|
|
1173
|
-
for (let i = 0; i < spine.length; i++) {
|
|
1174
|
-
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
1175
|
-
if (href) ordered.push(href);
|
|
847
|
+
out += s;
|
|
848
|
+
n -= v;
|
|
1176
849
|
}
|
|
1177
|
-
if (ordered.length > 0) return ordered;
|
|
1178
850
|
}
|
|
1179
|
-
return
|
|
851
|
+
return out;
|
|
1180
852
|
}
|
|
1181
|
-
function
|
|
1182
|
-
if (
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
853
|
+
function formatHeadNumber(n, numFormat) {
|
|
854
|
+
if (n <= 0) n = 1;
|
|
855
|
+
switch (numFormat) {
|
|
856
|
+
case "DIGIT":
|
|
857
|
+
return String(n);
|
|
858
|
+
case "CIRCLED_DIGIT":
|
|
859
|
+
return n <= 20 ? String.fromCodePoint(9312 + n - 1) : `(${n})`;
|
|
860
|
+
case "HANGUL_SYLLABLE":
|
|
861
|
+
return HANGUL_SYLLABLE_SEQ[(n - 1) % HANGUL_SYLLABLE_SEQ.length];
|
|
862
|
+
case "CIRCLED_HANGUL_SYLLABLE":
|
|
863
|
+
return n <= 14 ? String.fromCodePoint(12910 + n - 1) : HANGUL_SYLLABLE_SEQ[(n - 1) % 14];
|
|
864
|
+
case "HANGUL_JAMO":
|
|
865
|
+
return HANGUL_JAMO_SEQ[(n - 1) % HANGUL_JAMO_SEQ.length];
|
|
866
|
+
case "CIRCLED_HANGUL_JAMO":
|
|
867
|
+
return n <= 14 ? String.fromCodePoint(12896 + n - 1) : HANGUL_JAMO_SEQ[(n - 1) % 14];
|
|
868
|
+
case "LATIN_CAPITAL":
|
|
869
|
+
return String.fromCharCode(65 + (n - 1) % 26);
|
|
870
|
+
case "LATIN_SMALL":
|
|
871
|
+
return String.fromCharCode(97 + (n - 1) % 26);
|
|
872
|
+
case "CIRCLED_LATIN_CAPITAL":
|
|
873
|
+
return n <= 26 ? String.fromCodePoint(9398 + n - 1) : String.fromCharCode(65 + (n - 1) % 26);
|
|
874
|
+
case "CIRCLED_LATIN_SMALL":
|
|
875
|
+
return n <= 26 ? String.fromCodePoint(9424 + n - 1) : String.fromCharCode(97 + (n - 1) % 26);
|
|
876
|
+
case "ROMAN_CAPITAL":
|
|
877
|
+
return toRoman(n);
|
|
878
|
+
case "ROMAN_SMALL":
|
|
879
|
+
return toRoman(n).toLowerCase();
|
|
880
|
+
default:
|
|
881
|
+
return String(n);
|
|
1189
882
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
883
|
+
}
|
|
884
|
+
function resolveParaHeading(paraEl, ctx) {
|
|
885
|
+
const sm = ctx.styleMap;
|
|
886
|
+
if (!sm) return null;
|
|
887
|
+
const prId = paraEl.getAttribute("paraPrIDRef");
|
|
888
|
+
if (!prId) return null;
|
|
889
|
+
const ref = sm.paraHeadings.get(prId);
|
|
890
|
+
if (!ref) return null;
|
|
891
|
+
if (ref.type === "BULLET") {
|
|
892
|
+
const char = sm.bullets.get(ref.idRef);
|
|
893
|
+
return char ? { prefix: char } : null;
|
|
1196
894
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
1207
|
-
}
|
|
1208
|
-
const compactText = text.replace(/\s+/g, "");
|
|
1209
|
-
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
1210
|
-
if (level === 0) level = 3;
|
|
1211
|
-
}
|
|
1212
|
-
if (level > 0) {
|
|
1213
|
-
block.type = "heading";
|
|
1214
|
-
block.level = level;
|
|
1215
|
-
}
|
|
895
|
+
const numId = ref.type === "OUTLINE" ? ctx.outlineNumId || "1" : ref.idRef;
|
|
896
|
+
const level = Math.min(ref.level + 1, 10);
|
|
897
|
+
const headingLevel = ref.type === "OUTLINE" ? Math.min(ref.level + 1, 6) : void 0;
|
|
898
|
+
const numDef = sm.numberings.get(numId);
|
|
899
|
+
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
900
|
+
let counters = ctx.shared.numState.get(numId);
|
|
901
|
+
if (!counters) {
|
|
902
|
+
counters = new Array(11).fill(0);
|
|
903
|
+
ctx.shared.numState.set(numId, counters);
|
|
1216
904
|
}
|
|
905
|
+
const head = numDef.heads.get(level);
|
|
906
|
+
counters[level] = counters[level] === 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
907
|
+
for (let l = level + 1; l <= 10; l++) counters[l] = 0;
|
|
908
|
+
const fmtText = head?.text?.trim() || `^${level}.`;
|
|
909
|
+
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
910
|
+
const lv = parseInt(d, 10);
|
|
911
|
+
const refHead = numDef.heads.get(lv);
|
|
912
|
+
const n = counters[lv] || refHead?.start || 1;
|
|
913
|
+
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
914
|
+
});
|
|
915
|
+
return { prefix, headingLevel };
|
|
1217
916
|
}
|
|
917
|
+
|
|
918
|
+
// src/hwpx/table-build.ts
|
|
1218
919
|
function buildTableWithCellMeta(state) {
|
|
1219
920
|
const table = buildTable(state.rows);
|
|
1220
921
|
if (state.caption) table.caption = state.caption;
|
|
@@ -1294,6 +995,8 @@ function completeTable(newTable, tableStack, blocks, ctx) {
|
|
|
1294
995
|
}
|
|
1295
996
|
return parentTable;
|
|
1296
997
|
}
|
|
998
|
+
|
|
999
|
+
// src/hwpx/section-walker.ts
|
|
1297
1000
|
function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
1298
1001
|
const parser = createXmlParser(warnings);
|
|
1299
1002
|
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
@@ -1827,49 +1530,370 @@ function extractParagraphInfo(para, styleMap, ctx) {
|
|
|
1827
1530
|
break;
|
|
1828
1531
|
}
|
|
1829
1532
|
}
|
|
1830
|
-
};
|
|
1831
|
-
walk(para);
|
|
1832
|
-
const leaderIdx = text.indexOf("");
|
|
1833
|
-
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
1834
|
-
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
1835
|
-
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
1836
|
-
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
1837
|
-
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
1838
|
-
let style;
|
|
1839
|
-
if (styleMap && charPrId) {
|
|
1840
|
-
const charProp = styleMap.charProperties.get(charPrId);
|
|
1841
|
-
if (charProp) {
|
|
1842
|
-
style = {};
|
|
1843
|
-
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
1844
|
-
if (charProp.bold) style.bold = true;
|
|
1845
|
-
if (charProp.italic) style.italic = true;
|
|
1846
|
-
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
1847
|
-
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
1533
|
+
};
|
|
1534
|
+
walk(para);
|
|
1535
|
+
const leaderIdx = text.indexOf("");
|
|
1536
|
+
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
1537
|
+
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
1538
|
+
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
1539
|
+
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
1540
|
+
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
1541
|
+
let style;
|
|
1542
|
+
if (styleMap && charPrId) {
|
|
1543
|
+
const charProp = styleMap.charProperties.get(charPrId);
|
|
1544
|
+
if (charProp) {
|
|
1545
|
+
style = {};
|
|
1546
|
+
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
1547
|
+
if (charProp.bold) style.bold = true;
|
|
1548
|
+
if (charProp.italic) style.italic = true;
|
|
1549
|
+
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
1550
|
+
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return { text: cleanText, href, footnote, style };
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/hwpx/images.ts
|
|
1557
|
+
function imageExtToMime(ext) {
|
|
1558
|
+
switch (ext.toLowerCase()) {
|
|
1559
|
+
case "jpg":
|
|
1560
|
+
case "jpeg":
|
|
1561
|
+
return "image/jpeg";
|
|
1562
|
+
case "png":
|
|
1563
|
+
return "image/png";
|
|
1564
|
+
case "gif":
|
|
1565
|
+
return "image/gif";
|
|
1566
|
+
case "bmp":
|
|
1567
|
+
return "image/bmp";
|
|
1568
|
+
case "tif":
|
|
1569
|
+
case "tiff":
|
|
1570
|
+
return "image/tiff";
|
|
1571
|
+
case "wmf":
|
|
1572
|
+
return "image/wmf";
|
|
1573
|
+
case "emf":
|
|
1574
|
+
return "image/emf";
|
|
1575
|
+
case "svg":
|
|
1576
|
+
return "image/svg+xml";
|
|
1577
|
+
default:
|
|
1578
|
+
return "application/octet-stream";
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function mimeToExt(mime) {
|
|
1582
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
1583
|
+
if (mime.includes("png")) return "png";
|
|
1584
|
+
if (mime.includes("gif")) return "gif";
|
|
1585
|
+
if (mime.includes("bmp")) return "bmp";
|
|
1586
|
+
if (mime.includes("tiff")) return "tif";
|
|
1587
|
+
if (mime.includes("wmf")) return "wmf";
|
|
1588
|
+
if (mime.includes("emf")) return "emf";
|
|
1589
|
+
if (mime.includes("svg")) return "svg";
|
|
1590
|
+
return "bin";
|
|
1591
|
+
}
|
|
1592
|
+
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
1593
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
1594
|
+
for (const block of blocks) {
|
|
1595
|
+
if (block.type === "image") {
|
|
1596
|
+
out.push({ block, ownerCell });
|
|
1597
|
+
} else if (block.type === "table" && block.table) {
|
|
1598
|
+
for (const row of block.table.cells) {
|
|
1599
|
+
for (const cell of row) {
|
|
1600
|
+
if (cell.blocks?.length) collectImageBlocks(cell.blocks, out, cell, depth + 1);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
|
|
1607
|
+
const images = [];
|
|
1608
|
+
let imageIndex = 0;
|
|
1609
|
+
const imageBlocks = [];
|
|
1610
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
1611
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1612
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
1613
|
+
if (block.type !== "image" || !block.text) continue;
|
|
1614
|
+
const ref = block.text;
|
|
1615
|
+
let img = resolved.get(ref);
|
|
1616
|
+
if (img === void 0) {
|
|
1617
|
+
img = null;
|
|
1618
|
+
const candidates = [
|
|
1619
|
+
`BinData/${ref}`,
|
|
1620
|
+
`Contents/BinData/${ref}`,
|
|
1621
|
+
ref
|
|
1622
|
+
// 절대 경로일 수도 있음
|
|
1623
|
+
];
|
|
1624
|
+
let resolvedPath = null;
|
|
1625
|
+
if (!ref.includes(".")) {
|
|
1626
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
1627
|
+
for (const prefix of prefixes) {
|
|
1628
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
1629
|
+
if (match.length > 0) {
|
|
1630
|
+
resolvedPath = match[0].name;
|
|
1631
|
+
break;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
1636
|
+
for (const path of allCandidates) {
|
|
1637
|
+
if (isPathTraversal(path)) continue;
|
|
1638
|
+
const file = zip.file(path);
|
|
1639
|
+
if (!file) continue;
|
|
1640
|
+
try {
|
|
1641
|
+
const data = await file.async("uint8array");
|
|
1642
|
+
decompressed.total += data.length;
|
|
1643
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1644
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
1645
|
+
const mimeType = imageExtToMime(ext);
|
|
1646
|
+
imageIndex++;
|
|
1647
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1648
|
+
img = { filename, data, mimeType };
|
|
1649
|
+
images.push(img);
|
|
1650
|
+
break;
|
|
1651
|
+
} catch (err) {
|
|
1652
|
+
if (err instanceof KordocError) throw err;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1656
|
+
resolved.set(ref, img);
|
|
1657
|
+
}
|
|
1658
|
+
if (!img) {
|
|
1659
|
+
block.type = "paragraph";
|
|
1660
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1661
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
block.text = img.filename;
|
|
1665
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
1666
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1667
|
+
}
|
|
1668
|
+
return images;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// src/hwpx/metadata.ts
|
|
1672
|
+
import JSZip from "jszip";
|
|
1673
|
+
|
|
1674
|
+
// src/hwpx/zip-sections.ts
|
|
1675
|
+
import { inflateRawSync } from "zlib";
|
|
1676
|
+
function extractFromBrokenZip(buffer) {
|
|
1677
|
+
const data = new Uint8Array(buffer);
|
|
1678
|
+
const view = new DataView(buffer);
|
|
1679
|
+
let pos = 0;
|
|
1680
|
+
const blocks = [];
|
|
1681
|
+
const warnings = [
|
|
1682
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1683
|
+
];
|
|
1684
|
+
let totalDecompressed = 0;
|
|
1685
|
+
let entryCount = 0;
|
|
1686
|
+
let sectionNum = 0;
|
|
1687
|
+
const shared = createSectionShared();
|
|
1688
|
+
while (pos < data.length - 30) {
|
|
1689
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1690
|
+
pos++;
|
|
1691
|
+
while (pos < data.length - 30) {
|
|
1692
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1693
|
+
pos++;
|
|
1694
|
+
}
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1698
|
+
const method = view.getUint16(pos + 8, true);
|
|
1699
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
1700
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
1701
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
1702
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
1703
|
+
pos += 30 + nameLen + extraLen;
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1707
|
+
if (fileStart + compSize > data.length) break;
|
|
1708
|
+
if (compSize === 0 && method !== 0) {
|
|
1709
|
+
pos = fileStart;
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1713
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
1714
|
+
if (isPathTraversal(name)) {
|
|
1715
|
+
pos = fileStart + compSize;
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1719
|
+
pos = fileStart + compSize;
|
|
1720
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1721
|
+
try {
|
|
1722
|
+
let content;
|
|
1723
|
+
if (method === 0) {
|
|
1724
|
+
content = new TextDecoder().decode(fileData);
|
|
1725
|
+
} else if (method === 8) {
|
|
1726
|
+
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1727
|
+
content = new TextDecoder().decode(decompressed);
|
|
1728
|
+
} else {
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
totalDecompressed += content.length * 2;
|
|
1732
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1733
|
+
sectionNum++;
|
|
1734
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1735
|
+
} catch {
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (blocks.length === 0) throw new KordocError("\uC190\uC0C1\uB41C HWPX\uC5D0\uC11C \uC139\uC158 \uB370\uC774\uD130\uB97C \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1740
|
+
applyPageText(blocks, shared);
|
|
1741
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1742
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
1743
|
+
}
|
|
1744
|
+
async function resolveSectionPaths(zip) {
|
|
1745
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1746
|
+
for (const mp of manifestPaths) {
|
|
1747
|
+
const mpLower = mp.toLowerCase();
|
|
1748
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1749
|
+
if (!file) continue;
|
|
1750
|
+
const xml = await file.async("text");
|
|
1751
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
1752
|
+
if (paths.length > 0) return paths;
|
|
1753
|
+
}
|
|
1754
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
1755
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
1756
|
+
}
|
|
1757
|
+
function parseSectionPathsFromManifest(xml) {
|
|
1758
|
+
const parser = createXmlParser();
|
|
1759
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1760
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
1761
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
1762
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
1763
|
+
for (let i = 0; i < items.length; i++) {
|
|
1764
|
+
const item = items[i];
|
|
1765
|
+
const id = item.getAttribute("id") || "";
|
|
1766
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
1767
|
+
if (id && href) idToHref.set(id, href);
|
|
1768
|
+
}
|
|
1769
|
+
if (spine.length > 0) {
|
|
1770
|
+
const ordered = [];
|
|
1771
|
+
for (let i = 0; i < spine.length; i++) {
|
|
1772
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
1773
|
+
if (href) ordered.push(href);
|
|
1848
1774
|
}
|
|
1775
|
+
if (ordered.length > 0) return ordered;
|
|
1849
1776
|
}
|
|
1850
|
-
return
|
|
1777
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
1851
1778
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1779
|
+
|
|
1780
|
+
// src/hwpx/metadata.ts
|
|
1781
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
1782
|
+
try {
|
|
1783
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
1784
|
+
for (const mp of metaPaths) {
|
|
1785
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
1786
|
+
if (!file) continue;
|
|
1787
|
+
const xml = await file.async("text");
|
|
1788
|
+
if (decompressed) {
|
|
1789
|
+
decompressed.total += xml.length * 2;
|
|
1790
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1791
|
+
}
|
|
1792
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
1793
|
+
if (metadata.title || metadata.author) return;
|
|
1794
|
+
}
|
|
1795
|
+
} catch {
|
|
1860
1796
|
}
|
|
1861
|
-
return null;
|
|
1862
1797
|
}
|
|
1863
|
-
function
|
|
1864
|
-
|
|
1865
|
-
const
|
|
1866
|
-
if (!
|
|
1867
|
-
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1798
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
1799
|
+
const parser = createXmlParser();
|
|
1800
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1801
|
+
if (!doc.documentElement) return;
|
|
1802
|
+
const getText = (tagNames) => {
|
|
1803
|
+
for (const tag of tagNames) {
|
|
1804
|
+
const els = doc.getElementsByTagName(tag);
|
|
1805
|
+
if (els.length > 0) {
|
|
1806
|
+
const text = els[0].textContent?.trim();
|
|
1807
|
+
if (text) return text;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return void 0;
|
|
1811
|
+
};
|
|
1812
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
1813
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
1814
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
1815
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
1816
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
1817
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
1818
|
+
if (keywords && !metadata.keywords) {
|
|
1819
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
1871
1820
|
}
|
|
1872
|
-
|
|
1821
|
+
}
|
|
1822
|
+
async function extractHwpxMetadataOnly(buffer) {
|
|
1823
|
+
let zip;
|
|
1824
|
+
try {
|
|
1825
|
+
zip = await JSZip.loadAsync(buffer);
|
|
1826
|
+
} catch {
|
|
1827
|
+
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1828
|
+
}
|
|
1829
|
+
const metadata = {};
|
|
1830
|
+
await extractHwpxMetadata(zip, metadata);
|
|
1831
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
1832
|
+
metadata.pageCount = sectionPaths.length;
|
|
1833
|
+
return metadata;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// src/hwpx/parser.ts
|
|
1837
|
+
async function parseHwpxDocument(buffer, options) {
|
|
1838
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
1839
|
+
let zip;
|
|
1840
|
+
try {
|
|
1841
|
+
zip = await JSZip2.loadAsync(buffer);
|
|
1842
|
+
} catch {
|
|
1843
|
+
return extractFromBrokenZip(buffer);
|
|
1844
|
+
}
|
|
1845
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
1846
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
1847
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1848
|
+
}
|
|
1849
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
1850
|
+
if (manifestFile) {
|
|
1851
|
+
const manifestXml = await manifestFile.async("text");
|
|
1852
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
1853
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
1854
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
1855
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
1856
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
throw new KordocError("DRM \uC554\uD638\uD654\uB41C HWPX \uD30C\uC77C\uC785\uB2C8\uB2E4. Windows + \uD55C\uCEF4 \uC624\uD53C\uC2A4 \uC124\uCE58 \uC2DC \uC790\uB3D9 \uCD94\uCD9C\uB429\uB2C8\uB2E4.");
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
const decompressed = { total: 0 };
|
|
1863
|
+
const metadata = {};
|
|
1864
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
1865
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
1866
|
+
const warnings = [];
|
|
1867
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
1868
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1869
|
+
metadata.pageCount = sectionPaths.length;
|
|
1870
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
1871
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
1872
|
+
const blocks = [];
|
|
1873
|
+
const shared = createSectionShared();
|
|
1874
|
+
let parsedSections = 0;
|
|
1875
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
1876
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
1877
|
+
const file = zip.file(sectionPaths[si]);
|
|
1878
|
+
if (!file) continue;
|
|
1879
|
+
try {
|
|
1880
|
+
const xml = await file.async("text");
|
|
1881
|
+
decompressed.total += xml.length * 2;
|
|
1882
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1883
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
1884
|
+
parsedSections++;
|
|
1885
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
1886
|
+
} catch (secErr) {
|
|
1887
|
+
if (secErr instanceof KordocError) throw secErr;
|
|
1888
|
+
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
applyPageText(blocks, shared);
|
|
1892
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings);
|
|
1893
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
1894
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
1895
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1896
|
+
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
1873
1897
|
}
|
|
1874
1898
|
|
|
1875
1899
|
// src/hwp5/record.ts
|
|
@@ -2396,32 +2420,42 @@ function resolveImageBlocks(binDataMap, blocks, warnings) {
|
|
|
2396
2420
|
if (imageBlocks.length === 0) return [];
|
|
2397
2421
|
const images = [];
|
|
2398
2422
|
const renamed = /* @__PURE__ */ new Map();
|
|
2423
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2399
2424
|
let imageIndex = 0;
|
|
2400
2425
|
for (const block of imageBlocks) {
|
|
2401
2426
|
if (!block.text) continue;
|
|
2402
2427
|
const storageId = parseInt(block.text, 10);
|
|
2403
2428
|
if (isNaN(storageId)) continue;
|
|
2404
|
-
|
|
2405
|
-
if (
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2429
|
+
let img = resolved.get(storageId);
|
|
2430
|
+
if (img === void 0) {
|
|
2431
|
+
const bin = binDataMap.get(storageId);
|
|
2432
|
+
if (!bin) {
|
|
2433
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
2434
|
+
resolved.set(storageId, null);
|
|
2435
|
+
} else {
|
|
2436
|
+
const mime = detectImageMime(bin.data);
|
|
2437
|
+
if (!mime) {
|
|
2438
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2439
|
+
resolved.set(storageId, null);
|
|
2440
|
+
} else {
|
|
2441
|
+
imageIndex++;
|
|
2442
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2443
|
+
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
2444
|
+
resolved.set(storageId, img);
|
|
2445
|
+
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
2446
|
+
renamed.set(storageId, img.filename);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
img = resolved.get(storageId);
|
|
2410
2450
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2451
|
+
if (!img) {
|
|
2452
|
+
const bin = binDataMap.get(storageId);
|
|
2414
2453
|
block.type = "paragraph";
|
|
2415
|
-
block.text = `[\uC774\uBBF8\uC9C0: ${bin.name}]`;
|
|
2454
|
+
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
2416
2455
|
continue;
|
|
2417
2456
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
2421
|
-
images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime });
|
|
2422
|
-
renamed.set(storageId, filename);
|
|
2423
|
-
block.text = filename;
|
|
2424
|
-
block.imageData = { data: new Uint8Array(bin.data), mimeType: mime, filename: bin.name };
|
|
2457
|
+
block.text = img.filename;
|
|
2458
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
2425
2459
|
}
|
|
2426
2460
|
resolveCellImageSentinels(blocks, renamed);
|
|
2427
2461
|
return images;
|
|
@@ -16515,7 +16549,7 @@ function isDistributionSentinel(markdown) {
|
|
|
16515
16549
|
}
|
|
16516
16550
|
|
|
16517
16551
|
// src/xlsx/parser.ts
|
|
16518
|
-
import
|
|
16552
|
+
import JSZip3 from "jszip";
|
|
16519
16553
|
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
16520
16554
|
var MAX_SHEETS = 100;
|
|
16521
16555
|
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
@@ -16706,7 +16740,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
|
16706
16740
|
}
|
|
16707
16741
|
async function parseXlsxDocument(buffer, options) {
|
|
16708
16742
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
16709
|
-
const zip = await
|
|
16743
|
+
const zip = await JSZip3.loadAsync(buffer);
|
|
16710
16744
|
const warnings = [];
|
|
16711
16745
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
16712
16746
|
if (!workbookFile) {
|
|
@@ -17412,7 +17446,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
17412
17446
|
}
|
|
17413
17447
|
|
|
17414
17448
|
// src/docx/parser.ts
|
|
17415
|
-
import
|
|
17449
|
+
import JSZip4 from "jszip";
|
|
17416
17450
|
import { DOMParser as DOMParser3 } from "@xmldom/xmldom";
|
|
17417
17451
|
|
|
17418
17452
|
// src/docx/equation.ts
|
|
@@ -18123,7 +18157,7 @@ function parseTable(tbl, styles, numbering, footnotes, rels) {
|
|
|
18123
18157
|
};
|
|
18124
18158
|
return { type: "table", table };
|
|
18125
18159
|
}
|
|
18126
|
-
async function extractImages(zip, rels, doc) {
|
|
18160
|
+
async function extractImages(zip, rels, doc, warnings) {
|
|
18127
18161
|
const blocks = [];
|
|
18128
18162
|
const images = [];
|
|
18129
18163
|
const drawingElements = findElements(doc.documentElement, "drawing");
|
|
@@ -18154,7 +18188,11 @@ async function extractImages(zip, rels, doc) {
|
|
|
18154
18188
|
const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
|
|
18155
18189
|
images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
|
|
18156
18190
|
blocks.push({ type: "image", text: filename });
|
|
18157
|
-
} catch {
|
|
18191
|
+
} catch (err) {
|
|
18192
|
+
warnings.push({
|
|
18193
|
+
code: "SKIPPED_IMAGE",
|
|
18194
|
+
message: `DOCX \uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328 (${imgPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
18195
|
+
});
|
|
18158
18196
|
}
|
|
18159
18197
|
}
|
|
18160
18198
|
}
|
|
@@ -18162,7 +18200,7 @@ async function extractImages(zip, rels, doc) {
|
|
|
18162
18200
|
}
|
|
18163
18201
|
async function parseDocxDocument(buffer, options) {
|
|
18164
18202
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
18165
|
-
const zip = await
|
|
18203
|
+
const zip = await JSZip4.loadAsync(buffer);
|
|
18166
18204
|
const warnings = [];
|
|
18167
18205
|
const docFile = zip.file("word/document.xml");
|
|
18168
18206
|
if (!docFile) {
|
|
@@ -18178,7 +18216,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18178
18216
|
if (stylesFile) {
|
|
18179
18217
|
try {
|
|
18180
18218
|
styles = parseStyles(await stylesFile.async("text"));
|
|
18181
|
-
} catch {
|
|
18219
|
+
} catch (err) {
|
|
18220
|
+
warnings.push({
|
|
18221
|
+
code: "PARTIAL_PARSE",
|
|
18222
|
+
message: `DOCX \uC2A4\uD0C0\uC77C(styles.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uAE30\uBCF8 \uC2A4\uD0C0\uC77C\uB85C \uACC4\uC18D: ${err instanceof Error ? err.message : String(err)}`
|
|
18223
|
+
});
|
|
18182
18224
|
}
|
|
18183
18225
|
}
|
|
18184
18226
|
let numbering = /* @__PURE__ */ new Map();
|
|
@@ -18186,7 +18228,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18186
18228
|
if (numFile) {
|
|
18187
18229
|
try {
|
|
18188
18230
|
numbering = parseNumbering(await numFile.async("text"));
|
|
18189
|
-
} catch {
|
|
18231
|
+
} catch (err) {
|
|
18232
|
+
warnings.push({
|
|
18233
|
+
code: "PARTIAL_PARSE",
|
|
18234
|
+
message: `DOCX \uBC88\uD638\uB9E4\uAE30\uAE30(numbering.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uBAA9\uB85D \uBC88\uD638 \uC0DD\uB7B5: ${err instanceof Error ? err.message : String(err)}`
|
|
18235
|
+
});
|
|
18190
18236
|
}
|
|
18191
18237
|
}
|
|
18192
18238
|
let footnotes = /* @__PURE__ */ new Map();
|
|
@@ -18194,7 +18240,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18194
18240
|
if (fnFile) {
|
|
18195
18241
|
try {
|
|
18196
18242
|
footnotes = parseFootnotes(await fnFile.async("text"));
|
|
18197
|
-
} catch {
|
|
18243
|
+
} catch (err) {
|
|
18244
|
+
warnings.push({
|
|
18245
|
+
code: "PARTIAL_PARSE",
|
|
18246
|
+
message: `DOCX \uAC01\uC8FC(footnotes.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uAC01\uC8FC \uC0DD\uB7B5: ${err instanceof Error ? err.message : String(err)}`
|
|
18247
|
+
});
|
|
18198
18248
|
}
|
|
18199
18249
|
}
|
|
18200
18250
|
const docXml = await docFile.async("text");
|
|
@@ -18216,7 +18266,7 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18216
18266
|
if (block) blocks.push(block);
|
|
18217
18267
|
}
|
|
18218
18268
|
}
|
|
18219
|
-
const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc);
|
|
18269
|
+
const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc, warnings);
|
|
18220
18270
|
const metadata = {};
|
|
18221
18271
|
const coreFile = zip.file("docProps/core.xml");
|
|
18222
18272
|
if (coreFile) {
|
|
@@ -18234,7 +18284,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18234
18284
|
if (created) metadata.createdAt = created;
|
|
18235
18285
|
const modified = getFirst("dcterms:modified");
|
|
18236
18286
|
if (modified) metadata.modifiedAt = modified;
|
|
18237
|
-
} catch {
|
|
18287
|
+
} catch (err) {
|
|
18288
|
+
warnings.push({
|
|
18289
|
+
code: "PARTIAL_PARSE",
|
|
18290
|
+
message: `DOCX \uBA54\uD0C0\uB370\uC774\uD130(core.xml) \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
|
|
18291
|
+
});
|
|
18238
18292
|
}
|
|
18239
18293
|
}
|
|
18240
18294
|
const outline = blocks.filter((b) => b.type === "heading").map((b) => ({ level: b.level ?? 2, text: b.text ?? "" }));
|
|
@@ -19064,7 +19118,7 @@ function fillInlineFields(text, values, filled, matchedLabels) {
|
|
|
19064
19118
|
}
|
|
19065
19119
|
|
|
19066
19120
|
// src/form/filler-hwpx.ts
|
|
19067
|
-
import
|
|
19121
|
+
import JSZip5 from "jszip";
|
|
19068
19122
|
|
|
19069
19123
|
// src/roundtrip/source-map.ts
|
|
19070
19124
|
function escapeXmlText(text) {
|
|
@@ -19738,7 +19792,7 @@ function patchZipEntries(original, replacements) {
|
|
|
19738
19792
|
// src/form/filler-hwpx.ts
|
|
19739
19793
|
async function fillHwpx(hwpxBuffer, values) {
|
|
19740
19794
|
const u8 = new Uint8Array(hwpxBuffer);
|
|
19741
|
-
const zip = await
|
|
19795
|
+
const zip = await JSZip5.loadAsync(hwpxBuffer);
|
|
19742
19796
|
const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
|
|
19743
19797
|
if (sectionPaths.length === 0) {
|
|
19744
19798
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -19979,7 +20033,7 @@ async function fillHwpx(hwpxBuffer, values) {
|
|
|
19979
20033
|
}
|
|
19980
20034
|
|
|
19981
20035
|
// src/hwpx/generator.ts
|
|
19982
|
-
import
|
|
20036
|
+
import JSZip6 from "jszip";
|
|
19983
20037
|
|
|
19984
20038
|
// src/hwpx/text-metrics.ts
|
|
19985
20039
|
var ASCII_W = [
|
|
@@ -20815,7 +20869,7 @@ async function markdownToHwpx(markdown, options) {
|
|
|
20815
20869
|
const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
|
|
20816
20870
|
const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
|
|
20817
20871
|
const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
|
|
20818
|
-
const zip = new
|
|
20872
|
+
const zip = new JSZip6();
|
|
20819
20873
|
zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
|
|
20820
20874
|
zip.file("META-INF/container.xml", generateContainerXml());
|
|
20821
20875
|
zip.file("Contents/content.hpf", generateManifest());
|
|
@@ -21359,9 +21413,22 @@ function precomputeGongmunList(blocks, gongmun) {
|
|
|
21359
21413
|
continue;
|
|
21360
21414
|
}
|
|
21361
21415
|
const run = [];
|
|
21362
|
-
while (i < blocks.length
|
|
21363
|
-
|
|
21364
|
-
|
|
21416
|
+
while (i < blocks.length) {
|
|
21417
|
+
const t = blocks[i].type;
|
|
21418
|
+
if (t === "list_item") {
|
|
21419
|
+
run.push(i);
|
|
21420
|
+
i++;
|
|
21421
|
+
continue;
|
|
21422
|
+
}
|
|
21423
|
+
if (t === "table" || t === "html_table") {
|
|
21424
|
+
let j = i + 1;
|
|
21425
|
+
while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
|
|
21426
|
+
if (j < blocks.length && blocks[j].type === "list_item") {
|
|
21427
|
+
i = j;
|
|
21428
|
+
continue;
|
|
21429
|
+
}
|
|
21430
|
+
}
|
|
21431
|
+
break;
|
|
21365
21432
|
}
|
|
21366
21433
|
const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
|
|
21367
21434
|
const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
|
|
@@ -21629,7 +21696,7 @@ function diffTableCells(a, b) {
|
|
|
21629
21696
|
}
|
|
21630
21697
|
|
|
21631
21698
|
// src/roundtrip/patcher.ts
|
|
21632
|
-
import
|
|
21699
|
+
import JSZip7 from "jszip";
|
|
21633
21700
|
|
|
21634
21701
|
// src/roundtrip/table-rows.ts
|
|
21635
21702
|
var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
|
|
@@ -22267,7 +22334,7 @@ async function patchHwpx(original, editedMarkdown, options) {
|
|
|
22267
22334
|
}
|
|
22268
22335
|
let zip;
|
|
22269
22336
|
try {
|
|
22270
|
-
zip = await
|
|
22337
|
+
zip = await JSZip7.loadAsync(original);
|
|
22271
22338
|
} catch {
|
|
22272
22339
|
return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
|
|
22273
22340
|
}
|
|
@@ -22930,21 +22997,24 @@ function readRecordsStrict(stream) {
|
|
|
22930
22997
|
}
|
|
22931
22998
|
return recs;
|
|
22932
22999
|
}
|
|
22933
|
-
function serializeRecords(recs, repl) {
|
|
23000
|
+
function serializeRecords(recs, repl, inserts) {
|
|
22934
23001
|
const parts = [];
|
|
22935
|
-
|
|
22936
|
-
const data = repl?.get(i) ?? recs[i].data;
|
|
23002
|
+
const push = (tagId, level, data) => {
|
|
22937
23003
|
const ext = data.length >= 4095;
|
|
22938
23004
|
const header = Buffer.alloc(ext ? 8 : 4);
|
|
22939
|
-
header.writeUInt32LE((
|
|
23005
|
+
header.writeUInt32LE((tagId & 1023 | (level & 1023) << 10 | (ext ? 4095 : data.length) << 20) >>> 0, 0);
|
|
22940
23006
|
if (ext) header.writeUInt32LE(data.length, 4);
|
|
22941
23007
|
parts.push(header, data);
|
|
23008
|
+
};
|
|
23009
|
+
for (let i = 0; i < recs.length; i++) {
|
|
23010
|
+
for (const ins of inserts?.get(i) ?? []) push(ins.tagId, ins.level, ins.data);
|
|
23011
|
+
push(recs[i].tagId, recs[i].level, repl?.get(i) ?? recs[i].data);
|
|
22942
23012
|
}
|
|
22943
23013
|
return Buffer.concat(parts);
|
|
22944
23014
|
}
|
|
22945
23015
|
function scanSection(stream, sectionIndex, compressed) {
|
|
22946
23016
|
const records = readRecordsStrict(stream);
|
|
22947
|
-
if (!records) return { records: [], safe: false, paras: [], tables: [], compressed, repl: /* @__PURE__ */ new Map() };
|
|
23017
|
+
if (!records) return { records: [], safe: false, paras: [], tables: [], compressed, repl: /* @__PURE__ */ new Map(), inserts: /* @__PURE__ */ new Map() };
|
|
22948
23018
|
const safe = serializeRecords(records).equals(stream);
|
|
22949
23019
|
const parent = new Int32Array(records.length).fill(-1);
|
|
22950
23020
|
const stack = [];
|
|
@@ -23043,7 +23113,7 @@ function scanSection(stream, sectionIndex, compressed) {
|
|
|
23043
23113
|
}
|
|
23044
23114
|
tables.push({ sectionIndex, rows, cols, cells });
|
|
23045
23115
|
}
|
|
23046
|
-
return { records, safe, paras, tables, compressed, repl: /* @__PURE__ */ new Map() };
|
|
23116
|
+
return { records, safe, paras, tables, compressed, repl: /* @__PURE__ */ new Map(), inserts: /* @__PURE__ */ new Map() };
|
|
23047
23117
|
}
|
|
23048
23118
|
async function patchHwp(original, editedMarkdown, options) {
|
|
23049
23119
|
const skipped = [];
|
|
@@ -23111,15 +23181,15 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
23111
23181
|
}
|
|
23112
23182
|
}
|
|
23113
23183
|
let data;
|
|
23114
|
-
const dirty = scans.some((s) => s.repl.size > 0);
|
|
23184
|
+
const dirty = scans.some((s) => s.repl.size > 0 || s.inserts.size > 0);
|
|
23115
23185
|
if (!dirty) {
|
|
23116
23186
|
data = new Uint8Array(original);
|
|
23117
23187
|
} else {
|
|
23118
23188
|
try {
|
|
23119
23189
|
let out = originalBuf;
|
|
23120
23190
|
for (let i = 0; i < scans.length; i++) {
|
|
23121
|
-
if (scans[i].repl.size === 0) continue;
|
|
23122
|
-
const newStream = serializeRecords(scans[i].records, scans[i].repl);
|
|
23191
|
+
if (scans[i].repl.size === 0 && scans[i].inserts.size === 0) continue;
|
|
23192
|
+
const newStream = serializeRecords(scans[i].records, scans[i].repl, scans[i].inserts);
|
|
23123
23193
|
const content = compressed ? deflateRawSync2(newStream) : newStream;
|
|
23124
23194
|
out = replaceOleStream(out, sectionPaths[i], content);
|
|
23125
23195
|
}
|
|
@@ -23447,24 +23517,25 @@ function applyCellEdit5(table, scanTable, gridR, gridC, newLines, ctx, before, a
|
|
|
23447
23517
|
}
|
|
23448
23518
|
const unstable = newLines.find((l) => sanitizeText(l) !== l);
|
|
23449
23519
|
if (unstable !== void 0) return skip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uD328\uCE58 \uC2DC \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
23450
|
-
|
|
23520
|
+
const targets = nonEmpty.length > 0 ? nonEmpty : cell.paras;
|
|
23521
|
+
if (targets.length === 0) return skip("\uC140\uC5D0 \uBB38\uB2E8\uC774 \uC5C6\uC74C \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23451
23522
|
const assigned = [];
|
|
23452
|
-
for (let i = 0; i <
|
|
23523
|
+
for (let i = 0; i < targets.length; i++) {
|
|
23453
23524
|
if (i < newLines.length) {
|
|
23454
|
-
assigned.push(i ===
|
|
23525
|
+
assigned.push(i === targets.length - 1 && newLines.length > targets.length ? newLines.slice(i).join(" ") : newLines[i]);
|
|
23455
23526
|
} else {
|
|
23456
23527
|
assigned.push("");
|
|
23457
23528
|
}
|
|
23458
23529
|
}
|
|
23459
|
-
if (newLines.length >
|
|
23530
|
+
if (newLines.length > targets.length) {
|
|
23460
23531
|
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
|
|
23461
23532
|
} else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
|
|
23462
23533
|
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before), after: summarize(after), partial: true });
|
|
23463
23534
|
}
|
|
23464
23535
|
let staged = 0;
|
|
23465
|
-
for (let i = 0; i <
|
|
23466
|
-
if (assigned[i] ===
|
|
23467
|
-
staged += stageParaPatch(ctx.scans[
|
|
23536
|
+
for (let i = 0; i < targets.length; i++) {
|
|
23537
|
+
if (assigned[i] === targets[i].rawText || normForMatch(assigned[i]) === normForMatch(targets[i].rawText)) continue;
|
|
23538
|
+
staged += stageParaPatch(ctx.scans[targets[i].sectionIndex], targets[i], assigned[i], skip);
|
|
23468
23539
|
}
|
|
23469
23540
|
return staged > 0 ? 1 : 0;
|
|
23470
23541
|
}
|
|
@@ -23534,7 +23605,21 @@ function splitParaText(data) {
|
|
|
23534
23605
|
if (firstP < 0) firstP = k;
|
|
23535
23606
|
lastP = k;
|
|
23536
23607
|
}
|
|
23537
|
-
if (firstP < 0)
|
|
23608
|
+
if (firstP < 0) {
|
|
23609
|
+
if (toks.some((t) => t.visible)) return null;
|
|
23610
|
+
let cut = data.length;
|
|
23611
|
+
for (const t of toks) if (data.readUInt16LE(t.start) === 13) {
|
|
23612
|
+
cut = t.start;
|
|
23613
|
+
break;
|
|
23614
|
+
}
|
|
23615
|
+
return {
|
|
23616
|
+
prefix: data.subarray(0, cut),
|
|
23617
|
+
prefixUnits: cut / 2,
|
|
23618
|
+
core: "",
|
|
23619
|
+
suffix: data.subarray(cut),
|
|
23620
|
+
suffixUnits: (data.length - cut) / 2
|
|
23621
|
+
};
|
|
23622
|
+
}
|
|
23538
23623
|
for (let k = firstP; k <= lastP; k++) if (!toks[k].plain) return null;
|
|
23539
23624
|
for (let k = 0; k < firstP; k++) if (toks[k].visible) return null;
|
|
23540
23625
|
for (let k = lastP + 1; k < toks.length; k++) if (toks[k].visible) return null;
|
|
@@ -23567,7 +23652,6 @@ function rebuildCharShape(csData, coreStartUnit) {
|
|
|
23567
23652
|
}
|
|
23568
23653
|
function stageParaPatch(scan, para, newPlain, skip) {
|
|
23569
23654
|
if (!scan.safe) return skip("\uC139\uC158 \uB808\uCF54\uB4DC \uC7AC\uC9C1\uB82C\uD654 \uBD88\uC77C\uCE58 \u2014 \uC548\uC804\uC744 \uC704\uD574 \uC774 \uC139\uC158\uC740 \uBBF8\uC9C0\uC6D0");
|
|
23570
|
-
if (para.textIdx === -1) return skip("\uBE48 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
23571
23655
|
if (para.textIdx === -2) return skip("\uBCF5\uC218 PARA_TEXT \uB808\uCF54\uB4DC \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
23572
23656
|
if (para.rangeTagCount > 0) return skip("\uBC94\uC704 \uD0DC\uADF8(\uD615\uAD11\uD39C/\uAD50\uC815\uBD80\uD638) \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
23573
23657
|
if (para.charShapeIdx < 0 || para.lineSegIdx < 0) return skip("\uBB38\uB2E8 \uB808\uCF54\uB4DC \uAD6C\uC131 \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
@@ -23575,11 +23659,27 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
23575
23659
|
if (/[\u0000-\u001f]/.test(newPlain)) return skip("\uC0C8 \uD14D\uC2A4\uD2B8\uC5D0 \uC81C\uC5B4\uBB38\uC790 \uD3EC\uD568 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23576
23660
|
const records = scan.records;
|
|
23577
23661
|
const headerRec = records[para.headerIdx];
|
|
23578
|
-
const textRec = records[para.textIdx];
|
|
23579
23662
|
const charShapeRec = records[para.charShapeIdx];
|
|
23580
23663
|
if (charShapeRec.data.length < 8) {
|
|
23581
23664
|
return skip("CHAR_SHAPE \uB808\uCF54\uB4DC \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23582
23665
|
}
|
|
23666
|
+
if (para.textIdx === -1) {
|
|
23667
|
+
const nCharsLow = para.nCharsRaw & 2147483647;
|
|
23668
|
+
if (nCharsLow > 1) return skip("PARA_TEXT \uC5C6\uB294 \uBB38\uB2E8\uC758 nChars \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23669
|
+
const paraEnd = nCharsLow === 1 ? Buffer.from([13, 0]) : Buffer.alloc(0);
|
|
23670
|
+
const at = para.headerIdx + 1;
|
|
23671
|
+
const list = scan.inserts.get(at) ?? [];
|
|
23672
|
+
list.push({ tagId: TAG_PARA_TEXT, level: headerRec.level + 1, data: Buffer.concat([Buffer.from(newPlain, "utf16le"), paraEnd]) });
|
|
23673
|
+
scan.inserts.set(at, list);
|
|
23674
|
+
const newHeader2 = Buffer.from(headerRec.data);
|
|
23675
|
+
newHeader2.writeUInt32LE((para.nCharsRaw & 2147483648 | newPlain.length + nCharsLow) >>> 0, 0);
|
|
23676
|
+
const cs2 = rebuildCharShape(charShapeRec.data, 0);
|
|
23677
|
+
scan.repl.set(para.charShapeIdx, cs2.buf);
|
|
23678
|
+
newHeader2.writeUInt16LE(cs2.count, 12);
|
|
23679
|
+
scan.repl.set(para.headerIdx, newHeader2);
|
|
23680
|
+
return 1;
|
|
23681
|
+
}
|
|
23682
|
+
const textRec = records[para.textIdx];
|
|
23583
23683
|
const seg = splitParaText(textRec.data);
|
|
23584
23684
|
if (!seg) {
|
|
23585
23685
|
return skip(para.ctrlMask !== 0 ? "\uCEE8\uD2B8\uB864 \uBB38\uC790(\uD0ED/\uD544\uB4DC/\uD2B9\uC218\uACF5\uBC31 \uB4F1 \uD14D\uC2A4\uD2B8 \uC911\uAC04) \uD3EC\uD568 \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)" : "PARA_TEXT \uC7AC\uAD6C\uC131 \uBD88\uC77C\uCE58 \u2014 \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
@@ -23601,10 +23701,10 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
23601
23701
|
}
|
|
23602
23702
|
|
|
23603
23703
|
// src/roundtrip/session.ts
|
|
23604
|
-
import
|
|
23704
|
+
import JSZip8 from "jszip";
|
|
23605
23705
|
async function buildState(bytes) {
|
|
23606
23706
|
const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
|
|
23607
|
-
const zip = await
|
|
23707
|
+
const zip = await JSZip8.loadAsync(bytes);
|
|
23608
23708
|
const sectionPaths = await resolveSectionEntryNames(zip);
|
|
23609
23709
|
if (sectionPaths.length === 0) {
|
|
23610
23710
|
throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -24185,7 +24285,7 @@ async function parseHwp(buffer, options) {
|
|
|
24185
24285
|
async function parsePdf(buffer, options) {
|
|
24186
24286
|
let parsePdfDocument;
|
|
24187
24287
|
try {
|
|
24188
|
-
const mod = await import("./parser-
|
|
24288
|
+
const mod = await import("./parser-FFEBMLSH.js");
|
|
24189
24289
|
parsePdfDocument = mod.parsePdfDocument;
|
|
24190
24290
|
} catch {
|
|
24191
24291
|
return {
|
|
@@ -24320,4 +24420,4 @@ export {
|
|
|
24320
24420
|
parseHwpml,
|
|
24321
24421
|
fillForm
|
|
24322
24422
|
};
|
|
24323
|
-
//# sourceMappingURL=chunk-
|
|
24423
|
+
//# sourceMappingURL=chunk-KT2BCHXI.js.map
|