kordoc 2.4.1 → 2.5.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 CHANGED
@@ -31,6 +31,12 @@ Windows 도 자동으로 `cmd /c npx` 래핑. 수동 JSON 편집 불필요. 재
31
31
 
32
32
  > **CLI 로만 쓸 거면** 설치 없이 `npx kordoc <파일>` 바로 사용. 아래 [CLI](#cli) 섹션 참고.
33
33
 
34
+ > **`MODULE_NOT_FOUND` / `Cannot find module ...\dist\cli.js` 가 뜨면**: 과거에 깨진 글로벌 설치가 남아있는 상태입니다. 아래로 해결:
35
+ > ```powershell
36
+ > npm uninstall -g kordoc
37
+ > npx -y kordoc@latest setup
38
+ > ```
39
+
34
40
  ---
35
41
 
36
42
  ## 💡 kordoc으로 무엇을 할 수 있나요?
@@ -46,10 +52,18 @@ Windows 도 자동으로 `cmd /c npx` 래핑. 수동 JSON 편집 불필요. 재
46
52
 
47
53
  ---
48
54
 
49
- ## v2.4.0 변경사항
55
+ ## v2.5.0 변경사항
56
+
57
+ - **🏛️ macOS 한컴오피스 호환 HWPX 생성** (#4) — `markdownToHwpx()` 가 만든 HWPX 가 macOS 한컴에서 "파일이 깨졌다"며 거부되던 문제 해결. 테이블 XML 을 최소 스켈레톤에서 완전 스펙 형태로 재작성 — `<hp:tbl>` 필수 속성 10종 + `<hp:sz>`/`<hp:pos>`/`<hp:outMargin>`/`<hp:inMargin>`, `<hp:tc>` 안에 `<hp:subList>` 래퍼 + `<hp:cellAddr>`/`<hp:cellSpan>`/`<hp:cellSz>`/`<hp:cellMargin>`, paragraph 래핑. `Preview/PrvText.txt` 추가 + `borderFill` id=1(SOLID 0.12mm) 추가.
58
+ - **🔓 HWP 5.x 배포용 문서 COM fallback** (#25) — `.hwp` 바이너리에서 "이 문서는 상위 버전의 배포용 문서입니다..." 경고 플레이스홀더만 나오는 케이스에서, Windows + 한컴오피스 환경이면 자동으로 `HWPFrame.HwpObject` COM API 로 재시도. v2.4.0 의 HWPX DRM fallback 인프라를 `.hwp` 에도 확장.
59
+
60
+ <details>
61
+ <summary>v2.4.0 변경사항</summary>
50
62
 
51
63
  - **🔓 HWPX DRM 배포용 문서 자동 추출** — 공공기관 배포용 DRM이 걸린 HWPX 파일을 한컴 오피스 COM API로 자동 텍스트 추출. `manifest.xml`에서 암호화 감지 → `HWPFrame.HwpObject`의 `GetPageText`로 페이지별 추출 → Markdown 변환. Windows + 한컴 오피스 설치 환경에서 별도 설정 없이 동작.
52
64
 
65
+ </details>
66
+
53
67
  <details>
54
68
  <summary>v2.3.0 변경사항</summary>
55
69
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/utils.ts
4
- var VERSION = true ? "2.4.1" : "0.0.0-dev";
4
+ var VERSION = true ? "2.5.1" : "0.0.0-dev";
5
5
  function toArrayBuffer(buf) {
6
6
  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
7
7
  return buf.buffer;
@@ -454,4 +454,4 @@ export {
454
454
  HEADING_RATIO_H2,
455
455
  HEADING_RATIO_H3
456
456
  };
457
- //# sourceMappingURL=chunk-VYFIAYCW.js.map
457
+ //# sourceMappingURL=chunk-KO7DKAXW.js.map
@@ -1,5 +1,5 @@
1
1
  // src/utils.ts
2
- var VERSION = true ? "2.4.1" : "0.0.0-dev";
2
+ var VERSION = true ? "2.5.1" : "0.0.0-dev";
3
3
  function toArrayBuffer(buf) {
4
4
  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
5
5
  return buf.buffer;
@@ -447,4 +447,4 @@ export {
447
447
  HEADING_RATIO_H2,
448
448
  HEADING_RATIO_H3
449
449
  };
450
- //# sourceMappingURL=chunk-T65PPCNU.js.map
450
+ //# sourceMappingURL=chunk-OCVWJSG7.js.map
@@ -20,7 +20,7 @@ import {
20
20
  sanitizeHref,
21
21
  stripDtd,
22
22
  toArrayBuffer
23
- } from "./chunk-VYFIAYCW.js";
23
+ } from "./chunk-KO7DKAXW.js";
24
24
  import {
25
25
  parsePageRange
26
26
  } from "./chunk-MOL7MDBG.js";
@@ -3342,8 +3342,21 @@ async function markdownToHwpx(markdown) {
3342
3342
  zip.file("Contents/content.hpf", generateManifest());
3343
3343
  zip.file("Contents/header.xml", generateHeaderXml());
3344
3344
  zip.file("Contents/section0.xml", sectionXml);
3345
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
3345
3346
  return await zip.generateAsync({ type: "arraybuffer" });
3346
3347
  }
3348
+ function buildPrvText(blocks) {
3349
+ const lines = [];
3350
+ let bytes = 0;
3351
+ for (const b of blocks) {
3352
+ const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
3353
+ if (!text) continue;
3354
+ lines.push(text);
3355
+ bytes += text.length * 3;
3356
+ if (bytes > 1024) break;
3357
+ }
3358
+ return lines.join("\n").slice(0, 1024);
3359
+ }
3347
3360
  function parseMarkdownToBlocks(md) {
3348
3361
  const lines = md.split("\n");
3349
3362
  const blocks = [];
@@ -3578,7 +3591,7 @@ function generateHeaderXml() {
3578
3591
  </hh:font>
3579
3592
  </hh:fontface>
3580
3593
  </hh:fontfaces>
3581
- <hh:borderFills itemCnt="1">
3594
+ <hh:borderFills itemCnt="2">
3582
3595
  <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
3583
3596
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
3584
3597
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
@@ -3589,6 +3602,16 @@ function generateHeaderXml() {
3589
3602
  <hh:diagonal type="NONE" width="0.1mm" color="#000000"/>
3590
3603
  <hh:fillInfo/>
3591
3604
  </hh:borderFill>
3605
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
3606
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
3607
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
3608
+ <hh:leftBorder type="SOLID" width="0.12mm" color="#000000"/>
3609
+ <hh:rightBorder type="SOLID" width="0.12mm" color="#000000"/>
3610
+ <hh:topBorder type="SOLID" width="0.12mm" color="#000000"/>
3611
+ <hh:bottomBorder type="SOLID" width="0.12mm" color="#000000"/>
3612
+ <hh:diagonal type="NONE" width="0.1mm" color="#000000"/>
3613
+ <hh:fillInfo/>
3614
+ </hh:borderFill>
3592
3615
  </hh:borderFills>
3593
3616
  <hh:charProperties itemCnt="9">
3594
3617
  ${charPr(0, 1e3, false, false)}
@@ -3624,15 +3647,31 @@ ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
3624
3647
  function generateSecPr() {
3625
3648
  return `<hp:secPr textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" outlineShapeIDRef="0" memoShapeIDRef="0" textVerticalWidthHead="0" masterPageCnt="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="2835" footer="2835" gutter="0" left="5670" right="4252" top="8504" bottom="4252"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr></hp:secPr>`;
3626
3649
  }
3650
+ var TABLE_ID_BASE = 1e3;
3651
+ var tableIdCounter = TABLE_ID_BASE;
3652
+ function nextTableId() {
3653
+ return ++tableIdCounter;
3654
+ }
3627
3655
  function generateTable(rows) {
3628
- const trElements = rows.map((row) => {
3629
- const tdElements = row.map((cell) => {
3656
+ const rowCnt = rows.length;
3657
+ const colCnt = Math.max(...rows.map((r) => r.length), 1);
3658
+ const cellW = Math.floor(44e3 / colCnt);
3659
+ const cellH = 1500;
3660
+ const tblW = cellW * colCnt;
3661
+ const tblH = cellH * rowCnt;
3662
+ const tblId = nextTableId();
3663
+ const trElements = rows.map((row, rowIdx) => {
3664
+ const cells = row.length < colCnt ? [...row, ...Array(colCnt - row.length).fill("")] : row;
3665
+ const tdElements = cells.map((cell, colIdx) => {
3630
3666
  const runs = generateRuns(cell);
3631
- return `<hp:tc><hp:cellSpan colSpan="1" rowSpan="1"/><hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p></hp:tc>`;
3667
+ const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
3668
+ return `<hp:tc name="" header="${rowIdx === 0 ? 1 : 0}" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="1"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="TOP" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${p}</hp:subList><hp:cellAddr colAddr="${colIdx}" rowAddr="${rowIdx}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="${cellW}" height="${cellH}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
3632
3669
  }).join("");
3633
3670
  return `<hp:tr>${tdElements}</hp:tr>`;
3634
3671
  }).join("");
3635
- return `<hp:tbl>${trElements}</hp:tbl>`;
3672
+ const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
3673
+ const tbl = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="1" noShading="0">${tblInner}</hp:tbl>`;
3674
+ return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
3636
3675
  }
3637
3676
  function blocksToSectionXml(blocks) {
3638
3677
  const paraXmls = [];
@@ -3699,6 +3738,20 @@ function blocksToSectionXml(blocks) {
3699
3738
  // src/index.ts
3700
3739
  import { readFile } from "fs/promises";
3701
3740
 
3741
+ // src/hwp5/sentinel.ts
3742
+ var SENTINEL_PATTERNS = [
3743
+ /상위\s*버전의\s*배포용\s*문서/,
3744
+ /최신\s*버전의\s*한글.*뷰어/,
3745
+ /문서를\s*읽으려면/
3746
+ ];
3747
+ function isDistributionSentinel(markdown) {
3748
+ if (!markdown) return false;
3749
+ const hit = SENTINEL_PATTERNS.some((p) => p.test(markdown));
3750
+ if (!hit) return false;
3751
+ const stripped = markdown.split(/\r?\n/).filter((line) => !SENTINEL_PATTERNS.some((p) => p.test(line))).join("").replace(/\s+/g, "");
3752
+ return stripped.length < 120;
3753
+ }
3754
+
3702
3755
  // src/xlsx/parser.ts
3703
3756
  import JSZip4 from "jszip";
3704
3757
  import { DOMParser as DOMParser3 } from "@xmldom/xmldom";
@@ -4711,6 +4764,23 @@ async function parseHwpx(buffer, options) {
4711
4764
  async function parseHwp(buffer, options) {
4712
4765
  try {
4713
4766
  const { markdown, blocks, metadata, outline, warnings, images } = parseHwp5Document(Buffer.from(buffer), options);
4767
+ if (isDistributionSentinel(markdown) && isComFallbackAvailable() && options?.filePath) {
4768
+ try {
4769
+ const { pages, pageCount, warnings: comWarns } = extractTextViaCom(options.filePath);
4770
+ if (pages.some((p) => p && p.trim().length > 0)) {
4771
+ const com = comResultToParseResult(pages, pageCount, comWarns);
4772
+ return {
4773
+ success: true,
4774
+ fileType: "hwp",
4775
+ markdown: com.markdown,
4776
+ blocks: com.blocks,
4777
+ metadata: com.metadata,
4778
+ warnings: com.warnings
4779
+ };
4780
+ }
4781
+ } catch {
4782
+ }
4783
+ }
4714
4784
  return { success: true, fileType: "hwp", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
4715
4785
  } catch (err) {
4716
4786
  return { success: false, fileType: "hwp", error: err instanceof Error ? err.message : "HWP \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err) };
@@ -4719,7 +4789,7 @@ async function parseHwp(buffer, options) {
4719
4789
  async function parsePdf(buffer, options) {
4720
4790
  let parsePdfDocument;
4721
4791
  try {
4722
- const mod = await import("./parser-UHUCMAA7.js");
4792
+ const mod = await import("./parser-DA3CGOZF.js");
4723
4793
  parsePdfDocument = mod.parsePdfDocument;
4724
4794
  } catch {
4725
4795
  return {
@@ -4949,4 +5019,4 @@ export {
4949
5019
  compare,
4950
5020
  parse
4951
5021
  };
4952
- //# sourceMappingURL=chunk-JFPF7B5L.js.map
5022
+ //# sourceMappingURL=chunk-QEZ4CUF7.js.map