kordoc 2.2.1 → 2.2.3

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.
@@ -32,4 +32,4 @@ function parsePageRange(spec, maxPages) {
32
32
  export {
33
33
  parsePageRange
34
34
  };
35
- //# sourceMappingURL=chunk-3TBUDJDE.js.map
35
+ //# sourceMappingURL=chunk-MOL7MDBG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/page-range.ts"],"sourcesContent":["/** 페이지/섹션 범위 파싱 유틸리티 */\n\n/**\n * 페이지 범위 지정을 1-based Set<number>로 변환.\n *\n * @param spec - [1,2,3] 또는 \"1-3\" 또는 \"1,3,5-7\"\n * @param maxPages - 최대 페이지 수 (클램핑 상한)\n * @returns 1-based 페이지 번호 Set\n */\nexport function parsePageRange(spec: number[] | string, maxPages: number): Set<number> {\n const result = new Set<number>()\n if (maxPages <= 0) return result\n\n if (Array.isArray(spec)) {\n for (const n of spec) {\n const page = Math.round(n)\n if (page >= 1 && page <= maxPages) result.add(page)\n }\n return result\n }\n\n if (typeof spec !== \"string\" || spec.trim() === \"\") return result\n\n const parts = spec.split(\",\")\n for (const part of parts) {\n const trimmed = part.trim()\n if (!trimmed) continue\n\n const rangeMatch = trimmed.match(/^(\\d+)\\s*-\\s*(\\d+)$/)\n if (rangeMatch) {\n const start = Math.max(1, parseInt(rangeMatch[1], 10))\n const end = Math.min(maxPages, parseInt(rangeMatch[2], 10))\n for (let i = start; i <= end; i++) result.add(i)\n } else {\n const page = parseInt(trimmed, 10)\n if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page)\n }\n }\n\n return result\n}\n"],"mappings":";;;AASO,SAAS,eAAe,MAAyB,UAA+B;AACrF,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,YAAY,EAAG,QAAO;AAE1B,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI,QAAO;AAE3D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,QAAQ,MAAM,qBAAqB;AACtD,QAAI,YAAY;AACd,YAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AACrD,YAAM,MAAM,KAAK,IAAI,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AAC1D,eAAS,IAAI,OAAO,KAAK,KAAK,IAAK,QAAO,IAAI,CAAC;AAAA,IACjD,OAAO;AACL,YAAM,OAAO,SAAS,SAAS,EAAE;AACjC,UAAI,CAAC,MAAM,IAAI,KAAK,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
package/dist/cli.js CHANGED
@@ -4,11 +4,11 @@ import {
4
4
  parse,
5
5
  sanitizeError,
6
6
  toArrayBuffer
7
- } from "./chunk-FINXMRCH.js";
7
+ } from "./chunk-AIG7SDWU.js";
8
8
  import {
9
9
  detectFormat
10
- } from "./chunk-MUAWCQDY.js";
11
- import "./chunk-3TBUDJDE.js";
10
+ } from "./chunk-5Y2Q3BRW.js";
11
+ import "./chunk-MOL7MDBG.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
@@ -105,7 +105,7 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
105
105
  }
106
106
  });
107
107
  program.command("watch <dir>").description("\uB514\uB809\uD1A0\uB9AC \uAC10\uC2DC \u2014 \uC0C8 \uBB38\uC11C \uC790\uB3D9 \uBCC0\uD658").option("--webhook <url>", "\uACB0\uACFC \uC804\uC1A1 \uC6F9\uD6C5 URL").option("-d, --out-dir <dir>", "\uBCC0\uD658 \uACB0\uACFC \uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC").option("-p, --pages <range>", "\uD398\uC774\uC9C0/\uC139\uC158 \uBC94\uC704").option("--format <type>", "\uCD9C\uB825 \uD615\uC2DD: markdown \uB610\uB294 json", "markdown").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (dir, opts) => {
108
- const { watchDirectory } = await import("./watch-Q6L4UBTC.js");
108
+ const { watchDirectory } = await import("./watch-H672QAW2.js");
109
109
  await watchDirectory({
110
110
  dir,
111
111
  outDir: opts.outDir,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\r\n\r\nimport { readFileSync, writeFileSync, mkdirSync, statSync } from \"fs\"\r\nimport { basename, resolve } from \"path\"\r\nimport { Command } from \"commander\"\r\nimport { parse, detectFormat } from \"./index.js\"\r\nimport type { ParseOptions } from \"./types.js\"\r\nimport { VERSION, toArrayBuffer, sanitizeError } from \"./utils.js\"\r\n\r\nconst program = new Command()\r\n\r\nprogram\r\n .name(\"kordoc\")\r\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\r\n .version(VERSION)\r\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\r\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\r\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\r\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\r\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\r\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (files: string[], opts) => {\r\n const validFormats = [\"markdown\", \"json\"]\r\n if (!validFormats.includes(opts.format)) {\r\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\r\n process.exit(1)\r\n }\r\n for (let fi = 0; fi < files.length; fi++) {\r\n const filePath = files[fi]\r\n const absPath = resolve(filePath)\r\n const fileName = basename(absPath)\r\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\r\n\r\n try {\r\n const fileSize = statSync(absPath).size\r\n if (fileSize > 500 * 1024 * 1024) {\r\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\r\n process.exitCode = 1\r\n continue\r\n }\r\n const buffer = readFileSync(absPath)\r\n const arrayBuffer = toArrayBuffer(buffer)\r\n const format = detectFormat(arrayBuffer)\r\n\r\n if (!opts.silent) {\r\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\r\n }\r\n\r\n const parseOptions: ParseOptions = {}\r\n if (opts.pages) parseOptions.pages = opts.pages as string\r\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\r\n if (!opts.silent) {\r\n parseOptions.onProgress = (current: number, total: number) => {\r\n process.stderr.write(`\\r[kordoc] ${filePrefix}${fileName} (${format}) [${current}/${total}]`)\r\n }\r\n }\r\n const result = await parse(arrayBuffer, parseOptions)\r\n\r\n if (!result.success) {\r\n process.stderr.write(` FAIL\\n`)\r\n process.stderr.write(` → ${result.error}\\n`)\r\n process.exitCode = 1\r\n continue\r\n }\r\n\r\n if (!opts.silent) process.stderr.write(` OK\\n`)\r\n\r\n let markdown = result.markdown\r\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\r\n if (opts.outDir && result.images?.length) {\r\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\r\n }\r\n const output = opts.format === \"json\"\r\n ? JSON.stringify(result, (_key, value) =>\r\n value instanceof Uint8Array ? Buffer.from(value).toString(\"base64\") : value\r\n , 2)\r\n : markdown\r\n\r\n // 이미지 저장 (--out-dir 또는 --output 시)\r\n const saveImages = (dir: string) => {\r\n if (!result.images?.length) return\r\n const imgDir = resolve(dir, \"images\")\r\n mkdirSync(imgDir, { recursive: true })\r\n for (const img of result.images) {\r\n writeFileSync(resolve(imgDir, img.filename), img.data)\r\n }\r\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\r\n }\r\n\r\n if (opts.output && files.length === 1) {\r\n writeFileSync(opts.output, output, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\r\n saveImages(resolve(opts.output, \"..\"))\r\n } else if (opts.outDir) {\r\n mkdirSync(opts.outDir, { recursive: true })\r\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\r\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\r\n writeFileSync(outPath, output, \"utf-8\")\r\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\r\n saveImages(opts.outDir)\r\n } else {\r\n process.stdout.write(output + \"\\n\")\r\n }\r\n } catch (err) {\r\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\r\n process.exitCode = 1\r\n }\r\n }\r\n })\r\n\r\nprogram\r\n .command(\"watch <dir>\")\r\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\r\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\r\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\r\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\r\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\r\n .option(\"--silent\", \"진행 메시지 숨기기\")\r\n .action(async (dir: string, opts) => {\r\n const { watchDirectory } = await import(\"./watch.js\")\r\n await watchDirectory({\r\n dir,\r\n outDir: opts.outDir,\r\n webhook: opts.webhook,\r\n format: opts.format,\r\n pages: opts.pages,\r\n silent: opts.silent,\r\n })\r\n })\r\n\r\nprogram.parse()\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\n\nimport { readFileSync, writeFileSync, mkdirSync, statSync } from \"fs\"\nimport { basename, resolve } from \"path\"\nimport { Command } from \"commander\"\nimport { parse, detectFormat } from \"./index.js\"\nimport type { ParseOptions } from \"./types.js\"\nimport { VERSION, toArrayBuffer, sanitizeError } from \"./utils.js\"\n\nconst program = new Command()\n\nprogram\n .name(\"kordoc\")\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\n .version(VERSION)\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (files: string[], opts) => {\n const validFormats = [\"markdown\", \"json\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\n process.exit(1)\n }\n for (let fi = 0; fi < files.length; fi++) {\n const filePath = files[fi]\n const absPath = resolve(filePath)\n const fileName = basename(absPath)\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\n\n try {\n const fileSize = statSync(absPath).size\n if (fileSize > 500 * 1024 * 1024) {\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\n process.exitCode = 1\n continue\n }\n const buffer = readFileSync(absPath)\n const arrayBuffer = toArrayBuffer(buffer)\n const format = detectFormat(arrayBuffer)\n\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\n }\n\n const parseOptions: ParseOptions = {}\n if (opts.pages) parseOptions.pages = opts.pages as string\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\n if (!opts.silent) {\n parseOptions.onProgress = (current: number, total: number) => {\n process.stderr.write(`\\r[kordoc] ${filePrefix}${fileName} (${format}) [${current}/${total}]`)\n }\n }\n const result = await parse(arrayBuffer, parseOptions)\n\n if (!result.success) {\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${result.error}\\n`)\n process.exitCode = 1\n continue\n }\n\n if (!opts.silent) process.stderr.write(` OK\\n`)\n\n let markdown = result.markdown\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\n if (opts.outDir && result.images?.length) {\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\n }\n const output = opts.format === \"json\"\n ? JSON.stringify(result, (_key, value) =>\n value instanceof Uint8Array ? Buffer.from(value).toString(\"base64\") : value\n , 2)\n : markdown\n\n // 이미지 저장 (--out-dir 또는 --output 시)\n const saveImages = (dir: string) => {\n if (!result.images?.length) return\n const imgDir = resolve(dir, \"images\")\n mkdirSync(imgDir, { recursive: true })\n for (const img of result.images) {\n writeFileSync(resolve(imgDir, img.filename), img.data)\n }\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\n }\n\n if (opts.output && files.length === 1) {\n writeFileSync(opts.output, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\n saveImages(resolve(opts.output, \"..\"))\n } else if (opts.outDir) {\n mkdirSync(opts.outDir, { recursive: true })\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\n writeFileSync(outPath, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\n saveImages(opts.outDir)\n } else {\n process.stdout.write(output + \"\\n\")\n }\n } catch (err) {\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\n process.exitCode = 1\n }\n }\n })\n\nprogram\n .command(\"watch <dir>\")\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (dir: string, opts) => {\n const { watchDirectory } = await import(\"./watch.js\")\n await watchDirectory({\n dir,\n outDir: opts.outDir,\n webhook: opts.webhook,\n format: opts.format,\n pages: opts.pages,\n silent: opts.silent,\n })\n })\n\nprogram.parse()\n"],"mappings":"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","names":[]}
@@ -6,7 +6,7 @@ import {
6
6
  isOldHwpFile,
7
7
  isPdfFile,
8
8
  isZipFile
9
- } from "./chunk-MUAWCQDY.js";
9
+ } from "./chunk-5Y2Q3BRW.js";
10
10
  export {
11
11
  detectFormat,
12
12
  detectZipFormat,
@@ -15,4 +15,4 @@ export {
15
15
  isPdfFile,
16
16
  isZipFile
17
17
  };
18
- //# sourceMappingURL=detect-63IGCXTH.js.map
18
+ //# sourceMappingURL=detect-GYK3HKD5.js.map
package/dist/index.cjs CHANGED
@@ -183,7 +183,7 @@ var import_zlib = require("zlib");
183
183
  var import_xmldom = require("@xmldom/xmldom");
184
184
 
185
185
  // src/utils.ts
186
- var VERSION = true ? "2.2.1" : "0.0.0-dev";
186
+ var VERSION = true ? "2.2.3" : "0.0.0-dev";
187
187
  function toArrayBuffer(buf) {
188
188
  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
189
189
  return buf.buffer;
@@ -501,9 +501,47 @@ function blocksToMarkdown(blocks) {
501
501
  }
502
502
  return lines.join("\n").trim();
503
503
  }
504
+ function hasMergedCells(table) {
505
+ for (const row of table.cells) {
506
+ for (const cell of row) {
507
+ if (cell.colSpan > 1 || cell.rowSpan > 1) return true;
508
+ }
509
+ }
510
+ return false;
511
+ }
512
+ function tableToHtml(table) {
513
+ const { cells, rows: numRows, cols: numCols } = table;
514
+ const skip = /* @__PURE__ */ new Set();
515
+ const lines = ["<table>"];
516
+ for (let r = 0; r < numRows; r++) {
517
+ const tag = r === 0 ? "th" : "td";
518
+ const rowHtml = [];
519
+ for (let c = 0; c < numCols; c++) {
520
+ if (skip.has(`${r},${c}`)) continue;
521
+ const cell = cells[r]?.[c];
522
+ if (!cell) continue;
523
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
524
+ for (let dc = 0; dc < cell.colSpan; dc++) {
525
+ if (dr === 0 && dc === 0) continue;
526
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
527
+ }
528
+ }
529
+ const text = sanitizeText(cell.text).replace(/\n/g, "<br>");
530
+ const attrs = [];
531
+ if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
532
+ if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
533
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
534
+ rowHtml.push(`<${tag}${attrStr}>${text}</${tag}>`);
535
+ }
536
+ if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
537
+ }
538
+ lines.push("</table>");
539
+ return lines.join("\n");
540
+ }
504
541
  function tableToMarkdown(table) {
505
542
  if (table.rows === 0 || table.cols === 0) return "";
506
543
  const { cells, rows: numRows, cols: numCols } = table;
544
+ if (hasMergedCells(table)) return tableToHtml(table);
507
545
  if (numRows === 1 && numCols === 1) {
508
546
  const content = sanitizeText(cells[0][0].text);
509
547
  if (!content) return "";
@@ -6306,6 +6344,23 @@ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
6306
6344
  var NS_OPF = "http://www.idpf.org/2007/opf/";
6307
6345
  var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
6308
6346
  var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
6347
+ var CHAR_NORMAL = 0;
6348
+ var CHAR_BOLD = 1;
6349
+ var CHAR_ITALIC = 2;
6350
+ var CHAR_BOLD_ITALIC = 3;
6351
+ var CHAR_CODE = 4;
6352
+ var CHAR_H1 = 5;
6353
+ var CHAR_H2 = 6;
6354
+ var CHAR_H3 = 7;
6355
+ var CHAR_H4 = 8;
6356
+ var PARA_NORMAL = 0;
6357
+ var PARA_H1 = 1;
6358
+ var PARA_H2 = 2;
6359
+ var PARA_H3 = 3;
6360
+ var PARA_H4 = 4;
6361
+ var PARA_CODE = 5;
6362
+ var PARA_QUOTE = 6;
6363
+ var PARA_LIST = 7;
6309
6364
  async function markdownToHwpx(markdown) {
6310
6365
  const blocks = parseMarkdownToBlocks(markdown);
6311
6366
  const sectionXml = blocksToSectionXml(blocks);
@@ -6327,6 +6382,25 @@ function parseMarkdownToBlocks(md) {
6327
6382
  i++;
6328
6383
  continue;
6329
6384
  }
6385
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
6386
+ if (fenceMatch) {
6387
+ const fence = fenceMatch[1];
6388
+ const lang = fenceMatch[2].trim();
6389
+ const codeLines = [];
6390
+ i++;
6391
+ while (i < lines.length && !lines[i].startsWith(fence)) {
6392
+ codeLines.push(lines[i]);
6393
+ i++;
6394
+ }
6395
+ if (i < lines.length) i++;
6396
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
6397
+ continue;
6398
+ }
6399
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
6400
+ blocks.push({ type: "hr" });
6401
+ i++;
6402
+ continue;
6403
+ }
6330
6404
  const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
6331
6405
  if (headingMatch) {
6332
6406
  blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
@@ -6345,19 +6419,101 @@ function parseMarkdownToBlocks(md) {
6345
6419
  if (cells.length > 0) tableRows.push(cells);
6346
6420
  i++;
6347
6421
  }
6348
- if (tableRows.length > 0) {
6349
- blocks.push({ type: "table", rows: tableRows });
6422
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
6423
+ continue;
6424
+ }
6425
+ if (line.trimStart().startsWith("> ")) {
6426
+ const quoteLines = [];
6427
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
6428
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
6429
+ i++;
6430
+ }
6431
+ for (const ql of quoteLines) {
6432
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
6350
6433
  }
6351
6434
  continue;
6352
6435
  }
6436
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
6437
+ if (listMatch) {
6438
+ const indent = Math.floor(listMatch[1].length / 2);
6439
+ const ordered = /\d/.test(listMatch[2]);
6440
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
6441
+ i++;
6442
+ continue;
6443
+ }
6353
6444
  blocks.push({ type: "paragraph", text: line.trim() });
6354
6445
  i++;
6355
6446
  }
6356
6447
  return blocks;
6357
6448
  }
6449
+ function parseInlineMarkdown(text) {
6450
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
6451
+ text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
6452
+ text = text.replace(/~~([^~]+)~~/g, "$1");
6453
+ const spans = [];
6454
+ const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
6455
+ let lastIdx = 0;
6456
+ for (const match of text.matchAll(regex)) {
6457
+ const idx = match.index;
6458
+ if (idx > lastIdx) {
6459
+ spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
6460
+ }
6461
+ const raw = match[0];
6462
+ if (raw.startsWith("`")) {
6463
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
6464
+ } else if (raw.startsWith("***") || raw.startsWith("___")) {
6465
+ spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
6466
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
6467
+ spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
6468
+ } else {
6469
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
6470
+ }
6471
+ lastIdx = idx + raw.length;
6472
+ }
6473
+ if (lastIdx < text.length) {
6474
+ spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
6475
+ }
6476
+ if (spans.length === 0) {
6477
+ spans.push({ text, bold: false, italic: false, code: false });
6478
+ }
6479
+ return spans;
6480
+ }
6481
+ function spanToCharPrId(span) {
6482
+ if (span.code) return CHAR_CODE;
6483
+ if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
6484
+ if (span.bold) return CHAR_BOLD;
6485
+ if (span.italic) return CHAR_ITALIC;
6486
+ return CHAR_NORMAL;
6487
+ }
6358
6488
  function escapeXml(text) {
6359
6489
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6360
6490
  }
6491
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
6492
+ const spans = parseInlineMarkdown(text);
6493
+ return spans.map((span) => {
6494
+ const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
6495
+ return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
6496
+ }).join("");
6497
+ }
6498
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
6499
+ if (paraPrId === PARA_CODE) {
6500
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
6501
+ }
6502
+ const runs = generateRuns(text, charPrId);
6503
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
6504
+ }
6505
+ function headingParaPrId(level) {
6506
+ if (level === 1) return PARA_H1;
6507
+ if (level === 2) return PARA_H2;
6508
+ if (level === 3) return PARA_H3;
6509
+ return PARA_H4;
6510
+ }
6511
+ function headingCharPrId(level) {
6512
+ if (level === 1) return CHAR_H1;
6513
+ if (level === 2) return CHAR_H2;
6514
+ if (level === 3) return CHAR_H3;
6515
+ return CHAR_H4;
6516
+ }
6361
6517
  function generateContainerXml() {
6362
6518
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
6363
6519
  <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
@@ -6379,21 +6535,50 @@ function generateManifest() {
6379
6535
  </opf:spine>
6380
6536
  </opf:package>`;
6381
6537
  }
6538
+ function charPr(id, height, bold, italic, fontId = 0) {
6539
+ const boldAttr = bold ? ` bold="1"` : "";
6540
+ const italicAttr = italic ? ` italic="1"` : "";
6541
+ return ` <hh:charPr id="${id}" height="${height}" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
6542
+ <hh:fontRef hangul="${fontId}" latin="${fontId}" hanja="${fontId}" japanese="${fontId}" other="${fontId}" symbol="${fontId}" user="${fontId}"/>
6543
+ <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6544
+ <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6545
+ <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6546
+ <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6547
+ </hh:charPr>`;
6548
+ }
6549
+ function paraPr(id, opts = {}) {
6550
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts;
6551
+ return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="AUTO">
6552
+ <hh:align horizontal="${align}" vertical="BASELINE"/>
6553
+ <hh:heading type="NONE" idRef="0" level="0"/>
6554
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
6555
+ <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
6556
+ <hh:margin indent="${indent}" left="0" right="0" prev="${spaceBefore}" next="${spaceAfter}"/>
6557
+ <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
6558
+ <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
6559
+ </hh:paraPr>`;
6560
+ }
6382
6561
  function generateHeaderXml() {
6383
6562
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
6384
6563
  <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" version="1.4" secCnt="1">
6385
6564
  <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
6386
6565
  <hh:refList>
6387
6566
  <hh:fontfaces itemCnt="7">
6388
- <hh:fontface lang="HANGUL" fontCnt="1">
6567
+ <hh:fontface lang="HANGUL" fontCnt="2">
6389
6568
  <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
6390
6569
  <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
6391
6570
  </hh:font>
6571
+ <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
6572
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
6573
+ </hh:font>
6392
6574
  </hh:fontface>
6393
- <hh:fontface lang="LATIN" fontCnt="1">
6575
+ <hh:fontface lang="LATIN" fontCnt="2">
6394
6576
  <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
6395
6577
  <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
6396
6578
  </hh:font>
6579
+ <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
6580
+ <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
6581
+ </hh:font>
6397
6582
  </hh:fontface>
6398
6583
  <hh:fontface lang="HANJA" fontCnt="1">
6399
6584
  <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
@@ -6425,34 +6610,37 @@ function generateHeaderXml() {
6425
6610
  <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
6426
6611
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
6427
6612
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
6428
- <hh:leftBorder type="NONE" width="0.1mm" color="0"/>
6429
- <hh:rightBorder type="NONE" width="0.1mm" color="0"/>
6430
- <hh:topBorder type="NONE" width="0.1mm" color="0"/>
6431
- <hh:bottomBorder type="NONE" width="0.1mm" color="0"/>
6432
- <hh:diagonal type="NONE" width="0.1mm" color="0"/>
6613
+ <hh:leftBorder type="NONE" width="0.1mm" color="#000000"/>
6614
+ <hh:rightBorder type="NONE" width="0.1mm" color="#000000"/>
6615
+ <hh:topBorder type="NONE" width="0.1mm" color="#000000"/>
6616
+ <hh:bottomBorder type="NONE" width="0.1mm" color="#000000"/>
6617
+ <hh:diagonal type="NONE" width="0.1mm" color="#000000"/>
6433
6618
  <hh:fillInfo/>
6434
6619
  </hh:borderFill>
6435
6620
  </hh:borderFills>
6436
- <hh:charProperties itemCnt="1">
6437
- <hh:charPr id="0" height="1000" textColor="0" shadeColor="-1" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0">
6438
- <hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6439
- <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6440
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6441
- <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
6442
- <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
6443
- </hh:charPr>
6621
+ <hh:charProperties itemCnt="9">
6622
+ ${charPr(0, 1e3, false, false)}
6623
+ ${charPr(1, 1e3, true, false)}
6624
+ ${charPr(2, 1e3, false, true)}
6625
+ ${charPr(3, 1e3, true, true)}
6626
+ ${charPr(4, 900, false, false, 1)}
6627
+ ${charPr(5, 1800, true, false, 1)}
6628
+ ${charPr(6, 1400, true, false, 1)}
6629
+ ${charPr(7, 1200, true, false, 1)}
6630
+ ${charPr(8, 1100, true, false, 1)}
6444
6631
  </hh:charProperties>
6445
6632
  <hh:tabProperties itemCnt="0"/>
6446
6633
  <hh:numberings itemCnt="0"/>
6447
6634
  <hh:bullets itemCnt="0"/>
6448
- <hh:paraProperties itemCnt="1">
6449
- <hh:paraPr id="0" tabIDRef="0" condense="0" fontLineHeight="0" snapToGrid="0" suppressOverlap="0" checked="0">
6450
- <hh:parLineBreak lineBreak="BREAK_LINE" wordBreak="BREAK_WORD" breakLatinWord="BREAK_WORD" breakNonLatinWord="BREAK_WORD"/>
6451
- <hh:parMargin left="0" right="0" prev="0" next="0" indent="0"/>
6452
- <hh:parBorder borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
6453
- <hh:parShade borderFillIDRef="0"/>
6454
- <hh:parTabList/>
6455
- </hh:paraPr>
6635
+ <hh:paraProperties itemCnt="8">
6636
+ ${paraPr(0)}
6637
+ ${paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}
6638
+ ${paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}
6639
+ ${paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}
6640
+ ${paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}
6641
+ ${paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 })}
6642
+ ${paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 })}
6643
+ ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
6456
6644
  </hh:paraProperties>
6457
6645
  <hh:styles itemCnt="1">
6458
6646
  <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
@@ -6461,34 +6649,78 @@ function generateHeaderXml() {
6461
6649
  <hh:compatibleDocument targetProgram="HWP2018"/>
6462
6650
  </hh:head>`;
6463
6651
  }
6464
- function generateParagraph(text) {
6465
- return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
6652
+ function generateSecPr() {
6653
+ 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>`;
6466
6654
  }
6467
6655
  function generateTable(rows) {
6468
6656
  const trElements = rows.map((row) => {
6469
- const tdElements = row.map(
6470
- (cell) => `<hp:tc><hp:cellSpan colSpan="1" rowSpan="1"/>${generateParagraph(cell)}</hp:tc>`
6471
- ).join("");
6657
+ const tdElements = row.map((cell) => {
6658
+ const runs = generateRuns(cell);
6659
+ return `<hp:tc><hp:cellSpan colSpan="1" rowSpan="1"/><hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p></hp:tc>`;
6660
+ }).join("");
6472
6661
  return `<hp:tr>${tdElements}</hp:tr>`;
6473
6662
  }).join("");
6474
6663
  return `<hp:tbl>${trElements}</hp:tbl>`;
6475
6664
  }
6476
6665
  function blocksToSectionXml(blocks) {
6477
- const body = blocks.map((block) => {
6666
+ const paraXmls = [];
6667
+ let isFirst = true;
6668
+ for (const block of blocks) {
6669
+ let xml = "";
6478
6670
  switch (block.type) {
6479
- case "heading":
6480
- return generateParagraph(block.text || "");
6481
- case "table":
6482
- return block.rows ? generateTable(block.rows) : "";
6671
+ case "heading": {
6672
+ const pId = headingParaPrId(block.level || 1);
6673
+ const cId = headingCharPrId(block.level || 1);
6674
+ xml = generateParagraph(block.text || "", pId, cId);
6675
+ break;
6676
+ }
6483
6677
  case "paragraph":
6484
- return generateParagraph(block.text || "");
6485
- default:
6486
- return "";
6678
+ xml = generateParagraph(block.text || "");
6679
+ break;
6680
+ case "code_block": {
6681
+ const codeLines = (block.text || "").split("\n");
6682
+ xml = codeLines.map((line) => generateParagraph(line || " ", PARA_CODE)).join("\n ");
6683
+ break;
6684
+ }
6685
+ case "blockquote":
6686
+ xml = generateParagraph(block.text || "", PARA_QUOTE);
6687
+ break;
6688
+ case "list_item": {
6689
+ const marker = block.ordered ? `${(block.indent || 0) + 1}. ` : "\xB7 ";
6690
+ const indentPrefix = " ".repeat(block.indent || 0);
6691
+ xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
6692
+ break;
6693
+ }
6694
+ case "hr":
6695
+ xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500</hp:t></hp:run></hp:p>`;
6696
+ break;
6697
+ case "table":
6698
+ if (block.rows) {
6699
+ if (isFirst) {
6700
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run>`;
6701
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
6702
+ isFirst = false;
6703
+ }
6704
+ xml = generateTable(block.rows);
6705
+ }
6706
+ break;
6707
+ }
6708
+ if (!xml) continue;
6709
+ if (isFirst && block.type !== "table") {
6710
+ xml = xml.replace(
6711
+ /<hp:run charPrIDRef="(\d+)">/,
6712
+ `<hp:run charPrIDRef="$1">${generateSecPr()}`
6713
+ );
6714
+ isFirst = false;
6487
6715
  }
6488
- }).join("\n ");
6716
+ paraXmls.push(xml);
6717
+ }
6718
+ if (paraXmls.length === 0) {
6719
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`);
6720
+ }
6489
6721
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
6490
6722
  <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
6491
- ${body}
6723
+ ${paraXmls.join("\n ")}
6492
6724
  </hs:sec>`;
6493
6725
  }
6494
6726